添加新项目时,如何让 ListBox 自动滚动? [英] How can I have a ListBox auto-scroll when a new item is added?

查看:18
本文介绍了添加新项目时,如何让 ListBox 自动滚动?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个设置为水平滚动的 WPF ListBox.ItemsSource 绑定到我的 ViewModel 类中的 ObservableCollection.每次添加新项目时,我都希望 ListBox 向右滚动,以便新项目可见.

ListBox 是在 DataTemplate 中定义的,因此我无法在我的代码隐藏文件中按名称访问 ListBox.

如何让 ListBox 始终滚动以显示最新添加的项目?

我想知道何时向 ListBox 添加了新项目,但我没有看到执行此操作的事件.

解决方案

您可以使用附加属性扩展 ListBox 的行为.在您的情况下,我将定义一个名为 ScrollOnNewItem 的附加属性,当设置为 true 时,它会挂接到列表框项目源的 INotifyCollectionChanged 事件中,然后检测到新项目,将列表框滚动到它.

示例:

class ListBoxBehavior{静态只读字典关联 =新字典();public static bool GetScrollOnNewItem(DependencyObject obj){返回(布尔)obj.GetValue(ScrollOnNewItemProperty);}公共静态无效 SetScrollOnNewItem(DependencyObject obj, bool 值){obj.SetValue(ScrollOnNewItemProperty, value);}公共静态只读 DependencyProperty ScrollOnNewItemProperty =DependencyProperty.RegisterAttached("ScrollOnNewItem",类型(布尔),typeof(ListBoxBehavior),新 UIPropertyMetadata(false, OnScrollOnNewItemChanged));public static void OnScrollOnNewItemChanged(依赖对象 d,DependencyPropertyChangedEventArgs e){var listBox = d as ListBox;如果(listBox == null)返回;bool oldValue = (bool)e.OldValue, newValue = (bool)e.NewValue;if (newValue == oldValue) 返回;如果(新值){listBox.Loaded += ListBox_Loaded;listBox.Unloaded += ListBox_Unloaded;var itemsSourcePropertyDescriptor = TypeDescriptor.GetProperties(listBox)["ItemsSource"];itemsSourcePropertyDescriptor.AddValueChanged(listBox, ListBox_ItemsSourceChanged);}别的{listBox.Loaded -= ListBox_Loaded;listBox.Unloaded -= ListBox_Unloaded;if (Associations.ContainsKey(listBox))关联[listBox].Dispose();var itemsSourcePropertyDescriptor = TypeDescriptor.GetProperties(listBox)["ItemsSource"];itemsSourcePropertyDescriptor.RemoveValueChanged(listBox, ListBox_ItemsSourceChanged);}}private static void ListBox_ItemsSourceChanged(object sender, EventArgs e){var listBox = (ListBox)sender;if (Associations.ContainsKey(listBox))关联[listBox].Dispose();关联[listBox] = new Capture(listBox);}静态无效 ListBox_Unloaded(对象发送者,RoutedEventArgs e){var listBox = (ListBox)sender;if (Associations.ContainsKey(listBox))关联[listBox].Dispose();listBox.Unloaded -= ListBox_Unloaded;}静态无效 ListBox_Loaded(对象发送者,RoutedEventArgs e){var listBox = (ListBox)sender;var incc = listBox.Items as INotifyCollectionChanged;if (incc == null) 返回;listBox.Loaded -= ListBox_Loaded;关联[listBox] = new Capture(listBox);}类捕获:IDisposable{私有只读列表框列表框;私有只读 INotifyCollectionChanged incc;公共捕获(列表框列表框){this.listBox = 列表框;incc = listBox.ItemsSource as INotifyCollectionChanged;if (incc != null){incc.CollectionChanged += incc_CollectionChanged;}}void incc_CollectionChanged(对象发送者,NotifyCollectionChangedEventArgs e){if (e.Action == NotifyCollectionChangedAction.Add){listBox.ScrollIntoView(e.NewItems[0]);listBox.SelectedItem = e.NewItems[0];}}公共无效处置(){if (incc != null)incc.CollectionChanged -= incc_CollectionChanged;}}}

用法:

UPDATE 根据 Andrej 在下面评论中的建议,我添加了钩子来检测 ListBoxItemsSource 中的更改.

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 the ListBox to scroll to the right so that the new item is viewable.

