好吧,接受答案中的 CodeProject 示例很糟糕。
所以,
还有更多!
但是哪里?
等等,这里有一个。我已经实现了一种更简单、经过测试并且最重要的是可以使用的ListView
.
好吧,它并不是真正可绑定的,但它支持任何实现INotifyCollectionChanged
其底层类型为 implementation 的泛型类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();
}
}
}
关于使用这个类,你只需要知道两件事。
Out of the box 之间的主要区别在于ListView
名为Collection
type的新属性INotifyCollectionChanged
。您仍然可以操纵Items
集合,但我不建议这样做。预计您作为数据源提供的对象正在实现IEnumerable
接口,并且其底层类型正在实现INotifyPropertyChanged
。我没有将Collection
属性限制到这些接口的原因是我想在分配属性时避免额外的强制转换。您始终可以为这些接口添加额外的检查并抛出 ArgumentException 以避免任何意外行为。
还有一个称为附加属性的属性,它设置表示未指定DefaultBrowsableState
的数据源对象属性的列的默认可见性。BrowsableAttribute
原因是当您将它ListView
与另一个使用BrowsableAttribute
(例如PropertyGrid
)的控件一起使用时,您希望隐藏列表中的某些属性,同时保留它们在另一个控件上的可见性。然后,您可以将 设置DefaultBrowsableState
为 false 并将[Browsable(true)]
属性添加到您希望在列表中看到的所有属性。