none
CollectionViewSource をソート・フィルターするには RRS feed

  • 質問

  • 標準テンプレートに同梱されている SampleDataSource を基に、CollectionViewSource のアイテムに対して、並べ替えと絞り込みができるようにしようと考えています。

    CollectionViewSource のバインディング ソースに、SampleDataSource.GetGroupsAsync() の戻り値となる IEnumerable<SampleDataGroup> を指定し、それを ListView などから参照させる部分はできています。

    ページの DefaultViewModel を ObservableCollection にキャストして取り出し、そこからアイテムの Add/Remove を行うのであれば、ViewModel から View へ即座に変更が反映されます(Windows Phone 向けのピボット アプリ テンプレートなどでも、これと同様のコードでアイテムの追加を行っていました)。

    private void AddGroup()
    {
        var groups = this.DefaultViewModel["Items"]
            as ObservableCollection<SampleDataGroup>;
        if (groups == null)
            return;
        var newGroup = new SampleDataGroup(
            // 省略
        );
        groups.Add(newGroup);
    }
    
    private void DeleteGroup()
    {
        var groups = itemGridView.SelectedItem
            as SampleDataGroup;
    
        var groups = this.DefaultViewModel["Items"]
            as ObservableCollection<SampleDataGroup>;
        if (groups == null)
            return;
    
        groups.Remove(item);
    }

    しかしこれを機能させたままアイテムの並べ替えや絞り込みを実装するところでわからなくなってしまいました。ObservableCollection をどこから参照するのか、また ObservableCollection に対してどのような操作を加えるべきなのかの答えが掴めません。

    まず、ObservableCollection にアイテムを追加する前述のテンプレート コードを基に、DefaultViewModel から ObservableCollection を取り出し、それに対して並べ替えを加えるコードを考えました。

    var source = this.DefaultViewModel["Data"]
        as IEnumerable<SampleDataGroup>;
    
    source = source
        .OrderByDescending(g => g.Items.Count())
        .ThenBy(g => g.Title);
    this.DefaultViewModel["Devices"] = source;

    並べ替えを加えた source を DefaultViewModel に再代入することによって、見かけとしては並べ替えが完成していますが、ソート処理後に DefaultViewModel に SampleDataSource.Groups の複製が代入されたことで、SampleDataSource.Groups と CollectionViewSource とのつながりはなくなってしまうため、この複製にアイテムを追加しても SampleDataSource.Groups は変化しません。

    そこで、コレクションを参照する先を SampleDataSource.GetGroupsAsync の戻り値に変更してみました。

    var groups = await SampleDataSource.GetGroupsAsync();
    
    this.DefaultViewModel["Items"] = groups
        .Cast<SampleDataGroup>()
        .OrderByDescending(g => g.Items.Count())
        .ThenByDescending(g => g.ToString());

    アイテムを追加・削除するコードにも同様の修正を加えました。
    private async void AddGroup()
    {
        var groups = await SampleDataSource.GetGroupsAsync();
        if (groups == null)
            return;
        var newGroup = new SampleDataGroup(
            // 省略
        );
        groups.Add(newGroup);
    }
    
    private async void DeleteGroup()
    {
        var groups = await SampleDataSource.GetGroupsAsync();
        if (groups == null)
            return;
    
        var item = itemGridView.SelectedItem as SampleDataGroup;
        groups.Remove(item);
    }

    しかしこの場合、SampleDataSource.Groups へアイテムを追加/削除しても、DefaultViewModel にコレクションが再代入されるまでその変更が反映されません。

    次に考えたのは、CollectionViewSource の見かけだけを操作することでした。CollectionViewSource.View を SampleDataGroup 型にキャストし、これを並べ替えます。

    itemsViewSource.View
        .Cast<SampleDataGroup>()
        .OrderByDescending(g => g.Items.Count())
        .ThenBy(g => g.Title);

    CollectionViewSource.View にソート済みコレクションを設定することで実現できるかと考えましたが、View プロパティは読み取り専用であり、SetValue のようなメソッドも用意されていないようでした。また、View を直接 Model から操作しようとするこの手法は行儀がいいとも思えません。

    Windows 向けのアプリでアイテムのソートなどを実装したものはそう多くないようですが、けして不可能なことではないと思っています。なにか実現への足掛かりでもいただければ幸いです。


    • 編集済み asato2001 2014年12月23日 5:33
    2014年12月23日 5:30

