是否可以使用 BindingList使用 ListView 控件的方式与 ListBox 控件类似?

发布于 2024-10-13 05:01:32 字数 372 浏览 3 评论 0原文

一个简单的例子:

BindingList<Dog> dogs = kennel.Dogs;

// Works great!
listBoxDogs.DataSource = dogs;

// Confuses me.
listViewDogs.? = dogs;

我已经探索了 listViewDogs.DataBindings 属性,但我无法梳理出与使用 DataSource 在 listBox 控件中看到的类似行为。

必须有一种更好的方法来更新 listViewDogs.Items 集合,然后捕获 dogs.ListChanged 事件并手动操作 listViewDogs.Items 集合。

我缺少什么?

A simple example:

BindingList<Dog> dogs = kennel.Dogs;

// Works great!
listBoxDogs.DataSource = dogs;

// Confuses me.
listViewDogs.? = dogs;

I have explored the listViewDogs.DataBindings property, but I have not been able to tease out of it similar behavior to what I am seeing with the listBox control using DataSource.

There has to be a better method of updating the listViewDogs.Items collection then catching the dogs.ListChanged event and doing the manipulation of the listViewDogs.Items collection by hand.

What am I missing?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

关于从前 2024-10-20 05:01:32

不幸的是,列表视图不支持这种方式的数据绑定。

这是有关如何通过创建新控件来实现它的教程。

http://www.codeproject.com/KB/list/ListView_DataBinding.aspx

附:还有更多!

The list view unfortunately does not support databinding in that way.

here is a tutorial on how to implement it by creating a new control though.

http://www.codeproject.com/KB/list/ListView_DataBinding.aspx

ps. there are many more out there!

2024-10-20 05:01:32

好吧,CodeProject 上接受的答案中的例子很糟糕。

所以,

还有更多!

但在哪里呢?

等等,这里有一个。我实现了一种更简单的方法,经过测试,最重要的是,可以使用 ListView 的扩展。

嗯,它并不是真正可绑定的,但它支持任何实现 INotifyCollectionChanged 且其底层类型正在实现 INotifyPropertyChanged 的泛型类,例如 ObservableCollection <代码>其中 T : INotifyPropertyChanged。

public class BindableListView : ListView
{
    private const string DataCategoryName = "Data";


    private INotifyCollectionChanged _collection;
    [Category(DataCategoryName)]
    public INotifyCollectionChanged Collection
    {
        get { return _collection; }
        set
        {
            if (_collection != null) _collection.CollectionChanged -= CollectionChanged;
            _collection = value;
            BindObject(_collection);
            if (_collection != null) _collection.CollectionChanged += CollectionChanged;
        }
    }

    private const bool DefaultDefaultBrowsableState = false;
    [Category(DataCategoryName)]
    [DefaultValue(DefaultDefaultBrowsableState)]
    public bool DefaultBrowsableState { get; set; } = DefaultDefaultBrowsableState;


    private void BindObject(object obj)
    {
        Clear();
        if (obj != null)
        {
            Columns.AddRange(obj.GetType().GetGenericArguments().FirstOrDefault()?.GetProperties().Where(p =>
            {
                return p.GetCustomAttributes(true).OfType<BrowsableAttribute>().FirstOrDefault()?.Browsable ?? DefaultBrowsableState;
            }).Select(p =>
            {
                return new ColumnHeader()
                {
                    Name = p.Name,
                    Text = p.GetCustomAttributes(true).OfType<DisplayNameAttribute>().FirstOrDefault()?.DisplayName ?? p.Name
                };
            }).ToArray());
            AddItems(obj as System.Collections.IEnumerable);
            AutoResizeColumns(ColumnHeaderAutoResizeStyle.ColumnContent);
            AutoResizeColumns(ColumnHeaderAutoResizeStyle.HeaderSize);
        }
    }

