How to control the scroll position of a ListBox in a MVVM WPF app

后端 未结 4 581
后悔当初
后悔当初 2020-12-05 18:05

I have got a big ListBox with vertical scrolling enabled, my MVVM has New and Edit ICommands. I am adding new item to the end of the collection but I want the scrollbar also

相关标签:
4条回答
  • 2020-12-05 18:38

    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

    0 讨论(0)
  • 2020-12-05 18:38

    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.

    0 讨论(0)
  • 2020-12-05 18:40

    If the above code doesn't work for you, give this a try

    public class ListBoxExtenders : DependencyObject
    {
        public static readonly DependencyProperty AutoScrollToCurrentItemProperty = DependencyProperty.RegisterAttached("AutoScrollToCurrentItem", typeof(bool), typeof(ListBoxExtenders), new UIPropertyMetadata(default(bool), OnAutoScrollToCurrentItemChanged));
    
        public static bool GetAutoScrollToCurrentItem(DependencyObject obj)
        {
            return (bool)obj.GetValue(AutoScrollToSelectedItemProperty);
        }
    
        public static void SetAutoScrollToCurrentItem(DependencyObject obj, bool value)
        {
            obj.SetValue(AutoScrollToSelectedItemProperty, value);
        }
    
        public static void OnAutoScrollToCurrentItemChanged(DependencyObject s, DependencyPropertyChangedEventArgs e)
        {
            var listBox = s as ListBox;
            if (listBox != null)
            {
                var listBoxItems = listBox.Items;
                if (listBoxItems != null)
                {
                    var newValue = (bool)e.NewValue;
    
                    var autoScrollToCurrentItemWorker = new EventHandler((s1, e2) => OnAutoScrollToCurrentItem(listBox, listBox.Items.CurrentPosition));
    
                    if (newValue)
                        listBoxItems.CurrentChanged += autoScrollToCurrentItemWorker;
                    else
                        listBoxItems.CurrentChanged -= autoScrollToCurrentItemWorker;
                }
            }
        }
    
        public static void OnAutoScrollToCurrentItem(ListBox listBox, int index)
        {
            if (listBox != null && listBox.Items != null && listBox.Items.Count > index && index >= 0)
                listBox.ScrollIntoView(listBox.Items[index]);
        }
    
    }
    

    Usage in XAML

    <ListBox IsSynchronizedWithCurrentItem="True" extenders:ListBoxExtenders.AutoScrollToCurrentItem="True" ..../>
    
    0 讨论(0)
  • 2020-12-05 18:47

    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">
    
    0 讨论(0)
提交回复
热议问题