Сделать CustomCollection разрешающим EditItem (Custom CollectionView?)

Я пишу пользовательскую наблюдаемую коллекцию для некоторых своих нужд и хочу, чтобы она разрешала 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 с нуля. Но прежде чем сделать это, я хотел бы знать:

  1. Если у кого-нибудь есть идея получше?
  2. Если это возможно?

Может быть, некоторые ограничения словаря сделали это невозможным? Какие интерфейсы мне следует реализовать? (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
}

person fharreau    schedule 15.09.2017    source источник


Ответы (1)


Вы можете создать ObservableCollection, который реализует как словарь, так и список, дважды сохраняя вставленные данные во внутренний список и словарь. Словарь обеспечивает преимущества словаря, а список обеспечивает лучшую поддержку операций связывания.

Недостатки включают удвоение использования памяти и увеличение времени удаления предметов*.

* Вставки** и обращения имеют ту же эффективность, что и обычный словарь, но удаление также должно найти элемент во внутреннем списке

** Я предполагаю, что вставки также могут быть более дорогостоящими в тех случаях, когда они требуют изменения размеров массива.

person Dan    schedule 15.09.2017
comment
Я тоже думал об этом решении, но не могу правильно реализовать методы int Add(object value) и void Insert(int index, object value), потому что не знаю, какой ключ связать со значением... - person fharreau; 18.09.2017