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.
CodePudding user response:
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 viewmodel via the "CommandParameter", and binding it with x:Bind to the required controls name. This technicaly 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
//Impliment 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.