    private void CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        switch (e.Action)
        {
            case NotifyCollectionChangedAction.Add:
                AddItems(e.NewItems);
                break;
            case NotifyCollectionChangedAction.Remove:
                foreach (var oldItem in e.OldItems)
                {
                    if (Items.OfType<ListViewItem>().FirstOrDefault(item => Equals(item.Tag, oldItem)) is ListViewItem itemToRemove)
                    {
                        UnregisterItem(oldItem);
                        Items.Remove(itemToRemove);
                    }
                }
                break;
            case NotifyCollectionChangedAction.Replace:
                if (e.OldItems.Count == e.NewItems.Count)
                {
                    var count = e.OldItems.Count;
                    for (var i = 0; i < count; i++)
                    {
                        var itemPair = new { Old = e.OldItems[i], New = e.NewItems[i] };
                        if (Items.OfType<ListViewItem>().FirstOrDefault(item => Equals(item.Tag, itemPair.Old)) is ListViewItem itemToReplace)
                        {
                            UnregisterItem(itemPair.Old);
                            RegisterItem(itemPair.New);
                            itemToReplace.Tag = itemPair.New;
                            foreach (ColumnHeader column in Columns)
                            {
                                itemToReplace.SubItems[column.Index].Text = itemPair.New.GetType().GetProperty(column.Name).GetValue(itemToReplace)?.ToString();
                            }
                        }
                    }
                }
                break;
            case NotifyCollectionChangedAction.Move:
                foreach (var oldItem in e.OldItems)
                {
                    if (Items.OfType<ListViewItem>().FirstOrDefault(item => Equals(item.Tag, oldItem)) is ListViewItem itemToMove)
                    {
                        Items.Remove(itemToMove);
                        Items.Insert(e.NewStartingIndex, itemToMove);
                    }
                }
                break;
            case NotifyCollectionChangedAction.Reset:
                Items.Clear();
                break;
            default:
                break;
        }
        AutoResizeColumns(ColumnHeaderAutoResizeStyle.ColumnContent);
        AutoResizeColumns(ColumnHeaderAutoResizeStyle.HeaderSize);
    }

    private void AddItems(System.Collections.IEnumerable items)
    {
        Items.AddRange((items ?? Enumerable.Empty<object>()).OfType<object>().Select(item =>
        {
            RegisterItem(item);
            return new ListViewItem(Columns.OfType<ColumnHeader>().Select(column =>
            {
                return item.GetType().GetProperty(column.Name).GetValue(item)?.ToString() ?? "";
            }).ToArray())
            {
                Tag = item
            };
        }).ToArray());
    }

    private void RegisterItem(object item)
    {
        if(item is INotifyPropertyChanged observableItem) observableItem.PropertyChanged += ObservableItem_PropertyChanged;
    }

    private void UnregisterItem(object item)
    {
        if (item is INotifyPropertyChanged observableItem) observableItem.PropertyChanged -= ObservableItem_PropertyChanged;
    }

    private void ObservableItem_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (Items.OfType<ListViewItem>().FirstOrDefault(itm => Equals(itm.Tag, sender)) is ListViewItem item)
        {
            if (Columns[e.PropertyName] is ColumnHeader column)
                item.SubItems[column.Index].Text = sender.GetType().GetProperty(e.PropertyName).GetValue(sender)?.ToString();
        }
    }
}

关于使用此类,您只需要了解两件事。

  1. 开箱即用的 ListView 之间的主要区别是名为 Collection 的新属性,其类型为 INotifyCollectionChanged。您仍然可以操作 Items 集合,但我不建议这样做。预计您作为数据源提供的对象正在实现 IEnumerable 接口,并且其基础类型也正在实现 INotifyPropertyChanged。我没有将 Collection 属性限制到这些接口的原因是我想在分配属性时避免额外的转换。您始终可以为这些接口添加额外的检查并抛出 ArgumentException 以避免任何意外行为。

  2. 还有一个名为 DefaultBrowsableState 的附加属性,它设置表示未指定 BrowsableAttribute 的数据源对象的属性的列的默认可见性。原因是当您将此 ListView 与另一个使用 BrowsableAttribute 的控件(例如 PropertyGrid)一起使用时,并且您想要隐藏一些列表中的属性,同时保留它们在其他控件上的可见性。然后,您可以将 DefaultBrowsableState 设置为 false,并将 [Browsable(true)] 属性添加到您想要在列表中查看的所有属性。

Well, the example on CodeProject from the accepted answer is terrible.

So,

there are many more out there!

But where?

Wait, here's one. I've implemented a way simplier, tested and, what's most important, ready to use extension of the ListView.

Well, it's not really bindable but it supports any generic class that implements INotifyCollectionChanged whose underlying type is implementing INotifyPropertyChanged, such as ObservableCollection<T> where T : INotifyPropertyChanged.

public class BindableListView : ListView
{
    private const string DataCategoryName = "Data";


    private INotifyCollectionChanged _collection;
    [Category(DataCategoryName)]
    public INotifyCollectionChanged Collection
    {
        get { return _collection; }
        set
        {
            if (_collection != null) _collection.CollectionChanged -= CollectionChanged;
            _collection = value;
            BindObject(_collection);
            if (_collection != null) _collection.CollectionChanged += CollectionChanged;
        }
    }

    private const bool DefaultDefaultBrowsableState = false;
    [Category(DataCategoryName)]
    [DefaultValue(DefaultDefaultBrowsableState)]
    public bool DefaultBrowsableState { get; set; } = DefaultDefaultBrowsableState;


