WPF ListBox&具有更改的哈希码的项目

时间:2013-05-28 10:08:22

标签: c# wpf mvvm listbox

我有一个ListBox绑定到一个项目集合,这些项目具有用于生成GetHashCode()结果的ID。添加新项目时,它的ID为0,直到它首次保存到我们的数据库中。这导致我的ListBox抱怨;我相信原因是因为当ListBox首次使用某个项目时,它会存储在内部Dictionary中,而该内部Save(BusinessObject obj)不会期望哈希码会发生变化。

我可以通过从集合中删除未保存的项来解决此问题(我必须在此阶段通知UI将其从字典中删除),保存到数据库,然后将其添加回采集。这很麻烦,我并不总是可以通过ListBox方法访问该集合。有没有人有这个问题的替代解决方案?

编辑在回应Blam的回答:

我正在使用MVVM,所以我修改了代码以使用绑定。要重现问题,请单击“添加”,选择项目,单击“保存”,“重复”,然后尝试进行选择。我认为这表明Dictionary仍然保留在其内部<Window x:Class="ListBoxHashCode.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <StackPanel HorizontalAlignment="Center" Orientation="Horizontal"> <Button Click="Button_Click_Add" Content="Add"/> <Button Click="Button_Click_Save" Content="Save Selected"/> </StackPanel> <ListBox Grid.Row="1" ItemsSource="{Binding List}" DisplayMemberPath="ID" SelectedItem="{Binding Selected}"/> </Grid> </Window> public partial class MainWindow : Window { public ObservableCollection<ListItem> List { get; private set; } public ListItem Selected { get; set; } private Int32 saveId; public MainWindow() { this.DataContext = this; this.List = new ObservableCollection<ListItem>(); this.saveId = 100; InitializeComponent(); } private void Button_Click_Add(object sender, RoutedEventArgs e) { this.List.Add(new ListItem(0)); } private void Button_Click_Save(object sender, RoutedEventArgs e) { if (Selected != null && Selected.ID == 0) { Selected.ID = saveId; saveId++; } } } 中的旧哈希码,因此冲突键错误。

ListBox

编辑2经过一些测试,我发现了一些事情:

  • 更改ListBox中项目的哈希码似乎没问题。

  • IList ListBox.SelectedItems个商品中更改所选商品的哈希码 它的功能。

当进行选择(单个或多个选择模式)时,SelectedItems会更新。添加到选区中的项目会添加到SelectedItems,并且会删除不再包含在选区中的项目。

如果项目的哈希码在选中时更改,则无法将其从SelectedItems.Remove(item)中删除。即使是手动调用SelectedItems.Clear()SelectedIndex并将IList设置为-1也都无效,该项目仍保留在SelectedItems中。这会导致在下次选择异常后抛出异常,因为我相信它会再次添加到{{1}}。

2 个答案:

答案 0 :(得分:4)

  

有没有人有这个问题的替代解决方案?

在对象生命周期内,不得更改对象的哈希码。您不应该使用可变数据进行哈希码计算。

<强>更新

我没想到,我的回答会引起这样的讨论。这里有一些详细的解释,可能会有助于OP。

让我们看一下代码中定义的一些可变实体类型,它覆盖GetHashCode,当然还有Equals。平等基于Id平等:

class Mutable : IEquatable<Mutable>
{
    public int Id { get; set; }

    public override int GetHashCode()
    {
        return Id.GetHashCode();
    }

    public override bool Equals(object obj)
    {
        if (obj == null)
        {
            return false;
        }

        var mutable = obj as Mutable;
        if (mutable == null)
        {
            return false;
        }

        return this.Equals(mutable);
    }

    public bool Equals(Mutable other)
    {
        return Id.Equals(other.Id);
    }
}

您的代码中的某处已创建了此类型的多个实例:

        // here's some mutable entities with hash-code, calculated using mutable data:
        var key1 = new Mutable { Id = 1 };
        var key2 = new Mutable { Id = 2 };
        var key3 = new Mutable { Id = 3 };

这是一些外部代码,它使用Dictionary<Mutable, string>作为内部用途:

        // let's use them as a key for the dictionary:
        var dictionary = new Dictionary<Mutable, string>
        {
            { key1, "John" },
            { key2, "Mary" },
            { key3, "Peter" }
        };

        // everything is ok, all of the keys are located properly:
        Console.WriteLine(dictionary[key1]);
        Console.WriteLine(dictionary[key2]);
        Console.WriteLine(dictionary[key3]);

