使用 ICollectionView 对 ObservableCollection 进行排序无法正常工作

Sorting ObservableCollection with ICollectionView does't work correctly

要生成错误,select TopDataGrid 中的任何项目。结果,collection 项将被加载到 BottomDataGrid 中。这个 collection 按照我指定的 Name 属性 排序!然后 select TopDataGrid 中的任何其他项目。结果是 BottomDataGridItemsSource 将被重新加载。现在 collection 未排序! collection 看起来和我在代码中指定的一样。此外,如果我用调试器检查 _customerView 我会看到排序的 collection.

我知道我可以将 ListOrderByINotifyPropertyChanged 一起使用来明确命令 UI 更新自身而不是 ObservableCollectionICollectionView.但我认为这不是正确的做法。

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 的调用。

现在,在 ProgressWindowSelectedItem 属性 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();
    }
}