    private void BindObject(object obj)
    {
        Clear();
        if (obj != null)
        {
            Columns.AddRange(obj.GetType().GetGenericArguments().FirstOrDefault()?.GetProperties().Where(p =>
            {
                return p.GetCustomAttributes(true).OfType<BrowsableAttribute>().FirstOrDefault()?.Browsable ?? DefaultBrowsableState;
            }).Select(p =>
            {
                return new ColumnHeader()
                {
                    Name = p.Name,
                    Text = p.GetCustomAttributes(true).OfType<DisplayNameAttribute>().FirstOrDefault()?.DisplayName ?? p.Name
                };
            }).ToArray());
            AddItems(obj as System.Collections.IEnumerable);
            AutoResizeColumns(ColumnHeaderAutoResizeStyle.ColumnContent);
            AutoResizeColumns(ColumnHeaderAutoResizeStyle.HeaderSize);
        }
    }

    private void CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        switch (e.Action)
        {
            case NotifyCollectionChangedAction.Add:
                AddItems(e.NewItems);
                break;
            case NotifyCollectionChangedAction.Remove:
                foreach (var oldItem in e.OldItems)
                {
                    if (Items.OfType<ListViewItem>().FirstOrDefault(item => Equals(item.Tag, oldItem)) is ListViewItem itemToRemove)
                    {
                        UnregisterItem(oldItem);
                        Items.Remove(itemToRemove);
                    }
                }
                break;
            case NotifyCollectionChangedAction.Replace:
                if (e.OldItems.Count == e.NewItems.Count)
                {
                    var count = e.OldItems.Count;
                    for (var i = 0; i < count; i++)
                    {
                        var itemPair = new { Old = e.OldItems[i], New = e.NewItems[i] };
                        if (Items.OfType<ListViewItem>().FirstOrDefault(item => Equals(item.Tag, itemPair.Old)) is ListViewItem itemToReplace)
                        {
                            UnregisterItem(itemPair.Old);
                            RegisterItem(itemPair.New);
                            itemToReplace.Tag = itemPair.New;
                            foreach (ColumnHeader column in Columns)
                            {
                                itemToReplace.SubItems[column.Index].Text = itemPair.New.GetType().GetProperty(column.Name).GetValue(itemToReplace)?.ToString();
                            }
                        }
                    }
                }
                break;
            case NotifyCollectionChangedAction.Move:
                foreach (var oldItem in e.OldItems)
                {
                    if (Items.OfType<ListViewItem>().FirstOrDefault(item => Equals(item.Tag, oldItem)) is ListViewItem itemToMove)
                    {
                        Items.Remove(itemToMove);
                        Items.Insert(e.NewStartingIndex, itemToMove);
                    }
                }
                break;
            case NotifyCollectionChangedAction.Reset:
                Items.Clear();
                break;
            default:
                break;
        }
        AutoResizeColumns(ColumnHeaderAutoResizeStyle.ColumnContent);
        AutoResizeColumns(ColumnHeaderAutoResizeStyle.HeaderSize);
    }

    private void AddItems(System.Collections.IEnumerable items)
    {
        Items.AddRange((items ?? Enumerable.Empty<object>()).OfType<object>().Select(item =>
        {
            RegisterItem(item);
            return new ListViewItem(Columns.OfType<ColumnHeader>().Select(column =>
            {
                return item.GetType().GetProperty(column.Name).GetValue(item)?.ToString() ?? "";
            }).ToArray())
            {
                Tag = item
            };
        }).ToArray());
    }

    private void RegisterItem(object item)
    {
        if(item is INotifyPropertyChanged observableItem) observableItem.PropertyChanged += ObservableItem_PropertyChanged;
    }

    private void UnregisterItem(object item)
    {
        if (item is INotifyPropertyChanged observableItem) observableItem.PropertyChanged -= ObservableItem_PropertyChanged;
    }

    private void ObservableItem_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (Items.OfType<ListViewItem>().FirstOrDefault(itm => Equals(itm.Tag, sender)) is ListViewItem item)
        {
            if (Columns[e.PropertyName] is ColumnHeader column)
                item.SubItems[column.Index].Text = sender.GetType().GetProperty(e.PropertyName).GetValue(sender)?.ToString();
        }
    }
}

There are only two things you should know about using this class.

  1. The main difference between Out of the box ListView is the new property called Collection of type INotifyCollectionChanged. You can still manipulate the Items collection but I wouldn't recommend that. It is expected that the object you are providing as the data source is implementing IEnumerable interface and also its underlying type is implementing INotifyPropertyChanged. The reason I didn't constrained the Collection property to these interfaces was that I wanted to avoid additional casting when assinging the property. You can always add additional checking for these interfaces and throw an ArgumentException just to avoid any unexpected behaviors.

  2. There's additional property called DefaultBrowsableState which sets the default visibility of the columns representing properties of the data source object that have no BrowsableAttribute specified. The reason for this is when you using this ListView along with another control which uses the BrowsableAttribute (such as PropertyGrid) and you want to hide some properties in the list while preserve their visibility on the other control. You can then set the DefaultBrowsableState to false and add [Browsable(true)] attribute to all properties you want to see in the list.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文