The ListBox is defined in a DataTemplate, so I am unable to access the ListBox by name in my code behind file.

How can I get a ListBox to always scroll to show a latest added item?

I would like a way to know when the ListBox has a new item added to it, but I do not see an event that does this.

解决方案

You can extend the behavior of the ListBox by using attached properties. In your case I would define an attached property called ScrollOnNewItem that when set to true hooks into the INotifyCollectionChanged events of the list box items source and upon detecting a new item, scrolls the list box to it.

Example:

class ListBoxBehavior
{
    static readonly Dictionary<ListBox, Capture> Associations =
           new Dictionary<ListBox, Capture>();

    public static bool GetScrollOnNewItem(DependencyObject obj)
    {
        return (bool)obj.GetValue(ScrollOnNewItemProperty);
    }

    public static void SetScrollOnNewItem(DependencyObject obj, bool value)
    {
        obj.SetValue(ScrollOnNewItemProperty, value);
    }

    public static readonly DependencyProperty ScrollOnNewItemProperty =
        DependencyProperty.RegisterAttached(
            "ScrollOnNewItem",
            typeof(bool),
            typeof(ListBoxBehavior),
            new UIPropertyMetadata(false, OnScrollOnNewItemChanged));

    public static void OnScrollOnNewItemChanged(
        DependencyObject d,
        DependencyPropertyChangedEventArgs e)
    {
        var listBox = d as ListBox;
        if (listBox == null) return;
        bool oldValue = (bool)e.OldValue, newValue = (bool)e.NewValue;
        if (newValue == oldValue) return;
        if (newValue)
        {
            listBox.Loaded += ListBox_Loaded;
            listBox.Unloaded += ListBox_Unloaded;
            var itemsSourcePropertyDescriptor = TypeDescriptor.GetProperties(listBox)["ItemsSource"];
            itemsSourcePropertyDescriptor.AddValueChanged(listBox, ListBox_ItemsSourceChanged);
        }
        else
        {
            listBox.Loaded -= ListBox_Loaded;
            listBox.Unloaded -= ListBox_Unloaded;
            if (Associations.ContainsKey(listBox))
                Associations[listBox].Dispose();
            var itemsSourcePropertyDescriptor = TypeDescriptor.GetProperties(listBox)["ItemsSource"];
            itemsSourcePropertyDescriptor.RemoveValueChanged(listBox, ListBox_ItemsSourceChanged);
        }
    }

    private static void ListBox_ItemsSourceChanged(object sender, EventArgs e)
    {
        var listBox = (ListBox)sender;
        if (Associations.ContainsKey(listBox))
            Associations[listBox].Dispose();
        Associations[listBox] = new Capture(listBox);
    }

    static void ListBox_Unloaded(object sender, RoutedEventArgs e)
    {
        var listBox = (ListBox)sender;
        if (Associations.ContainsKey(listBox))
            Associations[listBox].Dispose();
        listBox.Unloaded -= ListBox_Unloaded;
    }

    static void ListBox_Loaded(object sender, RoutedEventArgs e)
    {
        var listBox = (ListBox)sender;
        var incc = listBox.Items as INotifyCollectionChanged;
        if (incc == null) return;
        listBox.Loaded -= ListBox_Loaded;
        Associations[listBox] = new Capture(listBox);
    }

    class Capture : IDisposable
    {
        private readonly ListBox listBox;
        private readonly INotifyCollectionChanged incc;

        public Capture(ListBox listBox)
        {
            this.listBox = listBox;
            incc = listBox.ItemsSource as INotifyCollectionChanged;
            if (incc != null)
            {
                incc.CollectionChanged += incc_CollectionChanged;
            }
        }

        void incc_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            if (e.Action == NotifyCollectionChangedAction.Add)
            {
                listBox.ScrollIntoView(e.NewItems[0]);
                listBox.SelectedItem = e.NewItems[0];
            }
        }

        public void Dispose()
        {
            if (incc != null)
                incc.CollectionChanged -= incc_CollectionChanged;
        }
    }
}

Usage:

<ListBox ItemsSource="{Binding SourceCollection}" 
         lb:ListBoxBehavior.ScrollOnNewItem="true"/>

UPDATE As per Andrej's suggestion in the comments below, I added hooks to detect a change in the ItemsSource of the ListBox.

这篇关于添加新项目时,如何让 ListBox 自动滚动?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