C# wpflistbox&;具有更改哈希代码的项
我有一个C# wpflistbox&;具有更改哈希代码的项,c#,wpf,mvvm,listbox,C#,Wpf,Mvvm,Listbox,我有一个列表框绑定到一组项目,这些项目的ID用于生成GetHashCode()的结果。添加新项目时,它的ID为0,直到它首次保存到我们的数据库。这导致我的列表框投诉;我认为原因是当一个项目第一次被列表框使用时,它存储在一个内部字典中,该字典不希望hashcode发生变化 我可以通过从集合中删除未保存的项(我必须在此阶段通知UI将其从字典中删除)、保存到DB并将其添加回集合来修复此问题。这很混乱,而且我并不总是能够通过Save(businessobjectobj)方法访问集合。有没有人能找到解决这
列表框
绑定到一组项目,这些项目的ID用于生成GetHashCode()
的结果。添加新项目时,它的ID为0,直到它首次保存到我们的数据库。这导致我的列表框
投诉;我认为原因是当一个项目第一次被列表框
使用时,它存储在一个内部字典
中,该字典不希望hashcode发生变化
我可以通过从集合中删除未保存的项(我必须在此阶段通知UI将其从字典中删除)、保存到DB并将其添加回集合来修复此问题。这很混乱,而且我并不总是能够通过Save(businessobjectobj)
方法访问集合。有没有人能找到解决这个问题的替代方案
根据布拉姆的回答进行编辑:
我正在使用MVVM,因此我修改了代码以使用绑定。要重现问题,请单击“添加”,选择项目,单击“保存”,重复,然后尝试进行选择。我认为这表明列表框
仍然保留其内部字典
中的旧哈希代码,因此出现了键冲突错误
<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++;
}
}
}
公共部分类主窗口:窗口{
公共可观测集合列表{get;private set;}
已选择公共ListItem{get;set;}
私有Int32-saveId;
公共主窗口(){
this.DataContext=this;
this.List=新的ObservableCollection();
this.saveId=100;
初始化组件();
}
私有无效按钮\u单击\u添加(对象发送方,路由目标){
this.List.Add(新列表项(0));
}
私有无效按钮\u单击\u保存(对象发送方,路由目标){
if(Selected!=null&&Selected.ID==0){
Selected.ID=saveId;
saveId++;
}
}
}
编辑2经过一些测试,我发现了一些东西:
- 更改
列表框中项目的哈希代码似乎可以正常工作
- 更改
列表框中所选项目的哈希代码将中断
这是功能性的
IList列表框.SelectedItems
。添加到所选内容的项目将添加到SelectedItems
,不再包含在所选内容中的项目将被删除
如果项目的哈希代码在选中时被更改,则无法将其从SelectedItems
中删除。即使手动调用SelectedItems.Remove(item)
,SelectedItems.Clear()
和将SelectedIndex
设置为-1都无效,并且该项仍保留在IList
中。这会导致在下次选择它时抛出异常,因为我相信它会再次添加到SelectedItems
有没有人能找到解决这个问题的替代方案
对象的哈希代码在对象生存期内不得更改。您不应该将可变数据用于哈希代码计算
更新
我没想到,我的回答会引起这样的讨论。这里有一些详细的解释,可能会有帮助
让我们看看代码中定义的一些可变实体类型,它覆盖GetHashCode
,当然,等于
。相等性基于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);
}
}
这是一些外部代码,它使用字典
作为内部用途:
// 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]);
还有,外部代码。在这里,它试图通过key1
来定位一些数据:
Console.WriteLine(dictionary[key1]); // ooops! key1 was not found in dictionary
当然,您可以设计可变类型,它覆盖GetHashCode
和Equals
,并在可变数据上计算哈希代码。但是你真的不应该这样做(除了那些情况,当你明确知道你在做什么的时候)
您不能保证任何外部代码不会在内部使用
字典
或哈希集
。我怀疑您的代码的问题在于它没有覆盖等于
ListBox
使用Equals
查找一个项目,因此如果有多个Equals返回true,则它将匹配多个项目,只是简单地搞乱了。列表框中的项目必须是唯一的,基于相等。
如果尝试将列表框
绑定到List Int32或List string并重复任何值,则它也会出现相同的问题
当你说抱怨的时候。它如何抱怨
在下面这个简单的示例中,ListView
没有与GetHashCode
的更改保持平衡
您是否实现了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; }
}
}
使用系统组件模型;
使用System.Collections.ObjectModel;
命名空间ListViewGetHashCode
{
公共部分类主窗口:窗口
{
ObservableCollection li=新的ObservableCollection();
私有Int32 saveId=100;
私有Int32 tempId=-1;
公共主窗口()
{
this.DataContext=this;
对于(int32i=1;i100)中断;
}
}
专用无效按钮
Console.WriteLine(dictionary[key1]); // ooops! key1 was not found in dictionary
<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; }
}
}