Search code examples
listviewmvvmscrollwinui-3

WinUI 3. Programmatically scroll listview using MVVM


I'm trying to programmatically scroll the content of a listview, using two buttons, (one for each direction) and the MvvM pattern.

So far all other solutions i could find made use of x:Name and the code behind to access the listview and call ScrollIntoView(Object) function. I thought i could make use of the VisualTreeHelper and the FindChildren<T>, however this also requires the x:Name to find and return the listview object for my ViewModel to use.

Xaml:

<!-- ListView to scroll through: -->
<ListView
    SelectionMode="None"
    ItemsSource="{Binding ListOfListItems}">
        <ListView.ItemTemplate>
            <DataTemplate x:DataType="UcViewModel:ListItemViewModel">
                <Uc:ListItemUserControl/>
            </DataTemplate>
        </ListView.ItemTemplate>
</ListView>

<!-- One of my two buttons: -->
<Button
    Content="Scroll up"
    Command="{Binding ScrollUp}"/>

ViewModel:

private DelegateCommand _scrollUp;
public ICommand ScrollUp => _scrollUp ??= new DelegateCommand(PerformScrollUp);

private void PerformScrollUp(object commandParameter)
{
    //Here i want to call ListView.BringIntoView(NextItem)
}

The MVVM library I'm using is the: Microsoft.Toolkit.Mvvm

I've tried reading through documentation as well, but as far as i understand i need to find a way to access the ListView Object in my ViewModel, and I'm unable to figure out how I would achieve this. I am somewhat new both to WinUI 3 and C#, so please say if there is any missing info, and I'll do my best to provide what is needed.

Edit: As I'm using DI for all pages and ViewModels, I do believe it's impossible to simply inject the listview into the ViewModels constructor in the code behind, using the x:Name. That said I'm looking to keep the code-behind as untouched as possible, to follow the MVVM pattern.


Solution

  • After having put my problem a bit behind me and looked at it with new eyes, i found the following solution. However I'm unaware about its efficiency, and / or if it is bad coding principle.

    What I do, is passing the control to the view model via the CommandParameter, and binding it with x:Bind to the required controls name. This technically uses bindings to the code-behind, however the code-behind it self is left unmodified.

    In the xaml:

    <ScrollViewer
       x:Name = "TargetScrollViewer">
       <ListView>
          //Disables listviews internal scrollviewer
          //Implement rest of ListView code
       </ListView>
    </ScrollViewer>
    
    <Button
       Command = "{Binding ScrollUp}"
       CommandParameter = "{x:Bind TargetScrollViewer}"/>
    
    <Button
       Command = "{Binding ScrollDown}"
       CommandParameter = "{x:Bind TargetScrollViewer}"/>
    

    Many seems to recommend using the listviews "ScrollIntoView", If this is the case just parse the Listview rather than the scrollviewer as the command parameter.

    And inside the viewmodel:

    private DelegateCommand _scrollDown;
    public ICommand ScrollDown => _scrollDown ??= new DelegateCommand(PerformScrollDown);
    
    private void PerformScrollDown(object commandParameter)
    {
        var scrollcontrol = commandParameter as ScrollViewer;
        scrollcontrol.ScrollToVerticalOffset(scrollcontrol.VerticalOffset + 72);
    }
    

    Inside the function I simply cast the command parameter back to a Scrollviewer, and call the required functions.

    In my case I'm using "Syncfusion" for the DelegateCommand to bind buttons to the view.