【问题标题】:Is it possible to use a BindingList<T> with the ListView control in a similar way to the ListBox control?是否可以以与 ListBox 控件类似的方式将 BindingList<T> 与 ListView 控件一起使用?
【发布时间】:2011-01-20 16:24:26
【问题描述】:
一个简单的例子:
BindingList<Dog> dogs = kennel.Dogs;
// Works great!
listBoxDogs.DataSource = dogs;
// Confuses me.
listViewDogs.? = dogs;
我已经探索了 listViewDogs.DataBindings 属性,但我无法梳理出与我在使用 DataSource 的 listBox 控件中看到的类似的行为。
必须有一种更好的方法来更新 listViewDogs.Items 集合,然后捕获 dog.ListChanged 事件并手动操作 listViewDogs.Items 集合。
我错过了什么?
【问题讨论】:
标签:
c#
listview
controls
listbox
bindinglist
【解决方案2】:
好吧,CodeProject 上接受的答案中的示例很糟糕。
所以,
还有更多!
但是在哪里呢?
等等,这是一个。我实现了一种更简单的方法,经过测试,最重要的是,可以使用 ListView 的扩展。
嗯,它并不是真正可绑定的,但它支持任何实现 INotifyCollectionChanged 的泛型类,其基础类型是实现 INotifyPropertyChanged,例如 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();
}
}
}
关于使用这个类,你应该知道的只有两件事。
开箱即用 ListView 之间的主要区别在于名为 Collection 的类型为 INotifyCollectionChanged 的新属性。您仍然可以操作 Items 集合,但我不建议这样做。预计您作为数据源提供的对象正在实现IEnumerable 接口,并且其底层类型正在实现INotifyPropertyChanged。我没有将Collection 属性限制到这些接口的原因是我想在分配属性时避免额外的强制转换。您始终可以为这些接口添加额外检查并抛出 ArgumentException,以避免任何意外行为。
还有一个名为DefaultBrowsableState 的附加属性,它设置表示未指定BrowsableAttribute 的数据源对象的属性的列的默认可见性。原因是当您使用此ListView 和另一个使用BrowsableAttribute 的控件(例如PropertyGrid)时,您希望隐藏列表中的某些属性,同时保留它们在另一个控件上的可见性。然后,您可以将 DefaultBrowsableState 设置为 false,并将 [Browsable(true)] 属性添加到您希望在列表中看到的所有属性。