再次,你的代码。假设您已更改Id的{​​{1}}。哈希码也改变了:

key1

再次,外部代码。在这里,它尝试按 // let's change the hashcode of key1: key1.Id = 4;

找到一些数据
key1

当然,可以设计可变类型,它会覆盖Console.WriteLine(dictionary[key1]); // ooops! key1 was not found in dictionary GetHashCode,并计算可变数据的哈希码。但你不应该这样做,真的(除了这些情况,当你明确知道,你在做什么)。

无法保证,任何外部代码都不会在内部使用EqualsDictionary<TKey, TValue>

答案 1 :(得分:1)

我怀疑您的代码存在的问题是它没有覆盖Equals

ListBox使用Equals来查找项目,因此如果多个等于返回true,则它会匹配多个项目并且只是简单的混乱。
ListBox中的项目必须基于等于唯一 如果您尝试将ListBox绑定到List Int32或List字符串并重复任何值,那么它会遇到同样的问题。

当你说抱怨时。它是如何抱怨的?

在下面的这个简单示例中,ListViewGetHashCode的更改没有收支平衡。

您是否实施了INotifyPropertyChanged

<Window x:Class="ListViewGetHashCode.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <StackPanel Grid.Row="0" Orientation="Horizontal">
            <Button Click="Button_Click" Content="Button"/>
            <Button Click="Button_Click2" Content="Add"/>
            <Button Grid.Row="0" Click="Button_Click_Save" Content="Save"/>
        </StackPanel>
        <ListBox Grid.Row="1" ItemsSource="{Binding BindingList}" DisplayMemberPath="ID" SelectedItem="{Binding Selected}" VirtualizingStackPanel.VirtualizationMode="Standard"/>
        <!--<ListBox Grid.Row="1" x:Name="lbHash" ItemsSource="{Binding}" DisplayMemberPath="ID"/>--> 
    </Grid>
</Window>

using System.ComponentModel;
using System.Collections.ObjectModel;

namespace ListViewGetHashCode
{
    public partial class MainWindow : Window
    {
        ObservableCollection<ListItem> li = new ObservableCollection<ListItem>();
        private Int32 saveId = 100;
        private Int32 tempId = -1;
        public MainWindow()
        {
            this.DataContext = this;
            for (Int32 i = 1; i < saveId; i++) li.Add(new ListItem(i));

            InitializeComponent();

        }
        public ObservableCollection<ListItem> BindingList { get { return li; } }
        public ListItem Selected { get; set; }
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            Int32 counter = 0;
            foreach (ListItem l in li)
            {
                l.ID = -l.ID;
                counter++;
                if (counter > 100) break;
            }
        }
        private void Button_Click2(object sender, RoutedEventArgs e)
        {          
            //li.Add(new ListItem(0)); // this is where it breaks as items were not unique
            li.Add(new ListItem(tempId));
            tempId--;
        }   
        private void Button_Click_Save(object sender, RoutedEventArgs e)
        {
            if (Selected != null && Selected.ID <= 0)
            {
                Selected.ID = saveId;
                saveId++;
            }
        }
    }
    public class ListItem : Object, INotifyPropertyChanged
    {
        private Int32 id;
        public event PropertyChangedEventHandler PropertyChanged;
        protected void NotifyPropertyChanged(String info)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(info));
            }
        }  
        public Int32 ID 
        {
            get 
            { 
                return (id < 0) ? 0 : id;
                //if you want users to see 0 and not the temp id 
                //internally much use id
                //return id;
            }
            set
            {
                if (id == value) return;
                id = value;
                NotifyPropertyChanged("ID");
            }
        }
        public override bool Equals(object obj)
        {
            if (obj is ListItem)
            {
                ListItem comp = (ListItem)obj;
                return (comp.id == this.id);
            }
            else return false;
        }
        public bool Equals(ListItem comp)
        {
            return (comp.id == this.id);
        }
        public override int GetHashCode()
        {
            System.Diagnostics.Debug.WriteLine("GetHashCode " + id.ToString());
            return id;
            //can even return 0 as the hash for negative but it will only slow 
            //things downs
            //if (id > 0) return id;
            //else return 0;
        }
        public ListItem(Int32 ID) { id = ID; }
    }
}