C#内联排序ObservableCollection不更新数据绑定。

3

我有一个ViewModel,其中包含一个绑定到View中控件的ObservableCollection<CustomKeyGroup<CustomItem>>属性。问题在于,我想按CustomKeyGroup<T>中的属性对此集合进行排序,而不需要设置ObservableCollection<...>对象属性(即内联地对集合进行排序):

public class MainViewModel : ViewModelBase {
    ... // data service etc code

    private ObservableCollection<CustomKeyGroup<CustomItem>> _items = new ObservableCollection<CustomKeyGroup<CustomItem>>();
    public ObservableCollection<CustomKeyGroup<CustomItem>> Items
    {
        get
        {
            return _items;
        }
        set
        {
            _items = value;
            RaisePropertyChanged("Items");
        }
    }

    public void Sort(string _orderBy = null, bool _descending = true) {
        if (string.IsNullOrEmpty(_orderBy) || this.Items.Count == 0) {
            return;
        }

        var test = this.Items.ToList();

        // bubble sort
        try {
            for (int i = test.Count - 1; i >= 0; i--) {
                for (int j = 1; j <= i; j++) {
                    CustomKeyGroup<CustomItem> o1 = test[j - 1];
                    CustomKeyGroup<CustomItem> o2 = test[j];
                    bool move = false;

                    var order = typeof(CustomKeyGroup<CustomItem>).GetProperty(orderBy);
                    var t = order.GetValue(o1);
                    var t2 = order.GetValue(o2);

                    // sort comparisons depending on property
                    if (_descending) { // ascending
                        if (t.GetType() == typeof(int)) { // descending and int property
                            if ((int)t < (int)t2) {
                                move = true;
                            }
                        } else { // descending and string property
                            if (t.ToString().CompareTo(t2.ToString()) > 0) {
                                move = true;
                            }
                        }
                    } else { // ascending
                        if (t.GetType() == typeof(int)) { // ascending and int property
                            if ((int)t > (int)t2) {
                                move = true;
                            }
                        } else { // ascending and string property
                            if (t.ToString().CompareTo(t2.ToString()) < 0) {
                                move = true;
                            }
                        }
                    }

                    // swap elements
                    if (move) {
                        //this.Items.Move(j - 1, j); // "inline"

                        test[j] = o1;
                        test[j - 1] = o2;
                    }
                }
            }
            // set property to raise property changed event
            this.Items = new ObservableCollection<CustomKeyGroup<CustomItem>>(test);
        } catch (Exception) {
            Debug.WriteLine("Sorting error");
        }

        //RaisePropertyChanged("Items"); // "inline sort" raise property changed to update Data binding

        Debug.WriteLine("Sorted complete");
    }

    ... // get data from service, etc.

从上面的代码中可以看出,尝试的内联排序被注释掉了(因为它们不会更新数据绑定到它的控件),而手动设置Items则保留下来了(虽然可以工作,但如果你滚动控件并进行排序,它将带你回到顶部 - 这是不可取的!)。
有人知道如何使用内联排序选项更新视图/控件吗?我也尝试过手动引发RaisePropertyChanged事件(在ObservableObject中指定,使用MVVMLight Toolkit),但没有成功。
注意:在try-catch的结尾处设置断点可以发现,ObservableCollection<...>确实已经排序,但更改只是不反映在View中!更奇怪的是,控件(LongListSelector)具有绑定到CustomKeyGroup<T>的另一个属性的JumpList,并且它成功地立即更新了!如果我点击JumpList中的任何一个项目,则View正确地更新自己,显示排序后的项目......然后我想到在排序后设置ViewDataContext,但这也无法解决问题。
谢谢。

在WPF中对集合进行排序可能会出现问题。请参见此处:https://dev59.com/WnI-5IYBdhLWcg3wQV8Z?rq=1 - piofusco
感谢您的回复。我认为我的问题与WP / Silverlight有关,因为我能够完美地对ObservableCollection进行排序,但在XAML绑定方面出现了问题。在集合排序后,它(在此情况下是LLS控件)不会更新(在排序后使用断点检查集合会发现已经被排序)。 - Travis Liew
你说得很对。除非你将ObservableCollection设置为新排序的ObservableCollection,否则更改不会被标记,因此视图不会得到更新。 - piofusco
我明白了。这是一个相当奇怪的问题,不是吗?我本来以为通过更改集合(删除某些元素)至少会更新视图(通过在集合上引发NotifyCollectionChanged),但显然并非如此。它只是从视图中删除元素,而不更新顺序!我想这在ObervableCollection的实现中是有道理的,因为Move会引发CollectionChanged - Travis Liew
确实。你不能改变ObservableCollection的任何部分并自动触发更改,这是非常违反直觉的。然而,针对WPF的每一个缺陷,都有第三方库来填补它。 - piofusco
1个回答

1

在此添加我的答案。

根据原帖的评论,@piofusco指出当ObservableCollection仅被排序时,视图不会更新。即使手动更改集合(因此引发NotifyPropertyChangedNotifyCollectionChanged),也不会更新它。

再搜索一下,我决定可以利用CollectionViewSource,它会为我进行排序-而不更改集合本身(因此允许控件保留其当前滚动位置)。要使其工作,基本上需要在ViewModel中添加一个新属性,类型为CollectionViewSource,添加一个SortDescription,设置其Source并直接绑定到该属性(而不是原始的ObservableCollection):

ViewModel中:

private CollectionViewSource _sortedCollection = new CollectionViewSource();
public CollectionViewSource SortedCollection {
    get {
        _sortedCollection.Source = this.Items; // Set source to our original ObservableCollection
        return _sortedCollection;
    }
    set {
        if (value != _sortedCollection) {
            _sortedCollection = value;
            RaiseNotifyPropertyChanged("SortedCollection"); // MVVMLight ObservableObject
        }
    }
}

查看XAML(注意绑定到Property.View):

<ListBox ItemsSource="{Binding SortedCollection.View}" ... />

在您的视图代码后台,如果您有一个排序按钮:
ViewModel _vm = this.DataContext as ViewModel;
viewModel.SortedCollection.SortDescriptions.Clear(); // Clear all 
viewModel.SortedCollection.SortDescriptions.Add(new SortDescription("PropertyName", ListSortDirection.Descending)); // Sort descending by "PropertyName"

瞧!你的排序集合应该会立即在视图中更新!更好的是,它保留了我们的ObservableCollection功能,因此对ObservableCollection中对象的任何更新都将引发NotifyPropertyChangedNotifyCollectionChanged处理程序,从而更新视图(允许同时保留当前滚动位置的排序和对象更新)!

注意:对于那些使用LongListSelector控件的人,我无法使其正常工作。通过进一步的互联网搜索,我找到了this post这篇文章,其中讨论了为什么LLS不能绑定到CollectionViewSource.View而需要进行一些修改。所以我最终使用了ListBox控件。您可以在here阅读有关一些差异的内容。但对于我的任务来说,ListBox就足够了。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接