首页 文章

与ListView SelectedItem的属性和TextBox绑定的ArgumentException

提问于
浏览
1

EDIT:

我发现这个问题的根源是 Equals() 中的 Equals()GetHashCode() 实现 .

特别是参与 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:

<TextBox Text="{Binding Path=SelectedFoo.FooBar.Name, Mode=TwoWay, UpdateSourceTrigger=LostFocus}" />

这就是事情:

当我从 List 中选择第一个 Foo 时,绑定有效,并且 TextBox 中出现所选 FooFooBar 属性的 Name . 无论我更改选择的次数, TextBox 中都会显示相应的值 .

但是现在如果我使用 TextBox 更改 Name (在焦点丢失之后,由于TwoWay数据绑定,正在工作并且我使用调试检查)然后从列表中更改我的选择, TextBox 仍然显示先前选择的项目的值 .

此外,再次选择相同的项目,然后选择其他一些项目,我得到以下异常(令人惊讶的是,调试器没有报告,我不得不将其记录到文件 . 可能是因为异常没有从我的代码 . )

这是日志:

异常是:-Exception :: System.ArgumentException:已添加具有相同键的项 . System.ChrowHelper.ThrowArgumentException(ExceptionResource资源)位于System.Collections.Generic.Dictionary2.Insert(TKey键,TValue值,布尔加法)的System.Collections.Generic.Dictionary2..ctor(IDictionary2字典,IEqualityComparer1比较器)中的System.ThrowHelper.ThrowArgumentException(ExceptionResource资源) System.Windows.Controls.Primitives.Selector.SelectionChanger的System.Windows.Controls.Primitives.Selector.SelectionChanger.ApplyCanSelectMultiple()中的.Windows.Controls.Primitives.Selector.InternalSelectedItemsStorage..ctor(InternalSelectedItemsStorage集合,IEqualityComparer`1 equalityComparer) System.Windows.Controls.Primitives.Selector.NotifyIsSelectedChanged(FrameworkElement container,Boolean selected,RoutedEventArgs e)中System.Windows.Controls.Primitives.Selector.SetSelectedHelper(Object item,FrameworkElement UI,Boolean selected)的.End() System.Windows.RoutedEventHandlerInfo.InvokeHandler中的.Windows.Controls.Primitives.Selector.OnSelected(Object sender,RoutedEventArgs e)(对象目标,Rout系统中的System.Windows.UIElement.RaiseEventImpl(DependencyObject sender,RoutedEventArgs args)处的System.Windows.EventRoute.InvokeHandlersImpl(Object source,RoutedEventArgs args,Boolean reRaised)中的edEventArgs routedEventArgs)系统的System.Windows.UIElement.RaiseEvent(RoutedEventArgs e) .Windows.Controls.ListBoxItem.OnSelected(RoutedEventArgs e)上System.Windows.Controls.ListBoxItem.OnIsSelectedChanged(的DependencyObject d,DependencyPropertyChangedEventArgs e)上System.Windows.DependencyObject.OnPropertyChanged(DependencyPropertyChangedEventArgs e)上System.Windows.FrameworkElement.OnPropertyChanged( DependencyPropertyChangedEventArgs e)上System.Windows.DependencyObject.NotifyPropertyChange(DependencyPropertyChangedEventArgs参数)在System.Windows.DependencyObject.UpdateEffectiveValue(EntryIndex entryIndex,的DependencyProperty DP,PropertyMetadata元数据,EffectiveValueEntry oldEntry,EffectiveValueEntry&newEntry,布尔coerceWithDeferredReference,布尔coerceWithCurrentVal ue,OperationType operationType)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)在System.Windows上的System.Windows.Controls.List.Imp中的System.Windows.Controls.ListBoxItem.HandleMouseButtonDown(MouseButton mouseButton)中的System.Windows.Controls.ListBox.NotifyListItemClicked(ListBoxItem项目,MouseButton mouseButton)处于System.Windows的System.Windows.Controls.ListBoxItem.OnMouseLeftButtonDown(MouseButtonEventArgs e) . System.Windows.Input.MouseButtonEventArgs.InvokeEventHandler(Delegate genericHandler,Object genericTarget)中的UIElement.OnMouseLeftButtonDownThunk(Object sender,MouseButtonEventArgs e)at at系统中System.Windows.EventRoute.InvokeHandlersImpl(Object source,RoutedEventArgs args,Boolean reRaised)的System.Windows.RoutedEventHandlerInfo.InvokeHandler(Object target,RoutedEventArgs routedEventArgs)中的System.Windows.RoutedEventArgs.InvokeHandler(Delegate handler,Object target) . 系统中的System.Windows.Input.MouseButtonEventArgs.InvokeEventHandler(Delegate genericHandler,Object genericTarget)中的System.Windows.UIElement.OnMouseDownThunk(Object sender,MouseButtonEventArgs e)中的Windows.UIElement.ReRaiseEventAs(DependencyObject sender,RoutedEventArgs args,RoutedEvent newEvent) . System.Windows上的System.Windows.EventRoute.InvokeHandlersImpl(Object source,RoutedEventArgs args,Boolean reRaised)中的System.Windows.RoutedEventHandlerInfo.InvokeHandler(Object target,RoutedEventArgs routedEventArgs)中的Windows.RoutedEventArgs.InvokeHandler(Delegate handler,Object target) . System.Windows.UIElement.Rais中的UIElement.RaiseEventImpl(DependencyObject sender,RoutedEventArgs args) System.Windows上的System.Windows.InputManager.ProcessInput(InputEventArgs输入)处的System.Windows.InputManager.ProcessStagingArea()处的System.Windows.UIElement.RaiseEvent(RoutedEventArgs args,布尔值为受信任)的eTrustedEvent(RoutedEventArgs args) System.Windows.Interop.HwndMouseInputProvider上System.Windows.Interop.HwndMouseInputProvider.ReportInput(IntPtr hwnd,InputMode mode,Int32 timestamp,RawMouseActions actions,Int32 x,Int32 y,Int32 wheel)的.Input.InputProviderSite.ReportInput(InputReport inputReport) MS.Win32.HwndWrapper的System.Windows.Interop.HwndSource.InputFilterMessage(IntPtr hwnd,Int32 msg,IntPtr wParam,IntPtr lParam,Boolean&handling)中的.FilterMessage(IntPtr hwnd,WindowMessage msg,IntPtr wParam,IntPtr lParam,Boolean&handling) .WndProc(IntPtr hwnd,Int32 msg,IntPtr wParam,IntPtr lParam,Boolean&handling)位于System.Windows.Threading.ExceptionWrapper.InternalRealCall(委托callb)的MS.Win32.HwndSubclass.DispatcherCallbackOperation(Object o) System.Windows.Threading.ExceptionWrapper.TryCatchWhen(对象源,委托回调,对象args,Int32 numArgs,Delegate catchHandler)中的ack,Object args,Int32 numArgs)

我注意到的另一件值得一提的是,如果我将 ListViewSelectionMode 设置为 Multiple ,则不会引发异常 . 在这种情况下, ListFooBar 的值从 TextBox 更改的项目保持选中状态,以及我之后可能选择的任何其他项目 .

注意:我使用Fody/PropertyChanged来实现 INotifyCollectionChanged .

2 回答

  • 2

    我不明白为什么会发生这种情况?

    我不能给你一个确定的答案,但看看错误,看起来ListView保持控件项目的内部字典 . 此外,我敢打赌,内部字典的关键是基于GetHashCode的值 . 问题是,当对象仍然是控件的一部分时,您允许用户更改该键(通过更改name属性) . 我想在选择时更改它是通过添加和删除项目来维护字典 . 由于您项目的有效“密钥”已更改,因此可能尝试将其读取到内部字典,但只发现该项目已在字典中(但在原始密钥下) .

    您可以通过在名称更改时删除并读取已更改的项目到Foos集合来测试该理论 . 这应该使用正确的新密钥清除并重新插入内部字典中的项目 .

  • 2

    如果要更新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>
    

    你的TextBox变成了:

    <!--No need for binding the DataContext of the Grid.-->
    <Grid>
        <TextBox Text="{Binding Path=SelectedFoo.FooBar.Name, Mode=TwoWay, UpdateSourceTrigger=LostFocus}" />
    </Grid>
    

相关问题