WPF - reset ListBox scroll position when ItemsSource changes

前端 未结 5 2404
有刺的猬
有刺的猬 2021-02-18 23:41

I currently have a ListBox whose ItemsSource collection is bound to a property on my viewmodel, of type IEnumerable. When that preoprty\'s reference changes, the ListBox update

相关标签:
5条回答
  • 2021-02-19 00:00

    I'm unable to reproduce your problem (for me, the ListBox is scrolled to the last item in the new collection when changing ItemsSource). Anyway, to scroll the ListBox to the top every time its ItemsSource changes you can use some code behind. First listen to changes in the ItemsSourceProperty and then scroll the ListBox to the top once its items has been generated

    Update

    Made an attached behavior that does this instead to avoid code behind. It can be used like this

    <ListBox ...
             behaviors:ScrollToTopBehavior.ScrollToTop="True"/>
    

    ScrollToTopBehavior

    public static class ScrollToTopBehavior 
    {
        public static readonly DependencyProperty ScrollToTopProperty = 
            DependencyProperty.RegisterAttached 
            (
                "ScrollToTop", 
                typeof(bool),
                typeof(ScrollToTopBehavior),
                new UIPropertyMetadata(false, OnScrollToTopPropertyChanged) 
            );
        public static bool GetScrollToTop(DependencyObject obj) 
        {
            return (bool)obj.GetValue(ScrollToTopProperty); 
        }
        public static void SetScrollToTop(DependencyObject obj, bool value) 
        {
            obj.SetValue(ScrollToTopProperty, value); 
        }
        private static void OnScrollToTopPropertyChanged(DependencyObject dpo, 
                                                         DependencyPropertyChangedEventArgs e) 
        {
            ItemsControl itemsControl = dpo as ItemsControl;
            if (itemsControl != null) 
            {
                DependencyPropertyDescriptor dependencyPropertyDescriptor =
                        DependencyPropertyDescriptor.FromProperty(ItemsControl.ItemsSourceProperty, typeof(ItemsControl));
                if (dependencyPropertyDescriptor != null)
                {
                    if ((bool)e.NewValue == true) 
                    {
                        dependencyPropertyDescriptor.AddValueChanged(itemsControl, ItemsSourceChanged);
                    }
                    else 
                    {
                        dependencyPropertyDescriptor.RemoveValueChanged(itemsControl, ItemsSourceChanged);
                    }
                } 
            } 
        }
        static void ItemsSourceChanged(object sender, EventArgs e)
        {
            ItemsControl itemsControl = sender as ItemsControl;
            EventHandler eventHandler = null;
            eventHandler = new EventHandler(delegate
            {
                if (itemsControl.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
                {
                    ScrollViewer scrollViewer = GetVisualChild<ScrollViewer>(itemsControl) as ScrollViewer;
                    scrollViewer.ScrollToTop();
                    itemsControl.ItemContainerGenerator.StatusChanged -= eventHandler;
                }
            });
            itemsControl.ItemContainerGenerator.StatusChanged += eventHandler;
        }
    }
    

    And an implementation of GetVisualChild

    private T GetVisualChild<T>(DependencyObject parent) where T : Visual
    {
        T child = default(T);
        int numVisuals = VisualTreeHelper.GetChildrenCount(parent);
        for (int i = 0; i < numVisuals; i++)
        {
            Visual v = (Visual)VisualTreeHelper.GetChild(parent, i);
            child = v as T;
            if (child == null)
            {
                child = GetVisualChild<T>(v);
            }
            if (child != null)
            {
                break;
            }
        }
        return child;
    }
    
    0 讨论(0)
  • 2021-02-19 00:06

    Late answer:

    A simple solution is to add an event handler for the TargetUpdated event, and set NotifyOnTargetUpdated=True on the ItemsSource binding:

    <ListBox x:Name="listBox" 
             ItemsSource="{Binding MySource, NotifyOnTargetUpdated=True}"
             TargetUpdated="ListBox_TargetUpdated"/>
    

    and in the event handler, scroll to the top item:

    private void ListBox_TargetUpdated(object sender, DataTransferEventArgs e)
    {
        if (listBox.Items.Count > 0)
        {
            listBox.ScrollIntoView(listBox.Items[0]);
        }
    }
    
    0 讨论(0)
  • 2021-02-19 00:09

    Try this:

    if (listBox.Items.Count > 0) {
        listBox.ScrollIntoView(listBox.Items[0]); 
    }
    
    0 讨论(0)
  • 2021-02-19 00:09

    Improved Fredrik Hedblad's answer to work with ObservableCollection:

    public static class ItemsControlAttachedProperties
    {
        #region ScrollToTopOnItemsSourceChange Property
    
        public static readonly DependencyProperty ScrollToTopOnItemsSourceChangeProperty =
            DependencyProperty.RegisterAttached(
                "ScrollToTopOnItemsSourceChange",
                typeof(bool),
                typeof(ItemsControlAttachedProperties),
                new UIPropertyMetadata(false, OnScrollToTopOnItemsSourceChangePropertyChanged));
    
        public static bool GetScrollToTopOnItemsSourceChange(DependencyObject obj)
        {
            return (bool) obj.GetValue(ScrollToTopOnItemsSourceChangeProperty);
        }
    
        public static void SetScrollToTopOnItemsSourceChange(DependencyObject obj, bool value)
        {
            obj.SetValue(ScrollToTopOnItemsSourceChangeProperty, value);
        }
    
        static void OnScrollToTopOnItemsSourceChangePropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            var itemsControl = obj as ItemsControl;
            if (itemsControl == null)
            {
                throw new Exception("ScrollToTopOnItemsSourceChange Property must be attached to an ItemsControl based control.");
            }
    
            DependencyPropertyDescriptor descriptor =
                DependencyPropertyDescriptor.FromProperty(ItemsControl.ItemsSourceProperty, typeof(ItemsControl));
            if (descriptor != null)
            {
                if ((bool) e.NewValue)
                {
                    descriptor.AddValueChanged(itemsControl, ItemsSourceChanged);
                }
                else
                {
                    descriptor.RemoveValueChanged(itemsControl, ItemsSourceChanged);
                }
            }
        }
    
        static void ItemsSourceChanged(object sender, EventArgs e)
        {
            var itemsControl = sender as ItemsControl;
            DoScrollToTop(itemsControl);
    
            var collection = itemsControl.ItemsSource as INotifyCollectionChanged;
            if (collection != null)
            {
                collection.CollectionChanged += (o, args) => DoScrollToTop(itemsControl);
            }
        }
    
        static void DoScrollToTop(ItemsControl itemsControl)
        {
            EventHandler eventHandler = null;
            eventHandler =
                delegate
                {
                    if (itemsControl.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
                    {
                        var scrollViewer = GetVisualChild<ScrollViewer>(itemsControl);
                        scrollViewer.ScrollToTop();
                        itemsControl.ItemContainerGenerator.StatusChanged -= eventHandler;
                    }
                };
            itemsControl.ItemContainerGenerator.StatusChanged += eventHandler;
        }
    
        static T GetVisualChild<T>(DependencyObject parent) where T : Visual
        {
            T child = default(T);
            int numVisuals = VisualTreeHelper.GetChildrenCount(parent);
            for (var i = 0; i < numVisuals; i++)
            {
                var v = (Visual) VisualTreeHelper.GetChild(parent, i);
                child = v as T ?? GetVisualChild<T>(v);
                if (child != null)
                {
                    break;
                }
            }
            return child;
        }
    
        #endregion
    }
    
    0 讨论(0)
  • 2021-02-19 00:25

    When you format the control, you select a range of cells as the selection choices which are then listed in the list box. You also select a cell as the link to the selected choices in which a number will be displayed depending on the position of the selection in the list. 1 for first in the list, 2 for second etc. The code is quite simply:-

    Range("A1")Select

    Selection = 1

    Change ("A1") to the cell you have linked and change the 1 to the position in the list you want selected.

    The cell reference being a link works both ways - if you change your selection, the number in the cell changes and if you change the number in the cell, the highlighted selection changes.

    0 讨论(0)
提交回复
热议问题