Search code examples
c#wpfdatagrid

WPF 'EditItem' is not allowed for this view


I know there are a lots of questions (1, 2, 3, 4, 5, etc) about that error, but I can't find one that explains the cause of this error and suitable for my case. Let me know if I miss one!

First of all, I am binding to my DataGrid ItemsSource with a custom class (not ObservableCollection or any other .NET built-in observable collection). Before showing you its code, let me explain how I thought of it (my assumptions are may be be wrong).

In my mind, to be bindable, a collection must implements at least IEnumerable and INotifyCollectionChanged. IEnumerable in order to the view to get the items to display (thanks to the GetEnumerator method) and INotifyCollectionChanged in order to the view to know the changes on the collection.

So I end up with this class:

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
}

It implements implicitly the IEnumerable<TValue>.GetEnumerator and explicitly the others GetEnumerator methods (IDictionary and IEnumerable) in order to my view display only the values of my dictionary, and I map the add/remove methods around the invocation of the CollectionChanged event.

My ViewModel is defined like this:

class MyViewModel
{
    public ObservableDictionary<string, Foo> Foos { get; private set; }

    public MyViewModel()
    {
        this.Foos = new ObservableDictionary<string, Foo>();
    }
}

And bind it to my view like this:

<DataGrid ItemsSource="{Binding Facts}" AutoGenerateColumns="False" CanUserAddRows="False" CanUserDeleteRows="False">
    <DataGrid.Columns>
        <DataGridTextColumn Header="Name" Binding="{Binding Name}" IsReadOnly="True" Width="*" />
        <DataGridTextColumn Header="Type" Binding="{Binding Type}" IsReadOnly="True" Width="*" />
        <DataGridTextColumn Header="Value" Binding="{Binding Value}" IsReadOnly="False" Width="*" />
    </DataGrid.Columns>
</DataGrid>

Then, when I try to edit the Value, I get the specified error:

'EditItem' is not allowed for this view

When I put some breakpoints in my code, I never reach the ObservableDictionary indexor setter nor Foo.Value setter.

Are my thoughts about how the view gets the item from the binded collection corrects? Why am I getting this error and/or how can I authorize my view to EditItem?


Solution

  • Your source collection type (ObservableDictionary<TKey, TValue>) should implement the IList interface if you want to be able to edit the data in a DataGrid.

    Whenever you bind to some collection property in WPF, you are always binding to an automatically generated view and not to the actual source collection itself.

    The type of view that is created for you by the runtime depends on the type of the source collection and your source collection must implement the non-generic IList interface for the internal editing functionality of the DataGrid control to work.