Я пишу пользовательскую наблюдаемую коллекцию для некоторых своих нужд и хочу, чтобы она разрешала EditItem
при использовании в WPF DataGrid
.
(см. код сборника в конце вопроса, чтобы не загрязнять чтение вопроса)
Вы увидите, что я придумал 3 решения. Но я не могу заставить ни один из них работать на данный момент.
Решение 1
Согласно этому вопросу, если я хочу, чтобы мой ObservableDictionary
разрешал EditItem
, он должен реализовать неуниверсальный IList
. Но поскольку я использую IDictionary
в качестве резервного поля для хранения элементов, невозможно правильно реализовать интерфейс IList
(поскольку он основан на упорядоченном индексе).
Пока кто-нибудь не найдет способ?
Решение 2
Следующая идея: вместо того, чтобы позволить системе выбрать реализацию CollectionView, я могу заставить ее использовать свою собственную, например:
<CollectionViewSource
x:Key="FooesSource"
Source="{Binding Fooes}"
CollectionViewType="local:MyCollectionView" />
Для этого я попытался переопределить один существующий CollectionView, который позволяет использовать EditItem
, в соответствии со своими потребностями. Например:
class ObservableDictionaryCollectionView : ListCollectionView
{
public ObservableDictionaryCollectionView(IDictionary dictionary)
: base(dictionary.Values)
{
}
}
Но это не работает, потому что dictionary.Values
является ICollection
, а ICollection
не реализует IList
(наоборот -_-).
Есть ли другой встроенный CollectionView, который мог бы удовлетворить мои потребности?
Решение 3
Основываясь на следующей идее, я мог бы попробовать написать свой собственный CollectionView с нуля. Но прежде чем сделать это, я хотел бы знать:
- Если у кого-нибудь есть идея получше?
- Если это возможно?
Может быть, некоторые ограничения словаря сделали это невозможным? Какие интерфейсы мне следует реализовать? (IEditableCollectionView
, IEditableCollectionViewAddNewItem
, IEditableCollectionView
, ICollectionViewLiveShaping
и т. д.)
Как и обещал, вот код коллекции:
public class ObservableDictionary<TKey, TValue> : IDictionary<TKey, TValue>, IEnumerable<TValue>, INotifyCollectionChanged
{
#region fields
private IDictionary<TKey, TValue> _innerDictionary;
#endregion
#region properties
public int Count { get { return _innerDictionary.Count; } }
public ICollection<TKey> Keys { get { return _innerDictionary.Keys; } }
public ICollection<TValue> Values { get { return _innerDictionary.Values; } }
public bool IsReadOnly { get { return false; } }
#endregion
#region indexors
public TValue this[TKey key]
{
get { return _innerDictionary[key]; }
set { this.InternalAdd(new KeyValuePair<TKey, TValue>(key, value)); }
}
#endregion
#region events
public event NotifyCollectionChangedEventHandler CollectionChanged;
#endregion
#region constructors
public ObservableDictionary()
{
_innerDictionary = new Dictionary<TKey, TValue>();
}
public ObservableDictionary(int capacity)
{
_innerDictionary = new Dictionary<TKey, TValue>(capacity);
}
public ObservableDictionary(IEqualityComparer<TKey> comparer)
{
_innerDictionary = new Dictionary<TKey, TValue>(comparer);
}
public ObservableDictionary(IDictionary<TKey, TValue> dictionary)
{
_innerDictionary = new Dictionary<TKey, TValue>(dictionary);
}
public ObservableDictionary(int capacity, IEqualityComparer<TKey> comparer)
{
_innerDictionary = new Dictionary<TKey, TValue>(capacity, comparer);
}
public ObservableDictionary(IDictionary<TKey, TValue> dictionary, IEqualityComparer<TKey> comparer)
{
_innerDictionary = new Dictionary<TKey, TValue>(dictionary, comparer);
}
#endregion
#region public methods
public bool ContainsKey(TKey key)
{
return _innerDictionary.ContainsKey(key);
}
public bool Contains(KeyValuePair<TKey, TValue> item)
{
return _innerDictionary.Contains(item);
}
public void Add(TKey key, TValue value)
{
this.InternalAdd(new KeyValuePair<TKey, TValue>(key, value));
}
public void AddRange(IEnumerable<KeyValuePair<TKey, TValue>> items)
{
if (!items.Any())
{
return;
}
var added = new List<TValue>();
var removed = new List<TValue>();
foreach (var item in items)
{
TValue value;
if (_innerDictionary.TryGetValue(item.Key, out value))
{
removed.Add(value);
}
added.Add(item.Value);
_innerDictionary[item.Key] = item.Value;
}
this.CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, added, null));
if (removed.Count > 0)
{
this.CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, null, removed));
}
}
public void Add(KeyValuePair<TKey, TValue> item)
{
this.InternalAdd(item);
}
public bool TryGetValue(TKey key, out TValue value)
{
return _innerDictionary.TryGetValue(key, out value);
}
public bool Remove(TKey key)
{
return this.InternalRemove(key);
}
public bool Remove(KeyValuePair<TKey, TValue> item)
{
return this.InternalRemove(item.Key);
}
public void Clear()
{
_innerDictionary.Clear();
this.CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
{
_innerDictionary.CopyTo(array, arrayIndex);
}
public IEnumerator<TValue> GetEnumerator()
{
return Values.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
IEnumerator<KeyValuePair<TKey, TValue>> IEnumerable<KeyValuePair<TKey, TValue>>.GetEnumerator()
{
return _innerDictionary.GetEnumerator();
}
#endregion
#region private methods
/// <summary>
/// Adds the specified value to the internal dictionary and indicates whether the element has actually been added. Fires the CollectionChanged event accordingly.
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
private void InternalAdd(KeyValuePair<TKey, TValue> item)
{
IList added = new TValue[] { item.Value };
TValue value;
if (_innerDictionary.TryGetValue(item.Key, out value))
{
this.CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, null, new TValue[] { value }));
}
_innerDictionary[item.Key] = item.Value;
this.CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, added, null));
}
/// <summary>
/// Remove the specified key from the internal dictionary and indicates whether the element has actually been removed. Fires the CollectionChanged event accordingly.
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
private bool InternalRemove(TKey key)
{
TValue value;
if (_innerDictionary.TryGetValue(key, out value))
{
_innerDictionary.Remove(key);
this.CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, null, new TValue[] { value }));
}
return value != null;
}
#endregion
}