CollectionViewSourceを利用して並べ替えを行う

 CollectionViewSourceについてはどこかで説明したような気がしているのですが、記事のタイトル一覧を見たところ説明していないようなので何回かに分けて使い方を紹介していきたいと思います。

 WPFでコレクションを使う場合、コレクションの内容が変更されたこと(追加や削除など)を通知しないといけません。
 もし変更通知を出さなかった場合はViewが更新されず、コレクションの内容が変わっているのに見た目は変化しないことになります。
 コレクションの変更通知を自分でやろうとすると地味にめんどくさいので通常はObservableCollection<T>を利用します。
 ObservableCollection<T>では追加や削除等の変更が起きた場合、自動で変更通知が出るので自分で何かする必要はありません。
 とても便利なObservableCollection<T>なのですが、残念ながら並べ替え機能はついていません。
 Modelのコレクションを並べ替える必要がある場合は自分でなんとかしなくてはいけませんが、Viewに表示している項目だけ並べ替えたい場合(例えばユーザーが見やすいように名前で並べ替える等)はCollectionViewSourceを利用することで簡単に並べ替えが行えます。
 CollectionViewSourceはC#でもXAMLでも作成できます。
 以下のサンプルではCollectionViewSourceのソースのコレクション、C#で作ったCollectionViewSource、XAMLで作ったCollectionViewSourceをそれぞれ表示しています。
 そのうち、C#で作ったCollectionViewSourceは動的な並べ替えができるようにしてあります。
using Livet;
using System.Collections.ObjectModel;

namespace CollectionViewSourceDemo1.Models
{
    public class Model : NotificationObject
    {
        public Model()
        {
            Books = new ObservableCollection<Book>();
            Books.Add(new Book("僕だけがいない街1", "三部けい", 605));       
            Books.Add(new Book("オーバーロード", "丸山くがね", 1080));
            Books.Add(new Book("きんいろモザイク1", "原悠衣", 885));
            Books.Add(new Book("僕だけがいない街2", "三部けい", 606));
            Books.Add(new Book("きんいろモザイク2", "原悠衣", 886));
        }

        public ObservableCollection<Book> Books { get; private set; }
    }

    public class Book : NotificationObject
    {
        public Book(string title, string author, int price)
        {
            Title = title;
            Author = author;
            Price = price;
        }

        /// <summary>
        /// 本のタイトル
        /// </summary>
        public string Title { get; set; }

        /// <summary>
        /// 作者
        /// </summary>
        public string Author { get; set; }

        /// <summary>
        /// 価格
        /// </summary>
        public int Price { get; set; }
    }
}
 このサンプルでは本の情報を表示します。
 Bookクラスは本のタイトルや価格等の情報を保持しているだけです。
 プロパティを変更することはないので変更通知は省略しています。
 ModelクラスのインスタンスではObservableCollection<T>でBookのコレクションを作っているだけです。
using CollectionViewSourceDemo1.Models;
using Livet;
using System.ComponentModel;
using System.Windows.Data;

namespace CollectionViewSourceDemo1.ViewModels
{
    public class MainWindowViewModel : ViewModel
    {
        private Model Model;

        public MainWindowViewModel()
        {
            Model = new Model();

            // ModelコレクションをViewModelコレクションに変換
            Books = ViewModelHelper.CreateReadOnlyDispatcherCollection(
                Model.Books,
                m => new BookViewModel(m),
                DispatcherHelper.UIDispatcher);
            CompositeDisposable.Add(Books);
            
            // CollectionViewSource作成
            BooksViewSource = new CollectionViewSource() { Source = Books };
        }

        public ReadOnlyDispatcherCollection<BookViewModel> Books { get; private set; }

        public CollectionViewSource BooksViewSource { get; private set; }

        /// <summary>
        /// Titleで並べ替える
        /// </summary>
        public void TitleSort()
        {
            BooksViewSource.SortDescriptions.Clear();
            BooksViewSource.SortDescriptions.Add(new SortDescription("Title", ListSortDirection.Ascending));
        }

