さて、受け入れられた答えからのCodeProjectの例はひどいです。
それで、
そこにはもっとたくさんあります!
しかしここで?
待って、これが1つです。私は、より単純でテスト済みの方法を実装しました。最も重要なのは、の拡張機能をすぐに使用できるようにすること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();
}
}
}
このクラスの使用について知っておくべきことは2つだけです。
Out of the boxの主な違いは、タイプがListView
呼ばれる新しいプロパティです。コレクションを操作することはできますが、お勧めしません。データソースとして提供しているオブジェクトはインターフェイスを実装しており、その基になるタイプもを実装していることが期待されます。プロパティをこれらのインターフェイスに制限しなかった理由は、プロパティを評価するときに追加のキャストを避けたかったためです。予期しない動作を回避するために、これらのインターフェイスのチェックをいつでも追加して、ArgumentExceptionをスローできます。Collection
INotifyCollectionChanged
Items
IEnumerable
INotifyPropertyChanged
Collection
DefaultBrowsableState
指定されていないデータソースオブジェクトのプロパティを表す列のデフォルトの可視性を設定するという追加のプロパティがありBrowsableAttribute
ます。この理由は、 (などの)ListView
を使用する別のコントロールと一緒にこれを使用し、他のコントロールでの可視性を維持しながらリスト内の一部のプロパティを非表示にする場合です。次に、をfalseに設定し、リストに表示するすべてのプロパティに属性を追加できます。BrowsableAttribute
PropertyGrid
DefaultBrowsableState
[Browsable(true)]