I am using the ScrollViewer with the MVVM pattern, and a list of items is wrapped by the ScrollViewer, such as
<ScrollViewer>
<ListView>
<ListView.View>
<GridView>
<GridViewColumn
Header = "Name"
DisplayMemberBinding="{Binding Path=Name}"
/>
</GridView>
</ListView.View>
</ListView>
</ScrollViewer>
The items of the listview are bound to a collection of objects in the viewmodel. I want the scrollviewer to scroll to the top whenever a item is added or removed from the collection.
I need the viewmodel to trigger the event, rather than using the ScrollToTop()
method in the code-behind of the view.
IMHO, the clearest way to do this is using a "Behavior" via an
AttachedProperty
. AnAttachedProperty
is a mechanism to extend existing controls functionality.First, create a class to hold the
AtachedProperty
, for instance:This attached property allows a
ScrollViewer
having "magically" a new property of typeBoolean
, acting like aDependencyProperty
in your XAML. If you bind this property to a standard property in your ViewModel, for instance:(again, the name is up to you) and then you set this
Reset
property totrue
, yourScrollViewer
will scroll to top. I have named theAtachedProperty
as AutoScrollToTop, but the name is not important for this purpose.The XAML will be something like:
Note:
my
is the namespace where yourScrollViewerBehavior
class lives. For example:xmlns:my="clr-namespace:MyApp.Behaviors"
Finally, the only thing you have to do in your ViewModel is to set
Reset = true
when you like, in your case, when you add or remove an element from the collection.The simplest correct way to do this in MVVM is by creating an event in your viewmodel and subscribing to it from your view. And then, in the event handler, call
ScrollToTop
.You fire the event from your viewmodel every time your collection is modified, for instance, and then it's up to the view to react to that event and scroll the list to the top.
Even if this involves some code-behind and demands that the view knows part of its viewmodel, it doesn't violate the MVVM pattern, unlike other workarounds.
EDIT: But it can be refined a lot more, of course. If you want to avoid using code-behind, look for DataEventTriggers. If you don't mind about code-behind but are concerned about memory leaks, look for weak events.
And finally, since the logic you want is 100% view-related (have the ListView scroll every time an item is added or removed to it), you could also implement it as a Behavior / attached property, or extending the ListView. That could get a tad more convoluted, but I encourage you to give those options some thought.
Create a new ListView control which extend Listview and use this new one instead
I have also faced a similar scenario where I needed to assign ScrollViewer's HorizontalOffset and VerticalOffset programmatically. I am afraid there is no direct binding mechanism for this. What I did was a way around (believe me, I still do not like the approach I followed, but I did not find any other option). Here is what I suggest:
Hook the ScrollViewer's Loaded event, cast the sender object to ScrollViewer and assign it to a property in DataContext (Means you need to keep a ScrollViewer propery in DataContext which will hold the reference of ScrollViewer in the UI). Hook up ObservableCollection's CollectionChanged events in ViewModel and using the ScrollViewer property, you can call methods like ScrollToTop() etc.
This is just a way around. I am still looking for better solution.