        /// <summary>
        /// Authorで並べ替える
        /// </summary>
        public void AuthorSort()
        {
            BooksViewSource.SortDescriptions.Clear();
            BooksViewSource.SortDescriptions.Add(new SortDescription("Author", ListSortDirection.Ascending));
        }

        /// <summary>
        /// 価格とタイトルで並べ替える
        /// </summary>
        public void PriceAndTitleSort()
        {
            BooksViewSource.SortDescriptions.Clear();
            BooksViewSource.SortDescriptions.Add(new SortDescription("Price", ListSortDirection.Descending));
            BooksViewSource.SortDescriptions.Add(new SortDescription("Title", ListSortDirection.Ascending));
        }
    }

    public class BookViewModel : ViewModel
    {
        private Book Book;

        public BookViewModel(Book book)
        {
            Book = book;
        }

        public string Title
        {
            get { return Book.Title; }
        }

        public string Author
        {
            get { return Book.Author; }
        }

        public int Price
        {
            get { return Book.Price; }
        }
    }
}
 BookViewModelはその名の通りBookクラスのViewModelクラスです。
 このサンプルのようにプロパティをそのまま渡すだけなら省略することも可能ですが、多くの場合はこのようにViewModelを作成しViewModelのコレクションとしてViewに公開することになります。(なので、サンプルでもViewModelに変換したコードを紹介します)
 MainWindowViewModelのコンストラクタではModelのBookコレクションをBookViewModelコレクションに変換しています。
 ここで使用しているReadOnlyDispatcherCollection<T>やViewModelHelperはLivetの機能で、ソースのコレクションが変更された場合、ViewModelコレクションもそれに合わせて変更されるようにできています。
 ここで作成したViewModelのコレクションをソースにしてCollectionViewSourceも作成しています。  CollectionViewSourceのインスタンスを作成し、Sourceプロパティにコレクションを指定するだけでOKです。

 あとは動的に並べ替えを行う為のメソッドを定義しています。
 CollectionViewSourceで並べ替えを行う場合はSortDescriptionsに並べ替え条件を追加します。
 並べ替え条件(SortDescription)には、並べ替えを行うプロパティ名と並べ替え順(昇順・降順)を指定します。
 並べ替え条件を初期化したい場合はClearメソッドでSortDescriptionsを空にします。
 並べ替え条件が複数ある場合は優先順位が高い順にSortDescriptionsに条件を追加していきます。
 PriceAndTitleSortメソッドでは最初に価格が高い順に並べ替えを行い、同価格があった場合はタイトルで並べ替えを行います。
 ちなみに、並べ替え条件の階層が深い場合は