回答

  • 標準テンプレートがどのテンプレートかはっきりしないので、グリッドアプリケーション(XAML)のGroupedItemsPage.xamlを対象として話をします。
    #都合によりVS2012のテンプレートです。

    残念ながらストアアプリでのCollectionViewSourceは機能が限定され、かつ継承不可です。
    また、CollectionViewSource.Viewの型はICollectionViewですがWPFと違って並べ替えとかに関する機能がありません。
    なので、CollectionViweSourceで並べ替えやフィルタを行うことは現時点ではできません。
    #ICollectionViewFactoryでなんとか対応できそうではありますが

    問題の解決方法ですが、テンプレートのままで大きく変更していなければページに表示されるデータは

            protected override void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)
            {
                var sampleDataGroups  = SampleDataSource.GetGroups((String)navigationParameter);
                this.DefaultViewModel["Groups"] =  sampleDataGroups ;
            }

    こんな感じで設定されていますよね。
    このときデータはSampleDataSource.csを見ればわかりますが、ObservableCollection<SampleDataGroup>が得られます。
    asato2001さんも1つ目のコードでキャストして取り出してるので判ると思います。
    そしてページのXAMLは

    <common:LayoutAwarePage x:Name="pageRoot" x:Class="App1.GroupedItemsPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:App1" xmlns:data="using:App1.Data" xmlns:common="using:App1.Common" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"
        DataContext="{Binding DefaultViewModel, RelativeSource={RelativeSource Self}}" >
        <Page.Resources>
            <CollectionViewSource
                x:Name="groupedItemsViewSource"
                Source="{Binding [Groups]}"
                IsSourceGrouped="true"
                ItemsPath="TopItems"
                d:Source="{Binding AllGroups, Source={d:DesignInstance Type=data:SampleDataSource, IsDesignTimeCreatable=True}}"/>
        </Page.Resources>
        <!-- 略 -->
    </common:LayoutAwarePage>

    となっており、ColelctionViewSourceのSourceにそのObservableCollectionがバインドされて、以降は変更されません。
    なのに並べ替えやフィルタを行う時にLINQで複製したものをCollectionViewSourceに渡すと元のObservableCollectionとは別になってしまうために反映されなくなってしまいます。
    であれば、このObservableCollectionのままで並べ替えやフィルタ処理を行えば実現することが可能であることが判るはずです。
    たとえば単純に並びを反転する場合であれば

    var collection = this.DefaultViewModel["Items"] as ObservableCollection<SampleDataGroup>;
    if (collection != null)
    {
        IEnumerable<SampleDataGroup> ie=collection.ToList();//一時退避
        collection.Clear();
        foreach (SampleDataGroup item in ie.Reverse()) //逆順にして取り出す
        {
            collection.Add(item);//逆順に追加
        }
    }

    のようにすれば実現できるという事です。

    同様にしてフィルタ処理なら要らないものをRemoveで抜くという事で対応できます。
    フィルタをやり直す毎に再度データを読み直すのであれば単純なコードだけで実現できるのでお手軽です。
    でも単に抜いてしまうと戻せなくなるので、元々の要素を全てどこかに保持しておいて復元できるようにする必要があるでしょう。
    この場合はVMで保持するとか、元のSampleDataSourceで保持させるかすることになります。

    以上がCollectionViewSource.SourceにバインドされるObservableCollectionの中身を入れ替えることで対応する単純な方法です。

    とはいえ、元のObservableCollectionに対して直接並べ替えやフィルタ処理を行うというのは不必要に元データをいじっていることになってしまうので避けたいところです。
    ですから、元のObservableCollectionには手を付けずに、表示専用のデータを用意して、それを操作する方がよいでしょう。

    というわけで、

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.ComponentModel;
    using System.Collections.ObjectModel;
    namespace App1.Data
    {
        /// <summary>WPFでのCollectionViewSourceの代用クラス</summary>
        class CollectionViewModel<T> : INotifyPropertyChanged
        {
            public ObservableCollection<T> Source
            {
                get { return _Source; }
                set
                {
                    if (_Source != value)
                    {
                        if (_Source != null)
                        {
                            _Source.CollectionChanged -= _Source_CollectionChanged;
                        }
                        _Source = value;
                        if (_Source != null)
                        {
                            _Source.CollectionChanged += _Source_CollectionChanged;
                        }
                        OnPropertyChanged("Source");
                        UpdateView();
                    }
                }
            }
            private ObservableCollection<T> _Source;
    
            void _Source_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
            {
                UpdateView();
            }
    
            public Func<T, T, int> Order
            {
                get
                {
                    return _Order;
                }
                set
                {
                    if (_Order != value)
                    {
                        _Order = value;
                        OnPropertyChanged("Order");
                        UpdateView();
                    }
                }
            }
            private Func<T, T, int> _Order;
    
            public Func<T, bool> Filter
            {
                get
                {
                    return _Filter;
                }
                set
                {
                    if (_Filter != value)
                    {
                        _Filter = value;
                        OnPropertyChanged("Filter");
                        UpdateView();
                    }
                }
            }
            private Func<T, bool> _Filter;
    
            /// <summary>並び替えとフィルタされた結果</summary>
            public IEnumerable<T> View
            {
                get
                {
                    return _View;
                }
            }
            private IEnumerable<T> _View;
    
            private void UpdateView()
            {
                IEnumerable<T> ie = this.Source;
                if (ie != null)
                {
                    if (this._Filter != null)
                    {
                        ie = ie.Where(_Filter);
                    }
                    if (this.Order != null)
                    {
                        ie = ie.OrderBy<T, T>((x) => x, new Comparator<T>(this.Order));
                    }
                }
                this._View = ie;
                OnPropertyChanged("View");
            }
    
            public event PropertyChangedEventHandler PropertyChanged;
            protected virtual void OnPropertyChanged(string name)
            {
                var pc = PropertyChanged;
                if (pc != null)
                {
                    pc(this, new PropertyChangedEventArgs(name));
                }
            }
    
            class Comparator<T> : IComparer<T>
            {
                public Comparator(Func<T, T, int> order)
                {
                    this.order = order;
                }
                Func<T, T, int> order;
    
                public int Compare(T x, T y)
                {
                    return order(x, y);
                }
            }
        }
    }
    <common:LayoutAwarePage x:Name="pageRoot" x:Class="App1.GroupedItemsPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:App1" xmlns:data="using:App1.Data" xmlns:common="using:App1.Common" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"
        DataContext="{Binding DefaultViewModel, RelativeSource={RelativeSource Self}}" >
        <Page.Resources>
            <!-- Sourceを並べ替え・フィルタ処理された結果(View)に変更 -->
            <CollectionViewSource
                x:Name="groupedItemsViewSource"
                Source="{Binding [Groups].View}"
                IsSourceGrouped="true"
                ItemsPath="TopItems"
                d:Source="{Binding AllGroups, Source={d:DesignInstance Type=data:SampleDataSource, IsDesignTimeCreatable=True}}"/>
        </Page.Resources>
        <!-- 略 -->
    </common:LayoutAwarePage>
    using App1.Data;
    
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using Windows.Foundation;
    using Windows.Foundation.Collections;
    using Windows.UI.Xaml;
    using Windows.UI.Xaml.Controls;
    using Windows.UI.Xaml.Controls.Primitives;
    using Windows.UI.Xaml.Data;
    using Windows.UI.Xaml.Input;
    using Windows.UI.Xaml.Media;
    using Windows.UI.Xaml.Navigation;
    using System.Collections.ObjectModel;
    
    namespace App1
    {
        public sealed partial class GroupedItemsPage : App1.Common.LayoutAwarePage
        {
            public GroupedItemsPage()
            {
                this.InitializeComponent();
            }
            protected override void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)
            {
              ObservableCollection<SampleDataGroup> sampleDataGroups 
                  = SampleDataSource.GetGroups((String)navigationParameter)
                  as ObservableCollection<SampleDataGroup>;
    
                //ObservableCollectionをソースにして並べ替えやフィルタ処理できるようにする
                cvm.Source= sampleDataGroups;//
    
                this.DefaultViewModel["Groups"] = cvm;//並び替え・フィルタ処理される結果をバインドさせるため
            }
    
    
            CollectionViewModel<SampleDataGroup> cvm = new CollectionViewModel<SampleDataGroup>();
    
            private void testbutton1_Click(object sender, RoutedEventArgs e)
            {
                //適当なところに配置したボタンをクリックしたらタイトルを逆順にして並べ替え
                this.cvm.Order = (s1, s2) =>{return -(s1.Title.CompareTo(s2.Title));};
            }
    
            private void testbutton2_Click(object sender, RoutedEventArgs e)
            {
                //適当なところに配置したボタンをクリックしたらタイトル文字でフィルタ
                if (this.cvm.Filter == null)
                {
                    this.cvm.Filter = (s) => s.Title.Contains("3") || s.Title.Contains("5");
                }
                else
                {
                    this.cvm.Filter = null;
                }
            }
    
            private void testbutton3_Click(object sender, RoutedEventArgs e)
            {
                //適当なところに配置したボタンをクリックしたらグループ追加
                var now = DateTime.Now.ToString("mm:ss.fff");
                SampleDataGroup group = new SampleDataGroup(now, "Title"+now, "Sub", "", "description");
                cvm.Source.Add(group);
            }
            void Header_Click(object sender, RoutedEventArgs e)
            {
                var group = (sender as FrameworkElement).DataContext;
                this.Frame.Navigate(typeof(GroupDetailPage), ((SampleDataGroup)group).UniqueId);
            }
    
            void ItemView_ItemClick(object sender, ItemClickEventArgs e)
            {
                var itemId = ((SampleDataItem)e.ClickedItem).UniqueId;
                this.Frame.Navigate(typeof(ItemDetailPage), itemId);
            }
        }
    }

    のような感じにして、元のObservableCollectionは残したままで並べ替えやフィルタをしてみてはどうでしょうか。

    追記
    GitHubに全体を置きました。 Zip
    ちょっとコード変わってます。


    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    • 編集済み gekkaMVP 2014年12月24日 3:40 GitHubのリンク追加と、コードをちょっと追加
    • 回答としてマーク asato2001 2014年12月25日 2:35
    • 回答としてマークされていない asato2001 2014年12月25日 4:49
    • 回答の候補に設定 星 睦美 2014年12月25日 5:24
    • 回答としてマーク asato2001 2014年12月25日 6:47
    2014年12月23日 18:16

