问题描述
我知道有很多问题(,,,等),但我找不到解释该错误的方法此错误的原因,适合我的情况。让我知道是否想念一个!
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!
首先,我使用自定义绑定到DataGrid ItemsSource
类(不是 ObservableCollection
或任何其他.NET内置可观察的集合)。在向您展示其代码之前,让我解释一下我的想法(我的假设可能是错误的)。
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).
在我看来,要具有可绑定性,集合必须至少实现 IEnumerable
和 INotifyCollectionChanged
。 IEnumerable可以使视图获取要显示的项目(由于 GetEnumerator
方法),而INotifyCollectionChanged则可以使视图了解集合中的更改。
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.
所以我结束了此类:
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
}
隐式实现 IEnumerable< TValue> .GetEnumerator
并显式地实现其他 GetEnumerator
方法( IDictionary
和 IEnumerable
),以便在我的视图中仅显示字典的值,并围绕<$的调用映射添加/删除方法c $ c> CollectionChanged 事件。
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.
我的ViewModel定义如下:
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:
何时我在代码中添加了一些断点,但从未达到 ObservableDictionary
索引器设置器或 Foo.Value
设置器。
When I put some breakpoints in my code, I never reach the ObservableDictionary
indexor setter nor Foo.Value
setter.
我对视图如何从绑定集合中获取项目的想法是否正确?为什么会出现此错误和/或如何授权我的视图为 EditItem
?
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
?
推荐答案
您的源集合类型( ObservableDictionary< TKey,TValue>
)应实现 IList
界面,如果您希望能够在 DataGrid
中编辑数据。
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
.
无论何时绑定到某些在WPF中使用collection属性,您总是绑定到自动生成的视图,而不是实际的源集合本身。
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.
运行时为您创建的视图类型取决于源集合的类型,您的源集合必须实现非通用的 IList
界面,用于 DataGrid
控件的内部编辑功能。
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.
这篇关于此视图不允许使用WPF'EditItem'的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!