Reputation: 3569
In created a simple WPF Window containing a DataGrid populated with instances of Item class. The items in the Datagrid consist of only 10 instances, each duplicated 10 times. In the same window I put a TextBlock displaying the number of items selected in the datagrid. After the window is loaded, all items are selected, then the collection of items is cleaned (this would yield to an empty set of selected items) and repopulated with some 5 new instances. At this point, no item is selected any more, so the number of selected items should be 0, but it is 56.
Even if slightly different, I suspect that this behavior is related to WPF: SelectedItems with duplicate object references and to Datagrid Multiselection of same object; I thing that this bug refers not only to DataGrid, but to MultiSelector in general and still I don't see an easy solution to this specific problem. Note that in my situation, I cannot replace copies of same element with different instances, nor I can re-implement Equals or GetHashCode in Items.
The same behavior is performed in .NET framework 4.0, 4.5, 4.5.1.
Here's the code to reproduce the error:
Item:
using System.Diagnostics;
namespace WpfApplication2
{
[DebuggerDisplay("{Name}")]
public class Item
{
public string Name
{
get;
set;
}
public string Description
{
get;
set;
}
}
}
Window (XAML):
<Window x:Class="WpfApplication2.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"
Loaded="Window_Loaded"
>
<DockPanel LastChildFill="True">
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal">
<TextBlock Text="Items: " />
<TextBlock Text="{Binding Path=Count}" />
</StackPanel>
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal">
<TextBlock Text="Selected items: " />
<TextBlock Text="{Binding ElementName=MyDataGrid, Path=SelectedItems.Count}" />
</StackPanel>
<DataGrid IsReadOnly="True" x:Name="MyDataGrid" ItemsSource="{Binding}" AutoGenerateColumns="False" >
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Path=Name}" />
<DataGridTextColumn Binding="{Binding Path=Description}" />
</DataGrid.Columns>
</DataGrid>
</DockPanel>
</Window>
Window (code):
using System.Collections.ObjectModel;
using System.Windows;
namespace WpfApplication2
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
ObservableCollection<Item> items = new ObservableCollection<Item>();
PopulateItems(items);
this.DataContext = items;
}
private static void PopulateItems(ObservableCollection<Item> items)
{
for (int i = 0; i < 10; i++)
{
Item item = new Item();
item.Name = string.Format("Name {0}", i);
item.Description = string.Format("Description {0}", i);
for (int j = 0; j < 10; j++)
{
items.Add(item);
}
}
}
private static void ChangeDataSource(ObservableCollection<Item> items)
{
items.Clear();
for (int i = 0; i < 5; i++)
{
Item item = new Item();
item.Name = string.Format("Name {0}", i);
item.Description = string.Format("Description {0}", i);
items.Add(item);
}
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
this.MyDataGrid.SelectAll();
ChangeDataSource(this.DataContext as ObservableCollection<Item>);
}
}
}
Upvotes: 0
Views: 231
Reputation: 4978
You could wrap each copy of each item in a new object:
private static void PopulateItems(ObservableCollection<ItemWrapper> wrappers)
{
for (int i = 0; i < 10; i++)
{
Item item = new Item();
item.Name = string.Format("Name {0}", i);
item.Description = string.Format("Description {0}", i);
for (int j = 0; j < 10; j++)
{
var wrapper = new ItemWrapper(item);
wrappers.Add(wrapper);
}
}
}
With ItemWrapper
being something like:
public class ItemWrapper
{
public Item { get; set; }
public ItemWrapper(Item item)
{
this.Item = item;
}
}
Obviously, you should then change your XAML to:
<DataGrid IsReadOnly="True" x:Name="MyDataGrid" ItemsSource="{Binding}" AutoGenerateColumns="False" >
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Path=Item.Name}" />
<DataGridTextColumn Binding="{Binding Path=Item.Description}" />
</DataGrid.Columns>
</DataGrid>
Upvotes: 1