new SortDescription("Xxx.Title", ListSortDirection.Ascending)
の様にして並べ替えプロパティを指定できます。
<Window x:Class="CollectionViewSourceDemo1.Views.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
        xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
        xmlns:l="http://schemas.livet-mvvm.net/2011/wpf"
        xmlns:v="clr-namespace:CollectionViewSourceDemo1.Views"
        xmlns:vm="clr-namespace:CollectionViewSourceDemo1.ViewModels"
        xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"
        Title="MainWindow" Height="210" Width="925">
    
    <Window.DataContext>
        <vm:MainWindowViewModel/>
    </Window.DataContext>
    
    <i:Interaction.Triggers>
        <!--Windowが閉じたタイミングでViewModelのDisposeメソッドが呼ばれます-->
        <i:EventTrigger EventName="Closed">
            <l:DataContextDisposeAction/>
        </i:EventTrigger>
    </i:Interaction.Triggers>
    
    <Window.Resources>
        <!-- XAMLでCollectionViewSourceを定義し、価格順に並べ替える -->
        <CollectionViewSource x:Key="booksView" Source="{Binding Books}">
            <CollectionViewSource.SortDescriptions>
                <scm:SortDescription PropertyName="Price" Direction="Ascending"/>
            </CollectionViewSource.SortDescriptions>
        </CollectionViewSource>
        
        <DataTemplate DataType="{x:Type vm:BookViewModel}">
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="{Binding Title}" Margin="0,0,10,0"/>
                <TextBlock Text="{Binding Author, StringFormat=作者:{0}}" Margin="0,0,10,0"/>
                <TextBlock Text="{Binding Price, StringFormat=価格:{0}円}"/>
            </StackPanel>
        </DataTemplate>
    </Window.Resources>
    
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        
        <StackPanel Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="3" Orientation="Horizontal">
            <Button Content="タイトルで並べ替える">
                <i:Interaction.Triggers>
                    <i:EventTrigger EventName="Click">
                        <l:LivetCallMethodAction MethodTarget="{Binding}" MethodName="TitleSort"/>
                    </i:EventTrigger>
                </i:Interaction.Triggers>
            </Button>
            <Button Content="作者で並べ替える">
                <i:Interaction.Triggers>
                    <i:EventTrigger EventName="Click">
                        <l:LivetCallMethodAction MethodTarget="{Binding}" MethodName="AuthorSort"/>
                    </i:EventTrigger>
                </i:Interaction.Triggers>
            </Button>
            <Button Content="価格とタイトルで並べ替える">
                <i:Interaction.Triggers>
                    <i:EventTrigger EventName="Click">
                        <l:LivetCallMethodAction MethodTarget="{Binding}" MethodName="PriceAndTitleSort"/>
                    </i:EventTrigger>
                </i:Interaction.Triggers>
            </Button>
        </StackPanel>
        
        <GroupBox Grid.Row="1" Grid.Column="0" Header="元コレクション">
            <ListBox ItemsSource="{Binding Books}"/>
        </GroupBox>
        <GroupBox Grid.Row="1" Grid.Column="1" Header="コードビハインドで作ったCollectionViewSource">
            <ListBox ItemsSource="{Binding BooksViewSource.View}"/>
        </GroupBox>
        <GroupBox Grid.Row="1" Grid.Column="2" Header="XAMLで作ったCollectionViewSource">
            <ListBox ItemsSource="{Binding Source={StaticResource booksView}}"/>
        </GroupBox>
    </Grid>
</Window>
 XAMLでCollectionViewSourceを作ることもできます。
 C#で作成したときと同様にSourceに元となるコレクションを指定するだけです。
 並べ替え条件を定義することもできます。(この場合、並べ替え条件は固定となります)
 SortDescriptionを使うにはSystem.ComponentModel(WindowsBase)の名前空間を追加する必要があります。
 System.ComponentModelでもWindowsBase以外のアセンブリだとSortDescriptionは使えないので注意してください。

 サンプルでは元コレクションとC#で作成したCollectionViewSource、XAMLで作成したCollectionViewSource をそれぞれ表示しています。
 並べ替えボタンを押すことでC#で作成したCollectionViewSourceだけがソートされます。
CollectionViewSource並べ替え
 CollectionViewSourceをC#で作成するかXAMLで作成するかはケースバイケースです。
 私は動的に並べ替え等を行う場合はC#で、並べ替え等の条件が固定だったり、同じソースを元に複数のCollectionViewSourceを作成する必要がある場合等はXAMLで作成しています。
スポンサーサイト

テーマ : プログラミング
ジャンル : コンピュータ

カレンダー
08 | 2015/09 | 10
- - 1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 - - -
全記事表示リンク

全ての記事を表示する

カテゴリ
タグリスト

月別アーカイブ
08  07  06  05  04  03  02  01  12  11  10  09  08  07  06  04  03  02  01  12  11  10  09  08  07  06  05  04  03  02  01  12  11  10  09 
最新記事
リンク
最新コメント
検索フォーム
Amazon