【发布时间】:2020-05-08 16:09:48
【问题描述】:
为了将字典绑定到listbox,我使用了observable dictionary,它已由某人实现。
当我在 InitializeComponent() 之前初始化 ObservableDictionary 时,Dic[0] = "three"; 正在抛出 System.ArgumentOutOfRangeException。
我尝试调试但没有成功的这个实现有问题。 有人可以指导我或指出这个实现有什么问题吗?
将OnCollectionChanged(NotifyCollectionChangedAction.Replace, new KeyValuePair<TKey, TValue>(key, value), new KeyValuePair<TKey, TValue>(key, item));替换为OnCollectionChanged();(在ObservableDictionary之前InitializeComponent()初始化的情况下)
似乎解决了异常问题。这是真正的解决方案吗?
TestWindow.xaml
<Window x:Class="Wpf.TestWindow" x:Name="win"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
DataContext="{Binding ElementName=win}"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" Title="TestWindow" Height="450" Width="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<StackPanel Orientation="Vertical">
<ListBox MinHeight="100" MinWidth="100"
DataContext="{Binding ElementName=win}"
ItemsSource="{Binding Path=Dic}"
SelectedValuePath="Key"
DisplayMemberPath="Value"/>
<Button Content="modify" MinWidth="100" Click="Button_Click"/>
</StackPanel>
</Grid>
TestWindow.cs
using System.Collections.ObjectModel;
using System.Windows;
namespace Wpf
{
public partial class TestWindow : Window, INotifyPropertyChanged
{
private ObservableDictionary<int, string> dic;
public ObservableDictionary<int, string> Dic {
get => dic;
set
{
dic = value;
PropertyChanged(this, new PropertyChangedEventArgs(nameof(Dic)));
}
}
public TestWindow()
{
InitializeComponent();
Dic = new ObservableDictionary<int, string>() { {0,"Zero" }, {1, "one" }, };
Dic.Add(2, "two");
}
private void Button_Click(object sender, RoutedEventArgs e)
{
Dic[0] = "three";
Dic.Remove(1);
}
}
}
ObservableDictionary.cs
using System.Linq;
using System.ComponentModel;
using System.Collections.Generic;
using System.Collections.Specialized;
namespace System.Collections.ObjectModel
{
public class ObservableDictionary<TKey, TValue> : IDictionary<TKey, TValue>, INotifyCollectionChanged, INotifyPropertyChanged
{
private const string CountString = "Count";
private const string IndexerName = "Item[]";
private const string KeysName = "Keys";
private const string ValuesName = "Values";
private IDictionary<TKey, TValue> _Dictionary;
protected IDictionary<TKey, TValue> Dictionary
{
get { return _Dictionary; }
}
#region Constructors
public ObservableDictionary()
{
_Dictionary = new Dictionary<TKey, TValue>();
}
public ObservableDictionary(IDictionary<TKey, TValue> dictionary)
{
_Dictionary = new Dictionary<TKey, TValue>(dictionary);
}
public ObservableDictionary(IEqualityComparer<TKey> comparer)
{
_Dictionary = new Dictionary<TKey, TValue>(comparer);
}
public ObservableDictionary(int capacity)
{
_Dictionary = new Dictionary<TKey, TValue>(capacity);
}
public ObservableDictionary(IDictionary<TKey, TValue> dictionary, IEqualityComparer<TKey> comparer)
{
_Dictionary = new Dictionary<TKey, TValue>(dictionary, comparer);
}
public ObservableDictionary(int capacity, IEqualityComparer<TKey> comparer)
{
_Dictionary = new Dictionary<TKey, TValue>(capacity, comparer);
}
#endregion
#region IDictionary<TKey,TValue> Members
public void Add(TKey key, TValue value)
{
Insert(key, value, true);
}
public bool ContainsKey(TKey key)
{
return Dictionary.ContainsKey(key);
}
public ICollection<TKey> Keys
{
get { return Dictionary.Keys; }
}
public bool Remove(TKey key)
{
if (key == null) throw new ArgumentNullException("key");
TValue value;
Dictionary.TryGetValue(key, out value);
var removed = Dictionary.Remove(key);
if (removed)
//OnCollectionChanged(NotifyCollectionChangedAction.Remove, new KeyValuePair<TKey, TValue>(key, value));
OnCollectionChanged();
return removed;
}
public bool TryGetValue(TKey key, out TValue value)
{
return Dictionary.TryGetValue(key, out value);
}
public ICollection<TValue> Values
{
get { return Dictionary.Values; }
}
public TValue this[TKey key]
{
get
{
return Dictionary[key];
}
set
{
Insert(key, value, false);
}
}
#endregion
#region ICollection<KeyValuePair<TKey,TValue>> Members
public void Add(KeyValuePair<TKey, TValue> item)
{
Insert(item.Key, item.Value, true);
}
public void Clear()
{
if (Dictionary.Count > 0)
{
Dictionary.Clear();
OnCollectionChanged();
}
}
public bool Contains(KeyValuePair<TKey, TValue> item)
{
return Dictionary.Contains(item);
}
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
{
Dictionary.CopyTo(array, arrayIndex);
}
public int Count
{
get { return Dictionary.Count; }
}
public bool IsReadOnly
{
get { return Dictionary.IsReadOnly; }
}
public bool Remove(KeyValuePair<TKey, TValue> item)
{
return Remove(item.Key);
}
#endregion
#region IEnumerable<KeyValuePair<TKey,TValue>> Members
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
{
return Dictionary.GetEnumerator();
}
#endregion
#region IEnumerable Members
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable)Dictionary).GetEnumerator();
}
#endregion
#region INotifyCollectionChanged Members
public event NotifyCollectionChangedEventHandler CollectionChanged;
#endregion
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
public void AddRange(IDictionary<TKey, TValue> items)
{
if (items == null) throw new ArgumentNullException("items");
if (items.Count > 0)
{
if (Dictionary.Count > 0)
{
if (items.Keys.Any((k) => Dictionary.ContainsKey(k)))
throw new ArgumentException("An item with the same key has already been added.");
else
foreach (var item in items) Dictionary.Add(item);
}
else
_Dictionary = new Dictionary<TKey, TValue>(items);
OnCollectionChanged(NotifyCollectionChangedAction.Add, items.ToArray());
}
}
private void Insert(TKey key, TValue value, bool add)
{
if (key == null) throw new ArgumentNullException("key");
TValue item;
if (Dictionary.TryGetValue(key, out item))
{
if (add) throw new ArgumentException("An item with the same key has already been added.");
if (Equals(item, value)) return;
Dictionary[key] = value;
//OnCollectionChanged();
OnCollectionChanged(NotifyCollectionChangedAction.Replace, new KeyValuePair<TKey, TValue>(key, value), new KeyValuePair<TKey, TValue>(key, item));
}
else
{
Dictionary[key] = value;
OnCollectionChanged(NotifyCollectionChangedAction.Add, new KeyValuePair<TKey, TValue>(key, value));
}
}
private void OnPropertyChanged()
{
OnPropertyChanged(CountString);
OnPropertyChanged(IndexerName);
OnPropertyChanged(KeysName);
OnPropertyChanged(ValuesName);
}
protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
private void OnCollectionChanged()
{
OnPropertyChanged();
if (CollectionChanged != null) CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
private void OnCollectionChanged(NotifyCollectionChangedAction action, KeyValuePair<TKey, TValue> changedItem)
{
OnPropertyChanged();
if (CollectionChanged != null) CollectionChanged(this, new NotifyCollectionChangedEventArgs(action, changedItem));
}
private void OnCollectionChanged(NotifyCollectionChangedAction action, KeyValuePair<TKey, TValue> newItem, KeyValuePair<TKey, TValue> oldItem)
{
OnPropertyChanged();
if (CollectionChanged != null) CollectionChanged(this, new NotifyCollectionChangedEventArgs(action, newItem, oldItem));
}
private void OnCollectionChanged(NotifyCollectionChangedAction action, IList newItems)
{
OnPropertyChanged();
if (CollectionChanged != null) CollectionChanged(this, new NotifyCollectionChangedEventArgs(action, newItems));
}
}
}
【问题讨论】:
-
我自己还没有特权,但是应该删除这个问题上的重复标志。这个问题不仅仅是询问
ArgumentOutOfRangeException是什么以及如何处理它。这个问题揭示了一个特定问题,即集合类型类的实现(似乎应该可以正常工作)在其周围的其他代码中导致异常。任何看到这个有特权的人都应该投票删除标签。 -
我不确定这是对版主标志的正确使用——但它可能是。正常的做法是对其进行投票,但我们都没有特权。您可以尝试稍微编辑问题以显示这两个问题之间的差异,看看是否能让人们投票。如果不是,并且您仍然足够关心,那么标记一个 mod 可能是唯一的其他选择 - 但请记住,给定的 mod 在这个特定领域可能不够了解,无法判断这一点。
-
@Keith: “这个问题不仅仅是询问 ArgumentOutOfRangeException 是什么以及如何处理一个”——问题的当前呈现方式,它正是如此。如果显示的
ObservableDictionary类产生了不正确的结果,并且问题的作者正在寻求帮助来避免这些结果,那么他们应该询问这个问题,而不是ArgumentOutOfRangeException。如果他们不寻求修复字典,那么他们非常想知道如何避免异常,这就是标记重复的含义。 -
话虽如此,使用
INotifyCollectionChanged和IDictionary的整个概念存在根本缺陷。 .NET 中的基本字典接口是unordered,而通知接口采用ordered 集合。因此在事件参数中使用索引来描述事件。如果实现者要偏离此规范,则由他们完全记录其预期用途,并让客户端代码遵循该文档。但鉴于INotifyCollectionChanged的许多客户可能没有该选项,恕我直言,这首先是个坏主意。
标签: c# wpf xaml dictionary observable