Scroll scrollviewer to the top via viewmodel

I am using ScrollViewer with an MVVM template, and the list of items is wrapped by a ScrollViewer, e.g.

<ScrollViewer> <ListView> <ListView.View> <GridView> <GridViewColumn Header = "Name" DisplayMemberBinding="{Binding Path=Name}" /> </GridView> </ListView.View> </ListView> </ScrollViewer> 

Listview elements are tied to a collection of objects in the viewmodel. I want the scrollviewer to scroll up whenever an element is added or removed from the collection.
I need a viewmodel to fire an event, and not using the ScrollToTop() method in the view code.

+6
source share
4 answers

IMHO, the clearest way to do this is to use Behavior through AttachedProperty . AttachedProperty is a mechanism for expanding existing management capabilities.

First create an AtachedProperty storage AtachedProperty , for example:

 public class ScrollViewerBehavior { public static bool GetAutoScrollToTop(DependencyObject obj) { return (bool)obj.GetValue(AutoScrollToTopProperty); } public static void SetAutoScrollToTop(DependencyObject obj, bool value) { obj.SetValue(AutoScrollToTopProperty, value); } public static readonly DependencyProperty AutoScrollToTopProperty = DependencyProperty.RegisterAttached("AutoScrollToTop", typeof(bool), typeof(ScrollViewerBehavior), new PropertyMetadata(false, (o, e) => { var scrollViewer = o as ScrollViewer; if (scrollViewer == null) { return; } if ((bool)e.NewValue) { scrollViewer.ScrollToTop(); SetAutoScrollToTop(o, false); } })); } 

This attached property allows ScrollViewer have a “magically” new property of type Boolean , acting as a DependencyProperty in your XAML. If you bind this property to a standard property in the ViewModel, for example:

 private bool _reset; public bool Reset { get { return _reset; } set { _reset = value; if(PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("Reset")); } } 

(again, the name is up to you) and then you set this Reset property to true , your ScrollViewer will scroll up. I named AtachedProperty as AutoScrollToTop, but this name is not important for this purpose.

XAML will look something like this:

 <ScrollViewer my:ScrollViewerBehavior.AutoScrollToTop="{Binding Reset, Mode=TwoWay}"> <ListView> <ListView.View> <GridView> <GridViewColumn Header = "Name" DisplayMemberBinding="{Binding Path=Name}" /> </GridView> </ListView.View> </ListView> </ScrollViewer> 

Note: my is the namespace in which your ScrollViewerBehavior class ScrollViewerBehavior . For example: xmlns:my="clr-namespace:MyApp.Behaviors"

Finally, the only thing you need to do in your ViewModel is to set Reset = true when you like, in your case when you add or remove an item from the collection.

+11
source

Create a new ListView control that extends Listview and uses this new one instead

 public class ScrollListView : ListView { protected override void OnItemsChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e) { if (e.OldItems.Count > 0) this.ScrollIntoView(e.OldItems[e.OldStartingIndex]); base.OnItemsChanged(e); } } 
+2
source

I also came across a similar scenario when I needed to programmatically configure ScrollViewer HorizontalOffset and VerticalOffset. I am afraid there is no direct binding mechanism for this. What I did was the way (believe me, I still don't like the approach that I followed, but I did not find another option). Here is what I suggest:

Connect the ScrollViewer Loaded event, pass the ScrollViewer sender object and assign it to the property in the DataContext (which means you need to save the ScrollViewer property in the DataContext, which will contain the ScrollViewer link in the user interface). Catching an ObservableCollection CollectionChanged event in the ViewModel and using the ScrollViewer property, you can call methods like ScrollToTop (), etc.

This is just a way. I'm still looking for a better solution.

-1
source

The easiest way to do this in MVVM is to create an event in your view model and subscribe to it from your view. Then in the event handler call ScrollToTop .

You fire an event from your view model every time your collection changes, for example, and then it appears in the view to respond to this event and scroll up.

Even if it is related to some code and requires the view to know part of its view model, it does not violate the MVVM pattern, unlike other workarounds.

 public interface IMyViewModel { event EventHandler MyCollectionChanged; } public class MyViewModel : IMyViewModel { public event EventHandler MyCollectionChanged; // More viewmodel related stuff protected virtual void OnMyCollectionChanged(EventArgs e) { if (MyCollectionChanged != null) MyCollectionChanged(this, e); } } public class MyWindow : Window { public MyWindow(IMyViewModel viewModel) { this.DataContext = viewModel; InitializeComponent(); (this.DataContext as IViewModel).MyCollectionChanged+= MyCollectionChangedEventHandler; } private void MyCollectionChangedEventHandler(object sender, EventArgs e) { // Do view related stuff scrollViewer.ScrollToTop(); } } 

EDIT: But, of course, it can be clarified. If you want to avoid using code, find DataEventTriggers. If you don't mind the code but are concerned about memory leaks, look at the weak events.

And finally, since the logic you want is 100% view-related (scroll through the ListView every time an item is added or removed to it), you can also implement it as a Behavior / attach property or extend the ListView. This may be a bit harder, but I recommend you think about these options.

-1
source

Source: https://habr.com/ru/post/888763/


All Articles