【问题标题】:Sorting ObservableCollection with ICollectionView does't work correctly使用 ICollectionView 对 ObservableCollection 进行排序无法正常工作
【发布时间】:2015-01-29 13:00:10
【问题描述】:

要生成错误,请选择TopDataGrid 中的任何项目。结果,项目集合将被加载到BottomDataGrid。这个集合按我指定的Name 属性排序!然后选择TopDataGrid 中的任何其他项目。结果是 ItemsSourceBottomDataGrid 将被重新加载。现在这个集合是未分类的!该集合看起来就像我在代码中指定的那样。此外,如果我使用调试器检查_customerView,我会看到排序后的集合。

我知道我可以使用 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 方法中排序被清除并且不再重新指定。我想这就是问题所在。

【问题讨论】:

  • 因为您只在 _collection 构造函数(使用 SortDescriptions)中创建 _collection 一次创建 ListCollectionView 的实例(使用 SortDescriptions)并始终在 MyCollectionView 中返回它
  • @dkozl 不起作用。都一样
  • 当用户点击最上面的网格项时,你想让_collection列表排序还是取消排序?
  • @SwDevMan81 显然已排序。现在没有排序
  • @monstr 这似乎是DataGrid 控件的某种问题。如果数据网格被切换为ListBox 之类的东西,我将无法复制该问题。我不知道为什么会这样。你确定要使用DataGrid 作为底部控件吗?

标签: c# wpf observablecollection collectionviewsource icollectionview


【解决方案1】:

经过一些调查和尝试,我认为我至少可以提出两种解决方案。

第一:

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();
    }
}

【讨论】:

    【解决方案2】:

    有一个 hacky 解决方法 :O)

    更改您的TopGridItem 类,使其实现INotifyPropertyChanged,然后更改MyCollectionView 属性,如下所示:

    public ICollectionView MyCollectionView
    {
        get
        {
            return _myCollectionView;
        }
        set
        {
            _myCollectionView = value;
            OnPropertyChanged("MyCollectionView");
        }
    }
    ICollectionView _myCollectionView;
    

    像这样向TopGridItem 添加一个公共方法:

    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 设为类字段,而不是仅在构造函数中使用的变量。说到这里:

    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 属性设置器中做的就是在选择更改时调用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; }
        }
    }
    

    【讨论】:

    • @monstr 我已将完整的工作代码添加到我的帖子底部。这是从我根据您的代码编写的测试应用程序中复制和粘贴的。就像我说的那样,它似乎对我有用。如果它不适合你,那么我不知道还有什么建议,对不起。
    • @Steven Rands,复制粘贴。仍然对我不起作用。集合根本没有排序。我已经找到了更好的解决方案,谢谢 :)
    • 也许另一个 SO 用户可以使用标准 Visual Studio 模板创建一个新的 WPF 项目,复制并粘贴代码,让我知道它是否有效。可能是 .NET Framework 版本问题? (这是在 4.5 中使用 VS2012 测试的)。
    • @Steven Rands,可能是。我正在开发 4.0、VS2010,并且已经遇到过在 4.0 中有效而在 4.5 中无效的场景
    猜你喜欢
    • 2021-03-21
    • 2023-03-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-12-14
    • 1970-01-01
    • 2012-03-29
    • 2018-07-18
    相关资源
    最近更新 更多