使用 ICollectionView 对 ObservableCollection 进行排序无法正常工作
Sorting ObservableCollection with ICollectionView does't work correctly
要生成错误,select TopDataGrid
中的任何项目。结果,collection 项将被加载到 BottomDataGrid
中。这个 collection 按照我指定的 Name
属性 排序!然后 select TopDataGrid
中的任何其他项目。结果是 BottomDataGrid
的 ItemsSource
将被重新加载。现在 collection 未排序! collection 看起来和我在代码中指定的一样。此外,如果我用调试器检查 _customerView
我会看到排序的 collection.
我知道我可以将 List
与 OrderBy
和 INotifyPropertyChanged
一起使用来明确命令 UI 更新自身而不是 ObservableCollection
和 ICollectionView
.但我认为这不是正确的做法。
Win 7,.Net 4.0。只需复制和粘贴即可。
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<DataGrid Grid.Row="0"
AutoGenerateColumns="False"
ItemsSource="{Binding Items}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
CanUserAddRows="False"
CanUserDeleteRows="False"
CanUserResizeRows="False"
CanUserSortColumns="False"
SelectionMode="Single"
SelectionUnit="FullRow">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Name}"></DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
<DataGrid Grid.Row="1"
AutoGenerateColumns="False"
ItemsSource="{Binding SelectedItem.MyCollectionView}"
CanUserAddRows="False"
CanUserDeleteRows="False"
CanUserResizeRows="False"
CanUserSortColumns="False"
SelectionMode="Single"
SelectionUnit="FullRow">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Name}"></DataGridTextColumn>
<DataGridTextColumn Binding="{Binding Index}"></DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
<Grid Grid.Row="2">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0"
Text="{Binding Name1}"></TextBox>
<TextBox Grid.Column="1"
Text="{Binding Index}"></TextBox>
</Grid>
</Grid>
代码
public class TopGridItem
{
private ObservableCollection<BottomGridItem> _collection;
public ObservableCollection<BottomGridItem> Collection
{
get { return _collection; }
}
public String Name { get; set; }
public ICollectionView MyCollectionView
{
get
{
ICollectionView _customerView = CollectionViewSource.GetDefaultView(Collection);
_customerView.SortDescriptions.Add(new SortDescription("Name", ListSortDirection.Ascending));
return _customerView;
}
}
public TopGridItem()
{
_collection = new ObservableCollection<BottomGridItem>();
_collection.Add(new BottomGridItem { Name = "bbbbbb" });
_collection.Add(new BottomGridItem { Name = "aaaaa" });
_collection.Add(new BottomGridItem { Name = "aaaaa" });
_collection.Add(new BottomGridItem { Name = "ccccc" });
_collection.Add(new BottomGridItem { Name = "dddddd" });
}
}
public class BottomGridItem
{
public String Name { get; set; }
public String Index { get; set; }
}
/// <summary>
/// Логика взаимодействия для NewWindow.xaml
/// </summary>
public partial class ProgressWindow : INotifyPropertyChanged
{
public TopGridItem _selectedItem;
public String Name1 { get; set; }
public String Index { get; set; }
public ObservableCollection<TopGridItem> Items { get; set; }
public TopGridItem SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
OnPropertyChanged("SelectedItem");
}
}
public ProgressWindow()
{
InitializeComponent();
DataContext = this;
Items = new ObservableCollection<TopGridItem>();
Items.Add(new TopGridItem {Name = "One"});
Items.Add(new TopGridItem {Name = "Two"});
Items.Add(new TopGridItem {Name = "Three"});
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
更新
private void ClearSortDescriptionsOnItemsSourceChange()
{
this.Items.SortDescriptions.Clear();
this._sortingStarted = false;
List<int> descriptionIndices = this.GroupingSortDescriptionIndices;
if (descriptionIndices != null)
descriptionIndices.Clear();
foreach (DataGridColumn dataGridColumn in (Collection<DataGridColumn>) this.Columns)
dataGridColumn.SortDirection = new ListSortDirection?();
}
private static object OnCoerceItemsSourceProperty(DependencyObject d, object baseValue)
{
DataGrid dataGrid = (DataGrid) d;
if (baseValue != dataGrid._cachedItemsSource && dataGrid._cachedItemsSource != null)
dataGrid.ClearSortDescriptionsOnItemsSourceChange();
return baseValue;
}
似乎在 ClearSortDescriptionsOnItemsSourceChange
方法中清除了排序并且不再重新指定。我觉得是这个问题。
有一个 hacky 解决方法 :O)
更改 TopGridItem
class 使其实现 INotifyPropertyChanged
,然后像这样更改 MyCollectionView
属性:
public ICollectionView MyCollectionView
{
get
{
return _myCollectionView;
}
set
{
_myCollectionView = value;
OnPropertyChanged("MyCollectionView");
}
}
ICollectionView _myCollectionView;
向 TopGridItem
添加一个 public 方法,如下所示:
public void ResetView()
{
MyCollectionView = null; // This is the key to making it work
ICollectionView customerView = CollectionViewSource.GetDefaultView(_collection);
customerView.SortDescriptions.Add(new SortDescription("Name", ListSortDirection.Ascending));
MyCollectionView = customerView;
}
这要求 _collection
成为 class 字段而不是仅在构造函数中使用的变量。说到这里:
public TopGridItem()
{
_collection = new ObservableCollection<BottomGridItem>();
_collection.Add(new BottomGridItem { Name = "bbbbbb" });
_collection.Add(new BottomGridItem { Name = "aaaaa" });
_collection.Add(new BottomGridItem { Name = "aaaaa" });
_collection.Add(new BottomGridItem { Name = "ccccc" });
_collection.Add(new BottomGridItem { Name = "dddddd" });
ResetView();
}
注意对 ResetView
的调用。
现在,在 ProgressWindow
的 SelectedItem
属性 setter 中,您需要做的就是在选择更改时调用 ResetView
方法:
public TopGridItem SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
OnPropertyChanged("SelectedItem");
if (_selectedItem != null)
{
_selectedItem.ResetView();
}
}
}
我已经在这里测试过了,它似乎可以工作。如果不是,那么我一定是在某处打错了字,或者可能遗漏了我发布的代码中的某些内容。
代码的工作版本
XAML:
<Window x:Class="WpfApplication4.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="*" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<DataGrid Grid.Row="0"
AutoGenerateColumns="False"
ItemsSource="{Binding Items}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
CanUserAddRows="False"
CanUserDeleteRows="False"
CanUserResizeRows="False"
CanUserSortColumns="False"
SelectionMode="Single"
SelectionUnit="FullRow">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Name}"></DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
<DataGrid Grid.Row="1"
AutoGenerateColumns="False"
ItemsSource="{Binding SelectedItem.MyCollectionView}"
CanUserAddRows="False"
CanUserDeleteRows="False"
CanUserResizeRows="False"
CanUserSortColumns="False"
SelectionMode="Single"
SelectionUnit="FullRow">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Name}"></DataGridTextColumn>
<DataGridTextColumn Binding="{Binding Index}"></DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
<Grid Grid.Row="2">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0"
Text="{Binding Name1}"></TextBox>
<TextBox Grid.Column="1"
Text="{Binding Index}"></TextBox>
</Grid>
</Grid>
</Window>
代码隐藏:
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
using System.Windows.Data;
namespace WpfApplication4
{
public partial class MainWindow : Window, INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
DataContext = this;
Items = new ObservableCollection<TopGridItem>();
Items.Add(new TopGridItem { Name = "One" });
Items.Add(new TopGridItem { Name = "Two" });
Items.Add(new TopGridItem { Name = "Three" });
}
public String Name1 { get; set; }
public String Index { get; set; }
public ObservableCollection<TopGridItem> Items { get; private set; }
public TopGridItem SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
OnPropertyChanged("SelectedItem");
if (_selectedItem != null)
{
_selectedItem.ResetView();
}
}
}
TopGridItem _selectedItem;
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public class TopGridItem : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
public String Name { get; set; }
public ICollectionView MyCollectionView
{
get
{
return _myCollectionView;
}
set
{
_myCollectionView = value;
OnPropertyChanged("MyCollectionView");
}
}
ICollectionView _myCollectionView;
public void ResetView()
{
MyCollectionView = null;
ICollectionView _customerView = CollectionViewSource.GetDefaultView(_collection);
_customerView.SortDescriptions.Add(new SortDescription("Name", ListSortDirection.Ascending));
MyCollectionView = _customerView;
}
ObservableCollection<BottomGridItem> _collection;
public TopGridItem()
{
_collection = new ObservableCollection<BottomGridItem>();
_collection.Add(new BottomGridItem { Name = "bbbbbb" });
_collection.Add(new BottomGridItem { Name = "aaaaa" });
_collection.Add(new BottomGridItem { Name = "aaaaa" });
_collection.Add(new BottomGridItem { Name = "ccccc" });
_collection.Add(new BottomGridItem { Name = "dddddd" });
ResetView();
}
}
public class BottomGridItem
{
public String Name { get; set; }
public String Index { get; set; }
}
}
经过一些调查和尝试,我认为我可以提出至少两个解决方案。
第一个:
public TopGridItem SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
OnPropertyChanged("SelectedItem");
// _dataGrid - link to BottomDataGrid
_dataGrid.Items.SortDescriptions.Add(new SortDescription("Name", ListSortDirection.Ascending));
}
}
第二个:
public TopGridItem SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
OnPropertyChanged("SelectedItem");
if (_selectedItem != null)
_selectedItem.MyCollectionView.Refresh();
}
}
要生成错误,select TopDataGrid
中的任何项目。结果,collection 项将被加载到 BottomDataGrid
中。这个 collection 按照我指定的 Name
属性 排序!然后 select TopDataGrid
中的任何其他项目。结果是 BottomDataGrid
的 ItemsSource
将被重新加载。现在 collection 未排序! collection 看起来和我在代码中指定的一样。此外,如果我用调试器检查 _customerView
我会看到排序的 collection.
我知道我可以将 List
与 OrderBy
和 INotifyPropertyChanged
一起使用来明确命令 UI 更新自身而不是 ObservableCollection
和 ICollectionView
.但我认为这不是正确的做法。
Win 7,.Net 4.0。只需复制和粘贴即可。
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<DataGrid Grid.Row="0"
AutoGenerateColumns="False"
ItemsSource="{Binding Items}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
CanUserAddRows="False"
CanUserDeleteRows="False"
CanUserResizeRows="False"
CanUserSortColumns="False"
SelectionMode="Single"
SelectionUnit="FullRow">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Name}"></DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
<DataGrid Grid.Row="1"
AutoGenerateColumns="False"
ItemsSource="{Binding SelectedItem.MyCollectionView}"
CanUserAddRows="False"
CanUserDeleteRows="False"
CanUserResizeRows="False"
CanUserSortColumns="False"
SelectionMode="Single"
SelectionUnit="FullRow">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Name}"></DataGridTextColumn>
<DataGridTextColumn Binding="{Binding Index}"></DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
<Grid Grid.Row="2">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0"
Text="{Binding Name1}"></TextBox>
<TextBox Grid.Column="1"
Text="{Binding Index}"></TextBox>
</Grid>
</Grid>
代码
public class TopGridItem
{
private ObservableCollection<BottomGridItem> _collection;
public ObservableCollection<BottomGridItem> Collection
{
get { return _collection; }
}
public String Name { get; set; }
public ICollectionView MyCollectionView
{
get
{
ICollectionView _customerView = CollectionViewSource.GetDefaultView(Collection);
_customerView.SortDescriptions.Add(new SortDescription("Name", ListSortDirection.Ascending));
return _customerView;
}
}
public TopGridItem()
{
_collection = new ObservableCollection<BottomGridItem>();
_collection.Add(new BottomGridItem { Name = "bbbbbb" });
_collection.Add(new BottomGridItem { Name = "aaaaa" });
_collection.Add(new BottomGridItem { Name = "aaaaa" });
_collection.Add(new BottomGridItem { Name = "ccccc" });
_collection.Add(new BottomGridItem { Name = "dddddd" });
}
}
public class BottomGridItem
{
public String Name { get; set; }
public String Index { get; set; }
}
/// <summary>
/// Логика взаимодействия для NewWindow.xaml
/// </summary>
public partial class ProgressWindow : INotifyPropertyChanged
{
public TopGridItem _selectedItem;
public String Name1 { get; set; }
public String Index { get; set; }
public ObservableCollection<TopGridItem> Items { get; set; }
public TopGridItem SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
OnPropertyChanged("SelectedItem");
}
}
public ProgressWindow()
{
InitializeComponent();
DataContext = this;
Items = new ObservableCollection<TopGridItem>();
Items.Add(new TopGridItem {Name = "One"});
Items.Add(new TopGridItem {Name = "Two"});
Items.Add(new TopGridItem {Name = "Three"});
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
更新
private void ClearSortDescriptionsOnItemsSourceChange()
{
this.Items.SortDescriptions.Clear();
this._sortingStarted = false;
List<int> descriptionIndices = this.GroupingSortDescriptionIndices;
if (descriptionIndices != null)
descriptionIndices.Clear();
foreach (DataGridColumn dataGridColumn in (Collection<DataGridColumn>) this.Columns)
dataGridColumn.SortDirection = new ListSortDirection?();
}
private static object OnCoerceItemsSourceProperty(DependencyObject d, object baseValue)
{
DataGrid dataGrid = (DataGrid) d;
if (baseValue != dataGrid._cachedItemsSource && dataGrid._cachedItemsSource != null)
dataGrid.ClearSortDescriptionsOnItemsSourceChange();
return baseValue;
}
似乎在 ClearSortDescriptionsOnItemsSourceChange
方法中清除了排序并且不再重新指定。我觉得是这个问题。
有一个 hacky 解决方法 :O)
更改 TopGridItem
class 使其实现 INotifyPropertyChanged
,然后像这样更改 MyCollectionView
属性:
public ICollectionView MyCollectionView
{
get
{
return _myCollectionView;
}
set
{
_myCollectionView = value;
OnPropertyChanged("MyCollectionView");
}
}
ICollectionView _myCollectionView;
向 TopGridItem
添加一个 public 方法,如下所示:
public void ResetView()
{
MyCollectionView = null; // This is the key to making it work
ICollectionView customerView = CollectionViewSource.GetDefaultView(_collection);
customerView.SortDescriptions.Add(new SortDescription("Name", ListSortDirection.Ascending));
MyCollectionView = customerView;
}
这要求 _collection
成为 class 字段而不是仅在构造函数中使用的变量。说到这里:
public TopGridItem()
{
_collection = new ObservableCollection<BottomGridItem>();
_collection.Add(new BottomGridItem { Name = "bbbbbb" });
_collection.Add(new BottomGridItem { Name = "aaaaa" });
_collection.Add(new BottomGridItem { Name = "aaaaa" });
_collection.Add(new BottomGridItem { Name = "ccccc" });
_collection.Add(new BottomGridItem { Name = "dddddd" });
ResetView();
}
注意对 ResetView
的调用。
现在,在 ProgressWindow
的 SelectedItem
属性 setter 中,您需要做的就是在选择更改时调用 ResetView
方法:
public TopGridItem SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
OnPropertyChanged("SelectedItem");
if (_selectedItem != null)
{
_selectedItem.ResetView();
}
}
}
我已经在这里测试过了,它似乎可以工作。如果不是,那么我一定是在某处打错了字,或者可能遗漏了我发布的代码中的某些内容。
代码的工作版本
XAML:
<Window x:Class="WpfApplication4.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="*" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<DataGrid Grid.Row="0"
AutoGenerateColumns="False"
ItemsSource="{Binding Items}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
CanUserAddRows="False"
CanUserDeleteRows="False"
CanUserResizeRows="False"
CanUserSortColumns="False"
SelectionMode="Single"
SelectionUnit="FullRow">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Name}"></DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
<DataGrid Grid.Row="1"
AutoGenerateColumns="False"
ItemsSource="{Binding SelectedItem.MyCollectionView}"
CanUserAddRows="False"
CanUserDeleteRows="False"
CanUserResizeRows="False"
CanUserSortColumns="False"
SelectionMode="Single"
SelectionUnit="FullRow">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Name}"></DataGridTextColumn>
<DataGridTextColumn Binding="{Binding Index}"></DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
<Grid Grid.Row="2">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0"
Text="{Binding Name1}"></TextBox>
<TextBox Grid.Column="1"
Text="{Binding Index}"></TextBox>
</Grid>
</Grid>
</Window>
代码隐藏:
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
using System.Windows.Data;
namespace WpfApplication4
{
public partial class MainWindow : Window, INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
DataContext = this;
Items = new ObservableCollection<TopGridItem>();
Items.Add(new TopGridItem { Name = "One" });
Items.Add(new TopGridItem { Name = "Two" });
Items.Add(new TopGridItem { Name = "Three" });
}
public String Name1 { get; set; }
public String Index { get; set; }
public ObservableCollection<TopGridItem> Items { get; private set; }
public TopGridItem SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
OnPropertyChanged("SelectedItem");
if (_selectedItem != null)
{
_selectedItem.ResetView();
}
}
}
TopGridItem _selectedItem;
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public class TopGridItem : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
public String Name { get; set; }
public ICollectionView MyCollectionView
{
get
{
return _myCollectionView;
}
set
{
_myCollectionView = value;
OnPropertyChanged("MyCollectionView");
}
}
ICollectionView _myCollectionView;
public void ResetView()
{
MyCollectionView = null;
ICollectionView _customerView = CollectionViewSource.GetDefaultView(_collection);
_customerView.SortDescriptions.Add(new SortDescription("Name", ListSortDirection.Ascending));
MyCollectionView = _customerView;
}
ObservableCollection<BottomGridItem> _collection;
public TopGridItem()
{
_collection = new ObservableCollection<BottomGridItem>();
_collection.Add(new BottomGridItem { Name = "bbbbbb" });
_collection.Add(new BottomGridItem { Name = "aaaaa" });
_collection.Add(new BottomGridItem { Name = "aaaaa" });
_collection.Add(new BottomGridItem { Name = "ccccc" });
_collection.Add(new BottomGridItem { Name = "dddddd" });
ResetView();
}
}
public class BottomGridItem
{
public String Name { get; set; }
public String Index { get; set; }
}
}
经过一些调查和尝试,我认为我可以提出至少两个解决方案。
第一个:
public TopGridItem SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
OnPropertyChanged("SelectedItem");
// _dataGrid - link to BottomDataGrid
_dataGrid.Items.SortDescriptions.Add(new SortDescription("Name", ListSortDirection.Ascending));
}
}
第二个:
public TopGridItem SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
OnPropertyChanged("SelectedItem");
if (_selectedItem != null)
_selectedItem.MyCollectionView.Refresh();
}
}