タイトルのままですが、WPFのListBoxにバインドしたデータをソートして表示したい!という状況にある前提で話をします。しかも、取得できるデータは目的の形にソートされていないという、最低の状態のところからスタートします。
下準備
とりあえず、表示するデータが単一の値だとつまらないので、適当なクラスをでっちあげます。名前と年齢を持つPersonクラスを下のように定義します。データ取得用のメソッドもPersonクラスのstaticなメソッドとして定義しています。
using System;
using System.Linq;
namespace WpfListBoxSort
{
// 人間
public class Person
{
// 年齢
public int Age { get; set; }
// 名前
public string Name { get; set; }
/// <summary>
/// 引数で渡された人数のPersonオブジェクトを生成して返す。
/// 年齢は、0~99才までのランダムに生成される。
/// </summary>
public static Person[] GetPeople(int count)
{
Random r = new Random();
// 無理やりLINQ使った
return (from val in Enumerable.Range(1, count)
select new Person { Name = val + "太郎", Age = r.Next(100) }).ToArray();
}
}
}
ここまで準備できたら、画面を作りにはいる。画面は、上部にデータ取得のためのボタン。残りの部分いっぱいにListBoxが表示されるといった感じで作ってみた。下記にXAML部分のコードを示す。
<Window x:Class="WpfListBoxSort.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<DockPanel>
<Button DockPanel.Dock="Top" Content="Generate Data" Click="Button_Click" />
<ListBox />
</DockPanel>
</Window>
実行結果は下の通り。
今のところ、取り立てて珍しいことは無い。これで下地が出来たので、ListBoxのデータのソートに入ろうと思う。
ソートするよ
さて、配列なListをWPFのBindingを使ってItemsSourceプロパティにバインドすると、裏ではCollectionViewSourceというクラスが使われてたりする。こいつは、DataTableとDataViewの対応に近い。実データの上に薄くかぶさってソート等の処理をやってくれる。CollectionViewSourceを使うと、指定したプロパティでソートしたり出来る。
今回は、XAMLでCollectionViewSourceを直接定義して使ってみようと思う。
まず、データ表示の大元になるObjectDataProviderの定義をWindow.Resourcesに追加する。
<Window x:Class="WpfListBoxSort.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:WpfListBoxSort="clr-namespace:WpfListBoxSort"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<ObjectDataProvider x:Key="PeopleDataProvider"
IsInitialLoadEnabled="False"
ObjectType="{x:Type WpfListBoxSort:Person}"
MethodName="GetPeople" />
</Window.Resources>
<!-- さっきと一緒なので以下は省略 -->
</Window>
次に、このDataProviderをもとにCollectionViewSourceの定義を追加する。
<!-- CollectionViewSourceの定義 データの元はPeopleDataProvider -->
<CollectionViewSource x:Key="PeopleViewSource" Source="{StaticResource PeopleDataProvider}">
<!-- Nameプロパティで昇順でソート -->
<CollectionViewSource.SortDescriptions>
<ComponentModel:SortDescription PropertyName="Age" Direction="Ascending" />
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
上のXAMLでしているように、CollectionViewSourceのCollectionViewSource.SortDescriptionsにソートの定義を追加していくとソートできる。何故かSortDescriptionsに入れるSortDescriptionはWindowsBaseのSystem.ComponentModelにあるので何処かで「xmlns:ComponentModel="clr-namespace:System.ComponentModel;assembly=WindowsBase"」を定義しないといけないので注意。
CollectionViewSourceが出来たので、ListBoxにバインドする。
<ListBox ItemsSource="{Binding Source={StaticResource PeopleViewSource}}"/>
そして、ボタンのクリックイベントで、DataProviderにデータの取得をお願いします。
private void Button_Click(object sender, RoutedEventArgs e)
{
var random = new Random();
var dataProvider = (ObjectDataProvider) FindResource("PeopleDataProvider");
dataProvider.MethodParameters.Clear();
dataProvider.MethodParameters.Add(random.Next(100));
dataProvider.Refresh();
}
これで、データがソートされて表示されますが・・・!!DataTemplateを定義してないので下のように表示されてがっかりorzしてしまいます。
ということで、DataTemplateの定義を下でやりたいと思います。Window.Resourcesに定義を足す形で書きます。
<DataTemplate DataType="{x:Type WpfListBoxSort:Person}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" />
<TextBlock Text="さん " />
<TextBlock Text="{Binding Age}" />
<TextBlock Text="才" />
</StackPanel>
</DataTemplate>
これでめでたくListBoxにデータがソートされて出力されます。ボタンを押してデータを取り直してもばっちりソートされてる。
めでたしめでたし。