ArgumentException 与 ListView SelectedItem 的 属性 和 TextBox 绑定
ArgumentException with ListView SelectedItem's property and TextBox binding
编辑:
我发现这个问题的根源是 Equals()
和 GetHashCode()
在 Bar
中的实现。
特别是参与 GetHashCode()
并且还绑定到 TextBox
的属性(如 Bar
中的 Name
)。删除这些覆盖方法后一切正常(除了我想保留它们)
我不明白为什么会这样?
我有一个 TextBox
、一个 ListView
,以及一些与以下 ViewModel 的数据绑定:
[PropertyChanged.ImplementPropertyChanged]
public class ViewModel
{
public ObservableCollection<Foo> Foos { get; set; }
public Foo SelectedFoo { get; set; }
}
[PropertyChanged.ImplementPropertyChanged]
public class Foo
{
public Bar FooBar { get; set; }
}
[PropertyChanged.ImplementPropertyChanged]
public class Bar
{
public string Name { get; set; }
public override bool Equals(object obj)
{
var other = obj as Bar;
if (other != null)
{
return other.Name == Name;
}
return false;
}
public override int GetHashCode()
{
return Name.GetHashCode();
}
}
这是我的清单:
<ListView x:Name="V_List" SelectedItem="{Binding SelectedFoo}" ItemsSource="{Binding Path=Foos}" SelectionMode="Single">
...
</ListView>
这是我的文本框:
<TextBox Text="{Binding Path=SelectedFoo.FooBar.Name, Mode=TwoWay, UpdateSourceTrigger=LostFocus}" />
事情是这样的:
当我 select 来自 List
的第一个 Foo
时,绑定有效,而 FooBar
属性 的 Name
selected Foo
出现在TextBox
。无论我更改 selection 多少次,适当的值都会出现在 TextBox
.
中
但是现在,如果我使用 TextBox
更改 Name
(由于 TwoWay 数据绑定,在失去焦点后,它正在工作,我通过调试进行了检查)然后更改我的 selection 从列表中,TextBox
仍然显示先前 selected 项目的值。
此外,再次 selecting 相同的项目,然后 selecting 其他一些项目,我得到以下异常(令人惊讶的是,调试器没有报告,我不得不将其记录到一个文件中。可能是因为我的代码没有引发异常。)
这是日志:
The Exception is:-Exception :: System.ArgumentException: An item with
the same key has already been added. at
System.ThrowHelper.ThrowArgumentException(ExceptionResource resource)
at System.Collections.Generic.Dictionary2.Insert(TKey key, TValue
value, Boolean add) at
System.Collections.Generic.Dictionary
2..ctor(IDictionary2
dictionary, IEqualityComparer
1 comparer) at
System.Windows.Controls.Primitives.Selector.InternalSelectedItemsStorage..ctor(InternalSelectedItemsStorage
collection, IEqualityComparer`1 equalityComparer) at
System.Windows.Controls.Primitives.Selector.SelectionChanger.ApplyCanSelectMultiple()
at System.Windows.Controls.Primitives.Selector.SelectionChanger.End()
at
System.Windows.Controls.Primitives.Selector.SetSelectedHelper(Object
item, FrameworkElement UI, Boolean selected) at
System.Windows.Controls.Primitives.Selector.NotifyIsSelectedChanged(FrameworkElement
container, Boolean selected, RoutedEventArgs e) at
System.Windows.Controls.Primitives.Selector.OnSelected(Object sender,
RoutedEventArgs e) at
System.Windows.RoutedEventHandlerInfo.InvokeHandler(Object target,
RoutedEventArgs routedEventArgs) at
System.Windows.EventRoute.InvokeHandlersImpl(Object source,
RoutedEventArgs args, Boolean reRaised) at
System.Windows.UIElement.RaiseEventImpl(DependencyObject sender,
RoutedEventArgs args) at
System.Windows.UIElement.RaiseEvent(RoutedEventArgs e) at
System.Windows.Controls.ListBoxItem.OnSelected(RoutedEventArgs e)
at
System.Windows.Controls.ListBoxItem.OnIsSelectedChanged(DependencyObject
d, DependencyPropertyChangedEventArgs e) at
System.Windows.DependencyObject.OnPropertyChanged(DependencyPropertyChangedEventArgs
e) at
System.Windows.FrameworkElement.OnPropertyChanged(DependencyPropertyChangedEventArgs
e) at
System.Windows.DependencyObject.NotifyPropertyChange(DependencyPropertyChangedEventArgs
args) at
System.Windows.DependencyObject.UpdateEffectiveValue(EntryIndex
entryIndex, DependencyProperty dp, PropertyMetadata metadata,
EffectiveValueEntry oldEntry, EffectiveValueEntry& newEntry, Boolean
coerceWithDeferredReference, Boolean coerceWithCurrentValue,
OperationType operationType) at
System.Windows.DependencyObject.SetValueCommon(DependencyProperty dp,
Object value, PropertyMetadata metadata, Boolean
coerceWithDeferredReference, Boolean coerceWithCurrentValue,
OperationType operationType, Boolean isInternal) at
System.Windows.DependencyObject.SetCurrentValueInternal(DependencyProperty
dp, Object value) at
System.Windows.Controls.ListBox.NotifyListItemClicked(ListBoxItem
item, MouseButton mouseButton) at
System.Windows.Controls.ListBoxItem.HandleMouseButtonDown(MouseButton
mouseButton) at
System.Windows.Controls.ListBoxItem.OnMouseLeftButtonDown(MouseButtonEventArgs
e) at System.Windows.UIElement.OnMouseLeftButtonDownThunk(Object
sender, MouseButtonEventArgs e) at
System.Windows.Input.MouseButtonEventArgs.InvokeEventHandler(Delegate
genericHandler, Object genericTarget) at
System.Windows.RoutedEventArgs.InvokeHandler(Delegate handler, Object
target) at
System.Windows.RoutedEventHandlerInfo.InvokeHandler(Object target,
RoutedEventArgs routedEventArgs) at
System.Windows.EventRoute.InvokeHandlersImpl(Object source,
RoutedEventArgs args, Boolean reRaised) at
System.Windows.UIElement.ReRaiseEventAs(DependencyObject sender,
RoutedEventArgs args, RoutedEvent newEvent) at
System.Windows.UIElement.OnMouseDownThunk(Object sender,
MouseButtonEventArgs e) at
System.Windows.Input.MouseButtonEventArgs.InvokeEventHandler(Delegate
genericHandler, Object genericTarget) at
System.Windows.RoutedEventArgs.InvokeHandler(Delegate handler, Object
target) at
System.Windows.RoutedEventHandlerInfo.InvokeHandler(Object target,
RoutedEventArgs routedEventArgs) at
System.Windows.EventRoute.InvokeHandlersImpl(Object source,
RoutedEventArgs args, Boolean reRaised) at
System.Windows.UIElement.RaiseEventImpl(DependencyObject sender,
RoutedEventArgs args) at
System.Windows.UIElement.RaiseTrustedEvent(RoutedEventArgs args) at
System.Windows.UIElement.RaiseEvent(RoutedEventArgs args, Boolean
trusted) at System.Windows.Input.InputManager.ProcessStagingArea()
at System.Windows.Input.InputManager.ProcessInput(InputEventArgs
input) at
System.Windows.Input.InputProviderSite.ReportInput(InputReport
inputReport) at
System.Windows.Interop.HwndMouseInputProvider.ReportInput(IntPtr hwnd,
InputMode mode, Int32 timestamp, RawMouseActions actions, Int32 x,
Int32 y, Int32 wheel) at
System.Windows.Interop.HwndMouseInputProvider.FilterMessage(IntPtr
hwnd, WindowMessage msg, IntPtr wParam, IntPtr lParam, Boolean&
handled) at
System.Windows.Interop.HwndSource.InputFilterMessage(IntPtr hwnd,
Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled) at
MS.Win32.HwndWrapper.WndProc(IntPtr hwnd, Int32 msg, IntPtr wParam,
IntPtr lParam, Boolean& handled) at
MS.Win32.HwndSubclass.DispatcherCallbackOperation(Object o) at
System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate
callback, Object args, Int32 numArgs) at
System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source,
Delegate callback, Object args, Int32 numArgs, Delegate catchHandler)
我注意到的另一件值得一提的事情是,如果我将 ListView
的 SelectionMode
设置为 Multiple
,则不会引发异常。在这种情况下,List
中 FooBar
的值我从 TextBox
更改的项目仍然 selected,以及我可能 [=75] 的任何其他项目=] 然后之后。
注意:我正在使用 Fody/PropertyChanged 来实现 INotifyCollectionChanged
。
如果要更新 Name,Bar 也需要实现 PropertyChanged。
[PropertyChanged.ImplementPropertyChanged]
public class Bar
{
public string Name { get; set; }
}
此外,通常最好通过 VM 上的 属性 完成所选项目的绑定。
例如:
[PropertyChanged.ImplementPropertyChanged]
public class ViewModel
{
public ObservableCollection<Foo> Foos { get; set; }
//This is the property to hold the selected item.
public Foo SelectedFoo { get; set; }
}
然后将您的 ListView 绑定更改为:
<ListView x:Name="V_List" SelectedItem="{Binding SelectedFoo}" ItemsSource="{Binding Path=Foos}" SelectionMode="Single">
...
</ListView>
您的文本框变为:
<!--No need for binding the DataContext of the Grid.-->
<Grid>
<TextBox Text="{Binding Path=SelectedFoo.FooBar.Name, Mode=TwoWay, UpdateSourceTrigger=LostFocus}" />
</Grid>
What I don't understand is WHY is this happening?
我不能给你一个明确的答案,但从错误来看,ListView 似乎保留了控件项的内部字典。此外,我敢打赌该内部字典的键是基于 GetHashCode 的值。问题是您允许用户更改该键(通过更改名称 属性),而该对象仍然是控件的一部分。我猜想在选择更改时它通过根据需要添加和删除项目来维护字典。由于您的项目的有效 "key" 已更改,它可能正在尝试将其读入内部字典,却发现该项目已经在字典中(但在原始键下)。
您可以通过在名称更改时删除更改的项目并将其读取到 Foos 集合来测试该理论。这应该使用正确的新密钥清除并重新插入内部字典中的项目。
编辑:
我发现这个问题的根源是 Equals()
和 GetHashCode()
在 Bar
中的实现。
特别是参与 GetHashCode()
并且还绑定到 TextBox
的属性(如 Bar
中的 Name
)。删除这些覆盖方法后一切正常(除了我想保留它们)
我不明白为什么会这样?
我有一个 TextBox
、一个 ListView
,以及一些与以下 ViewModel 的数据绑定:
[PropertyChanged.ImplementPropertyChanged]
public class ViewModel
{
public ObservableCollection<Foo> Foos { get; set; }
public Foo SelectedFoo { get; set; }
}
[PropertyChanged.ImplementPropertyChanged]
public class Foo
{
public Bar FooBar { get; set; }
}
[PropertyChanged.ImplementPropertyChanged]
public class Bar
{
public string Name { get; set; }
public override bool Equals(object obj)
{
var other = obj as Bar;
if (other != null)
{
return other.Name == Name;
}
return false;
}
public override int GetHashCode()
{
return Name.GetHashCode();
}
}
这是我的清单:
<ListView x:Name="V_List" SelectedItem="{Binding SelectedFoo}" ItemsSource="{Binding Path=Foos}" SelectionMode="Single">
...
</ListView>
这是我的文本框:
<TextBox Text="{Binding Path=SelectedFoo.FooBar.Name, Mode=TwoWay, UpdateSourceTrigger=LostFocus}" />
事情是这样的:
当我 select 来自 List
的第一个 Foo
时,绑定有效,而 FooBar
属性 的 Name
selected Foo
出现在TextBox
。无论我更改 selection 多少次,适当的值都会出现在 TextBox
.
但是现在,如果我使用 TextBox
更改 Name
(由于 TwoWay 数据绑定,在失去焦点后,它正在工作,我通过调试进行了检查)然后更改我的 selection 从列表中,TextBox
仍然显示先前 selected 项目的值。
此外,再次 selecting 相同的项目,然后 selecting 其他一些项目,我得到以下异常(令人惊讶的是,调试器没有报告,我不得不将其记录到一个文件中。可能是因为我的代码没有引发异常。)
这是日志:
The Exception is:-Exception :: System.ArgumentException: An item with the same key has already been added. at System.ThrowHelper.ThrowArgumentException(ExceptionResource resource) at System.Collections.Generic.Dictionary
2.Insert(TKey key, TValue value, Boolean add) at System.Collections.Generic.Dictionary
2..ctor(IDictionary2 dictionary, IEqualityComparer
1 comparer) at System.Windows.Controls.Primitives.Selector.InternalSelectedItemsStorage..ctor(InternalSelectedItemsStorage collection, IEqualityComparer`1 equalityComparer) at System.Windows.Controls.Primitives.Selector.SelectionChanger.ApplyCanSelectMultiple() at System.Windows.Controls.Primitives.Selector.SelectionChanger.End() at System.Windows.Controls.Primitives.Selector.SetSelectedHelper(Object item, FrameworkElement UI, Boolean selected) at System.Windows.Controls.Primitives.Selector.NotifyIsSelectedChanged(FrameworkElement container, Boolean selected, RoutedEventArgs e) at System.Windows.Controls.Primitives.Selector.OnSelected(Object sender, RoutedEventArgs e) at System.Windows.RoutedEventHandlerInfo.InvokeHandler(Object target, RoutedEventArgs routedEventArgs) at System.Windows.EventRoute.InvokeHandlersImpl(Object source, RoutedEventArgs args, Boolean reRaised) at System.Windows.UIElement.RaiseEventImpl(DependencyObject sender, RoutedEventArgs args) at System.Windows.UIElement.RaiseEvent(RoutedEventArgs e) at System.Windows.Controls.ListBoxItem.OnSelected(RoutedEventArgs e)
at System.Windows.Controls.ListBoxItem.OnIsSelectedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) at System.Windows.DependencyObject.OnPropertyChanged(DependencyPropertyChangedEventArgs e) at System.Windows.FrameworkElement.OnPropertyChanged(DependencyPropertyChangedEventArgs e) at System.Windows.DependencyObject.NotifyPropertyChange(DependencyPropertyChangedEventArgs args) at System.Windows.DependencyObject.UpdateEffectiveValue(EntryIndex entryIndex, DependencyProperty dp, PropertyMetadata metadata, EffectiveValueEntry oldEntry, EffectiveValueEntry& newEntry, Boolean coerceWithDeferredReference, Boolean coerceWithCurrentValue, OperationType operationType) at System.Windows.DependencyObject.SetValueCommon(DependencyProperty dp, Object value, PropertyMetadata metadata, Boolean coerceWithDeferredReference, Boolean coerceWithCurrentValue, OperationType operationType, Boolean isInternal) at System.Windows.DependencyObject.SetCurrentValueInternal(DependencyProperty dp, Object value) at System.Windows.Controls.ListBox.NotifyListItemClicked(ListBoxItem item, MouseButton mouseButton) at System.Windows.Controls.ListBoxItem.HandleMouseButtonDown(MouseButton mouseButton) at System.Windows.Controls.ListBoxItem.OnMouseLeftButtonDown(MouseButtonEventArgs e) at System.Windows.UIElement.OnMouseLeftButtonDownThunk(Object sender, MouseButtonEventArgs e) at System.Windows.Input.MouseButtonEventArgs.InvokeEventHandler(Delegate genericHandler, Object genericTarget) at System.Windows.RoutedEventArgs.InvokeHandler(Delegate handler, Object target) at System.Windows.RoutedEventHandlerInfo.InvokeHandler(Object target, RoutedEventArgs routedEventArgs) at System.Windows.EventRoute.InvokeHandlersImpl(Object source, RoutedEventArgs args, Boolean reRaised) at System.Windows.UIElement.ReRaiseEventAs(DependencyObject sender, RoutedEventArgs args, RoutedEvent newEvent) at System.Windows.UIElement.OnMouseDownThunk(Object sender, MouseButtonEventArgs e) at System.Windows.Input.MouseButtonEventArgs.InvokeEventHandler(Delegate genericHandler, Object genericTarget) at System.Windows.RoutedEventArgs.InvokeHandler(Delegate handler, Object target) at System.Windows.RoutedEventHandlerInfo.InvokeHandler(Object target, RoutedEventArgs routedEventArgs) at System.Windows.EventRoute.InvokeHandlersImpl(Object source, RoutedEventArgs args, Boolean reRaised) at System.Windows.UIElement.RaiseEventImpl(DependencyObject sender, RoutedEventArgs args) at System.Windows.UIElement.RaiseTrustedEvent(RoutedEventArgs args) at System.Windows.UIElement.RaiseEvent(RoutedEventArgs args, Boolean trusted) at System.Windows.Input.InputManager.ProcessStagingArea() at System.Windows.Input.InputManager.ProcessInput(InputEventArgs input) at System.Windows.Input.InputProviderSite.ReportInput(InputReport inputReport) at System.Windows.Interop.HwndMouseInputProvider.ReportInput(IntPtr hwnd, InputMode mode, Int32 timestamp, RawMouseActions actions, Int32 x, Int32 y, Int32 wheel) at System.Windows.Interop.HwndMouseInputProvider.FilterMessage(IntPtr hwnd, WindowMessage msg, IntPtr wParam, IntPtr lParam, Boolean& handled) at System.Windows.Interop.HwndSource.InputFilterMessage(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled) at MS.Win32.HwndWrapper.WndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled) at MS.Win32.HwndSubclass.DispatcherCallbackOperation(Object o) at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs) at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler)
我注意到的另一件值得一提的事情是,如果我将 ListView
的 SelectionMode
设置为 Multiple
,则不会引发异常。在这种情况下,List
中 FooBar
的值我从 TextBox
更改的项目仍然 selected,以及我可能 [=75] 的任何其他项目=] 然后之后。
注意:我正在使用 Fody/PropertyChanged 来实现 INotifyCollectionChanged
。
如果要更新 Name,Bar 也需要实现 PropertyChanged。
[PropertyChanged.ImplementPropertyChanged]
public class Bar
{
public string Name { get; set; }
}
此外,通常最好通过 VM 上的 属性 完成所选项目的绑定。
例如:
[PropertyChanged.ImplementPropertyChanged]
public class ViewModel
{
public ObservableCollection<Foo> Foos { get; set; }
//This is the property to hold the selected item.
public Foo SelectedFoo { get; set; }
}
然后将您的 ListView 绑定更改为:
<ListView x:Name="V_List" SelectedItem="{Binding SelectedFoo}" ItemsSource="{Binding Path=Foos}" SelectionMode="Single">
...
</ListView>
您的文本框变为:
<!--No need for binding the DataContext of the Grid.-->
<Grid>
<TextBox Text="{Binding Path=SelectedFoo.FooBar.Name, Mode=TwoWay, UpdateSourceTrigger=LostFocus}" />
</Grid>
What I don't understand is WHY is this happening?
我不能给你一个明确的答案,但从错误来看,ListView 似乎保留了控件项的内部字典。此外,我敢打赌该内部字典的键是基于 GetHashCode 的值。问题是您允许用户更改该键(通过更改名称 属性),而该对象仍然是控件的一部分。我猜想在选择更改时它通过根据需要添加和删除项目来维护字典。由于您的项目的有效 "key" 已更改,它可能正在尝试将其读入内部字典,却发现该项目已经在字典中(但在原始键下)。
您可以通过在名称更改时删除更改的项目并将其读取到 Foos 集合来测试该理论。这应该使用正确的新密钥清除并重新插入内部字典中的项目。