How can I have a ListBox auto-scroll when a new item is added?

后端 未结 12 2363
走了就别回头了
走了就别回头了 2020-11-28 20:34

I have a WPF ListBox that is set to scroll horizontally. The ItemsSource is bound to an ObservableCollection in my ViewModel class. Every time a new item is added, I want th

12条回答
  •  轻奢々
    轻奢々 (楼主)
    2020-11-28 20:53

    I was not happy with proposed solutions.

    • I didn't want to use "leaky" property descriptors.
    • I didn't want to add Rx dependency and 8-line query for seemingly trivial task. Neither did I want a constantly running timer.
    • I did like shawnpfiore's idea though, so I've built an attached behavior on top of it, which so far works well in my case.

    Here is what I ended up with. Maybe it will save somebody some time.

    public class AutoScroll : Behavior
    {
        public static readonly DependencyProperty ModeProperty = DependencyProperty.Register(
            "Mode", typeof(AutoScrollMode), typeof(AutoScroll), new PropertyMetadata(AutoScrollMode.VerticalWhenInactive));
        public AutoScrollMode Mode
        {
            get => (AutoScrollMode) GetValue(ModeProperty);
            set => SetValue(ModeProperty, value);
        }
    
        protected override void OnAttached()
        {
            base.OnAttached();
            AssociatedObject.Loaded += OnLoaded;
            AssociatedObject.Unloaded += OnUnloaded;
        }
    
        protected override void OnDetaching()
        {
            Clear();
            AssociatedObject.Loaded -= OnLoaded;
            AssociatedObject.Unloaded -= OnUnloaded;
            base.OnDetaching();
        }
    
        private static readonly DependencyProperty ItemsCountProperty = DependencyProperty.Register(
            "ItemsCount", typeof(int), typeof(AutoScroll), new PropertyMetadata(0, (s, e) => ((AutoScroll)s).OnCountChanged()));
        private ScrollViewer _scroll;
    
        private void OnLoaded(object sender, RoutedEventArgs e)
        {
            var binding = new Binding("ItemsSource.Count")
            {
                Source = AssociatedObject,
                Mode = BindingMode.OneWay
            };
            BindingOperations.SetBinding(this, ItemsCountProperty, binding);
            _scroll = AssociatedObject.FindVisualChild() ?? throw new NotSupportedException("ScrollViewer was not found!");
        }
    
        private void OnUnloaded(object sender, RoutedEventArgs e)
        {
            Clear();
        }
    
        private void Clear()
        {
            BindingOperations.ClearBinding(this, ItemsCountProperty);
        }
    
        private void OnCountChanged()
        {
            var mode = Mode;
            if (mode == AutoScrollMode.Vertical)
            {
                _scroll.ScrollToBottom();
            }
            else if (mode == AutoScrollMode.Horizontal)
            {
                _scroll.ScrollToRightEnd();
            }
            else if (mode == AutoScrollMode.VerticalWhenInactive)
            {
                if (_scroll.IsKeyboardFocusWithin) return;
                _scroll.ScrollToBottom();
            }
            else if (mode == AutoScrollMode.HorizontalWhenInactive)
            {
                if (_scroll.IsKeyboardFocusWithin) return;
                _scroll.ScrollToRightEnd();
            }
        }
    }
    
    public enum AutoScrollMode
    {
        /// 
        /// No auto scroll
        /// 
        Disabled,
        /// 
        /// Automatically scrolls horizontally, but only if items control has no keyboard focus
        /// 
        HorizontalWhenInactive,
        /// 
        /// Automatically scrolls vertically, but only if itmes control has no keyboard focus
        /// 
        VerticalWhenInactive,
        /// 
        /// Automatically scrolls horizontally regardless of where the focus is
        /// 
        Horizontal,
        /// 
        /// Automatically scrolls vertically regardless of where the focus is
        /// 
        Vertical
    }
    

提交回复
热议问题