How to control the scroll position of a ListBox in a MVVM WPF app
I typically set IsSynchronizedWithCurrentItem="True"
on the ListBox
. Then I add a SelectionChanged
handler and always bring the selected item into view, with code like this:
private void BringSelectionIntoView(object sender, SelectionChangedEventArgs e) { Selector selector = sender as Selector; if (selector is ListBox) { (selector as ListBox).ScrollIntoView(selector.SelectedItem); } }
From my VM I can get the default collection view and use one of the MoveCurrent*()
methods to ensure that the item being edited is the current item.
CollectionViewSource.GetDefaultView(_myCollection).MoveCurrentTo(thisItem);
NOTE: Edited to use ListBox.ScrollIntoView()
to accomodate virtualization
Using this in MVVM can be easily accomplished via an attached behavior like so:
using System.Windows.Controls;using System.Windows.Interactivity;namespace Jarloo.Sojurn.Behaviors{ public sealed class ScrollIntoViewBehavior : Behavior<ListBox> { protected override void OnAttached() { base.OnAttached(); AssociatedObject.SelectionChanged += ScrollIntoView; } protected override void OnDetaching() { AssociatedObject.SelectionChanged -= ScrollIntoView; base.OnDetaching(); } private void ScrollIntoView(object o, SelectionChangedEventArgs e) { ListBox b = (ListBox) o; if (b == null) return; if (b.SelectedItem == null) return; ListBoxItem item = (ListBoxItem) ((ListBox) o).ItemContainerGenerator.ContainerFromItem(((ListBox) o).SelectedItem); if (item != null) item.BringIntoView(); } }}
Then in the View ad this reference at the top:
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
And do this:
<ListBox ItemsSource="{Binding MyData}" SelectedItem="{Binding MySelectedItem}"> <i:Interaction.Behaviors> <behaviors:ScrollIntoViewBehavior /> </i:Interaction.Behaviors></ListBox>
Now when the SelectedItem changes the behavior will do the BringIntoView() call for you.
This is the attached property form of the accepted answer:
using System.Windows;using System.Windows.Controls;namespace CommonBehaviors{ public static class ScrollCurrentItemIntoViewBehavior { public static readonly DependencyProperty AutoScrollToCurrentItemProperty = DependencyProperty.RegisterAttached("AutoScrollToCurrentItem", typeof(bool), typeof(ScrollCurrentItemIntoViewBehavior), new UIPropertyMetadata(default(bool), OnAutoScrollToCurrentItemChanged)); public static bool GetAutoScrollToCurrentItem(DependencyObject obj) { return (bool)obj.GetValue(AutoScrollToCurrentItemProperty); } public static void OnAutoScrollToCurrentItemChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) { var listBox = obj as ListBox; if (listBox == null) return; var newValue = (bool)e.NewValue; if (newValue) listBox.SelectionChanged += listBoxSelectionChanged; else listBox.SelectionChanged -= listBoxSelectionChanged; } static void listBoxSelectionChanged(object sender, SelectionChangedEventArgs e) { var listBox = sender as ListBox; if (listBox == null || listBox.SelectedItem == null || listBox.Items == null) return; listBox.Items.MoveCurrentTo(listBox.SelectedItem); listBox.ScrollIntoView(listBox.SelectedItem); } public static void SetAutoScrollToCurrentItem(DependencyObject obj, bool value) { obj.SetValue(AutoScrollToCurrentItemProperty, value); } }}
Usage:
<ListBox ItemsSource="{Binding}" IsSynchronizedWithCurrentItem="True" behaviors:ScrollCurrentItemIntoViewBehavior.AutoScrollToCurrentItem="True">