すべての返信

  • ストアアプリは一度教育を受けに行って以来、実戦を離れていますので詳しくないのですが、SortDescription等を使えばいいのではないでしょうか?外していたらごめんなさい。

     CollectionViewSourceを使ったグルーピングあれこれ【その1】
    http://d.hatena.ne.jp/trapemiya/20120803/1343963471


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/

    2014年12月23日 13:15
  • 標準テンプレートがどのテンプレートかはっきりしないので、グリッドアプリケーション(XAML)のGroupedItemsPage.xamlを対象として話をします。
    #都合によりVS2012のテンプレートです。

    残念ながらストアアプリでのCollectionViewSourceは機能が限定され、かつ継承不可です。
    また、CollectionViewSource.Viewの型はICollectionViewですがWPFと違って並べ替えとかに関する機能がありません。
    なので、CollectionViweSourceで並べ替えやフィルタを行うことは現時点ではできません。
    #ICollectionViewFactoryでなんとか対応できそうではありますが

    問題の解決方法ですが、テンプレートのままで大きく変更していなければページに表示されるデータは

            protected override void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)
            {
                var sampleDataGroups  = SampleDataSource.GetGroups((String)navigationParameter);
                this.DefaultViewModel["Groups"] =  sampleDataGroups ;
            }

    こんな感じで設定されていますよね。
    このときデータはSampleDataSource.csを見ればわかりますが、ObservableCollection<SampleDataGroup>が得られます。
    asato2001さんも1つ目のコードでキャストして取り出してるので判ると思います。
    そしてページのXAMLは

    <common:LayoutAwarePage x:Name="pageRoot" x:Class="App1.GroupedItemsPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:App1" xmlns:data="using:App1.Data" xmlns:common="using:App1.Common" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"
        DataContext="{Binding DefaultViewModel, RelativeSource={RelativeSource Self}}" >
        <Page.Resources>
            <CollectionViewSource
                x:Name="groupedItemsViewSource"
                Source="{Binding [Groups]}"
                IsSourceGrouped="true"
                ItemsPath="TopItems"
                d:Source="{Binding AllGroups, Source={d:DesignInstance Type=data:SampleDataSource, IsDesignTimeCreatable=True}}"/>
        </Page.Resources>
        <!-- 略 -->
    </common:LayoutAwarePage>

    となっており、ColelctionViewSourceのSourceにそのObservableCollectionがバインドされて、以降は変更されません。
    なのに並べ替えやフィルタを行う時にLINQで複製したものをCollectionViewSourceに渡すと元のObservableCollectionとは別になってしまうために反映されなくなってしまいます。
    であれば、このObservableCollectionのままで並べ替えやフィルタ処理を行えば実現することが可能であることが判るはずです。
    たとえば単純に並びを反転する場合であれば

    var collection = this.DefaultViewModel["Items"] as ObservableCollection<SampleDataGroup>;
    if (collection != null)
    {
        IEnumerable<SampleDataGroup> ie=collection.ToList();//一時退避
        collection.Clear();
        foreach (SampleDataGroup item in ie.Reverse()) //逆順にして取り出す
        {
            collection.Add(item);//逆順に追加
        }
    }

    のようにすれば実現できるという事です。

    同様にしてフィルタ処理なら要らないものをRemoveで抜くという事で対応できます。
    フィルタをやり直す毎に再度データを読み直すのであれば単純なコードだけで実現できるのでお手軽です。
    でも単に抜いてしまうと戻せなくなるので、元々の要素を全てどこかに保持しておいて復元できるようにする必要があるでしょう。
    この場合はVMで保持するとか、元のSampleDataSourceで保持させるかすることになります。

    以上がCollectionViewSource.SourceにバインドされるObservableCollectionの中身を入れ替えることで対応する単純な方法です。

    とはいえ、元のObservableCollectionに対して直接並べ替えやフィルタ処理を行うというのは不必要に元データをいじっていることになってしまうので避けたいところです。
    ですから、元のObservableCollectionには手を付けずに、表示専用のデータを用意して、それを操作する方がよいでしょう。

    というわけで、

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.ComponentModel;
    using System.Collections.ObjectModel;
    namespace App1.Data
    {
        /// <summary>WPFでのCollectionViewSourceの代用クラス</summary>
        class CollectionViewModel<T> : INotifyPropertyChanged
        {
            public ObservableCollection<T> Source
            {
                get { return _Source; }
                set
                {
                    if (_Source != value)
                    {
                        if (_Source != null)
                        {
                            _Source.CollectionChanged -= _Source_CollectionChanged;
                        }
                        _Source = value;
                        if (_Source != null)
                        {
                            _Source.CollectionChanged += _Source_CollectionChanged;
                        }
                        OnPropertyChanged("Source");
                        UpdateView();
                    }
                }
            }
            private ObservableCollection<T> _Source;
    
            void _Source_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
            {
                UpdateView();
            }
    
            public Func<T, T, int> Order
            {
                get
                {
                    return _Order;
                }
                set
                {
                    if (_Order != value)
                    {
                        _Order = value;
                        OnPropertyChanged("Order");
                        UpdateView();
                    }
                }
            }
            private Func<T, T, int> _Order;
    
            public Func<T, bool> Filter
            {
                get
                {
                    return _Filter;
                }
                set
                {
                    if (_Filter != value)
                    {
                        _Filter = value;
                        OnPropertyChanged("Filter");
                        UpdateView();
                    }
                }
            }
            private Func<T, bool> _Filter;
    
            /// <summary>並び替えとフィルタされた結果</summary>
            public IEnumerable<T> View
            {
                get
                {
                    return _View;
                }
            }
            private IEnumerable<T> _View;
    
            private void UpdateView()
            {
                IEnumerable<T> ie = this.Source;
                if (ie != null)
                {
                    if (this._Filter != null)
                    {
                        ie = ie.Where(_Filter);
                    }
                    if (this.Order != null)
                    {
                        ie = ie.OrderBy<T, T>((x) => x, new Comparator<T>(this.Order));
                    }
                }
                this._View = ie;
                OnPropertyChanged("View");
            }
    
            public event PropertyChangedEventHandler PropertyChanged;
            protected virtual void OnPropertyChanged(string name)
            {
                var pc = PropertyChanged;
                if (pc != null)
                {
                    pc(this, new PropertyChangedEventArgs(name));
                }
            }
    
            class Comparator<T> : IComparer<T>
            {
                public Comparator(Func<T, T, int> order)
                {
                    this.order = order;
                }
                Func<T, T, int> order;
    
                public int Compare(T x, T y)
                {
                    return order(x, y);
                }
            }
        }
    }
    <common:LayoutAwarePage x:Name="pageRoot" x:Class="App1.GroupedItemsPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:App1" xmlns:data="using:App1.Data" xmlns:common="using:App1.Common" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"
        DataContext="{Binding DefaultViewModel, RelativeSource={RelativeSource Self}}" >
        <Page.Resources>
            <!-- Sourceを並べ替え・フィルタ処理された結果(View)に変更 -->
            <CollectionViewSource
                x:Name="groupedItemsViewSource"
                Source="{Binding [Groups].View}"
                IsSourceGrouped="true"
                ItemsPath="TopItems"
                d:Source="{Binding AllGroups, Source={d:DesignInstance Type=data:SampleDataSource, IsDesignTimeCreatable=True}}"/>
        </Page.Resources>
        <!-- 略 -->
    </common:LayoutAwarePage>
    using App1.Data;
    
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using Windows.Foundation;
    using Windows.Foundation.Collections;
    using Windows.UI.Xaml;
    using Windows.UI.Xaml.Controls;
    using Windows.UI.Xaml.Controls.Primitives;
    using Windows.UI.Xaml.Data;
    using Windows.UI.Xaml.Input;
    using Windows.UI.Xaml.Media;
    using Windows.UI.Xaml.Navigation;
    using System.Collections.ObjectModel;
    
    namespace App1
    {
        public sealed partial class GroupedItemsPage : App1.Common.LayoutAwarePage
        {
            public GroupedItemsPage()
            {
                this.InitializeComponent();
            }
            protected override void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)
            {
              ObservableCollection<SampleDataGroup> sampleDataGroups 
                  = SampleDataSource.GetGroups((String)navigationParameter)
                  as ObservableCollection<SampleDataGroup>;
    
                //ObservableCollectionをソースにして並べ替えやフィルタ処理できるようにする
                cvm.Source= sampleDataGroups;//
    
                this.DefaultViewModel["Groups"] = cvm;//並び替え・フィルタ処理される結果をバインドさせるため
            }
    
    
            CollectionViewModel<SampleDataGroup> cvm = new CollectionViewModel<SampleDataGroup>();
    
            private void testbutton1_Click(object sender, RoutedEventArgs e)
            {
                //適当なところに配置したボタンをクリックしたらタイトルを逆順にして並べ替え
                this.cvm.Order = (s1, s2) =>{return -(s1.Title.CompareTo(s2.Title));};
            }
    
            private void testbutton2_Click(object sender, RoutedEventArgs e)
            {
                //適当なところに配置したボタンをクリックしたらタイトル文字でフィルタ
                if (this.cvm.Filter == null)
                {
                    this.cvm.Filter = (s) => s.Title.Contains("3") || s.Title.Contains("5");
                }
                else
                {
                    this.cvm.Filter = null;
                }
            }
    
            private void testbutton3_Click(object sender, RoutedEventArgs e)
            {
                //適当なところに配置したボタンをクリックしたらグループ追加
                var now = DateTime.Now.ToString("mm:ss.fff");
                SampleDataGroup group = new SampleDataGroup(now, "Title"+now, "Sub", "", "description");
                cvm.Source.Add(group);
            }
            void Header_Click(object sender, RoutedEventArgs e)
            {
                var group = (sender as FrameworkElement).DataContext;
                this.Frame.Navigate(typeof(GroupDetailPage), ((SampleDataGroup)group).UniqueId);
            }
    
            void ItemView_ItemClick(object sender, ItemClickEventArgs e)
            {
                var itemId = ((SampleDataItem)e.ClickedItem).UniqueId;
                this.Frame.Navigate(typeof(ItemDetailPage), itemId);
            }
        }
    }

    のような感じにして、元のObservableCollectionは残したままで並べ替えやフィルタをしてみてはどうでしょうか。

    追記
    GitHubに全体を置きました。 Zip
    ちょっとコード変わってます。


    個別に明示されていない限りgekkaがフォーラムに投稿したコードにはフォーラム使用条件に基づき「MICROSOFT LIMITED PUBLIC LICENSE」が適用されます。(かなり自由に使ってOK!)

    • 編集済み gekkaMVP 2014年12月24日 3:40 GitHubのリンク追加と、コードをちょっと追加
    • 回答としてマーク asato2001 2014年12月25日 2:35
    • 回答としてマークされていない asato2001 2014年12月25日 4:49
    • 回答の候補に設定 星 睦美 2014年12月25日 5:24
    • 回答としてマーク asato2001 2014年12月25日 6:47
    2014年12月23日 18:16
  • なるほど、DefaultViewModel を上書きせずに、ObservableCollection を一度クリアしたのち加工したデータ順で Add していく、というのも考えてはいましたが、元本となるデータを頻繁に書き換えるのはどうかと思っていました。View に渡すためのコレクションとは別に、データ全体を保持するコレクションを用意すればよかったのですね。

    これでなんとかできそうです。ありがとうございました。

    2014年12月25日 2:35
  • 実際にこのコードを試して、おおむねこの方法が正攻法であると感じていますが、一度ソートやフィルターを行ったのちにアイテムの Add/Remove を行うと、CollectionView がコレクションを再読み込みすることで、全体に書き換えが発生してしまうことになります。

    Add の場合はそれでもいいのですが、コレクション全体の書き換えが行われると CollectionView のスクロール位置も初期化されてしまうので、総数の多いコレクションを表示させている際の Remove 操作では違和感が出てきます。

    そこで、_View を IEnumerable から ObservableCollection へ変更し、_Source_CollectionChanged で UpdateView() を行わずに _View にアイテムを追加/削除するようにしました。

    void _Source_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
        if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
            foreach (T item in e.NewItems)
                View.Add(item);
    
        if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Remove)
            foreach (T item in e.OldItems)
                View.Remove(item);
    }
    public ObservableCollection<T> View
    {
        get { return _View; }
    }
    private ObservableCollection<T> _View;
    
    private void UpdateView()
    {
        IEnumerable<T> ie = this.Source;
        if (ie != null)
        {
            if (this._Filter != null)
            {
                ie = ie.Where(_Filter);
            }
            if (this._Order != null)
            {
                ie = ie.OrderBy<T, T>((x) => x, new Comparator<T>(this.Order));
            }
            this._View = new ObservableCollection<T>(ie);
            OnPropertyChanged("View");
        }
    }
    2014年12月25日 6:47