WPFアプリでDataGridコントロールやListViewコントロールに表示されるデータを、プログラムコードの側からソートして表示する方法を解説する。
対象:.NET 4.0以降
WPFには、データのコレクションを表示するためのコントロールが用意されている。DataGridコントロールやListViewコントロールなどがそうだ(いずれもSystem.Windows.Controls名前空間)。そこに表示するデータをソートしておくにはどうしたらよいだろうか? もちろん、与えるデータをソートしておけば可能なのだが、ソート順(昇順/降順)やソート対象の項目を変更するたびにデータを作り直すのは非効率である。CollectionViewSourceクラス(System.Windows.Data名前空間)を利用すれば、元のデータを並べ替えたり削除したりすることなく、表示だけをソート/フィルタリング/グルーピングできる。本稿では、CollectionViewSourceクラスを使って、ソートして表示する方法を解説する。
なお、本稿のプログラミングには、無償のVisual Studio Express 2012 for Windows Desktop(以降、VS 2012)を使用した。Visual Studio 2013でも手順は同じである。
簡単なデータを表示/編集できるプログラムを用意する(次の画像)。画面の左側にはDataGridコントロールを、右側にはListViewコントロールを、画面下部にはデータを追加/編集するための簡単なUIを配置する。
少々長くなるが、以下の手順に従ってコーディングしてほしい。SampleDataクラスの新設、MainWindowクラスへのUIの追加(XAML)、そしてMainWindowクラスのコードビハインドへのコードの追加(C#またはVB)を行う。
まず、Visual StudioでWPFアプリのプロジェクトを新しく作成したら、そこに新しくクラスのファイルを追加し、その名前を「SampleData.cs/.vb」とする。ファイルの内容を次のコードのように書き換える。
using System.Collections.ObjectModel;
using System.ComponentModel;
namespace dotNetTips1082CS //←ここはプロジェクトによって異なる
{
// 表示する個々のデータ(データバインド可能)
public class SampleData : INotifyPropertyChanged
{
// Indexプロパティ
string _index;
public string Index
{
get { return _index; }
set{_index = value; OnPropertyChanged("Index");}
}
// Valueプロパティ
string _value;
public string Value
{
get { return _value; }
set { _value = value; OnPropertyChanged("Value"); }
}
// INotifyPropertyChangedインターフェースの実装
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged(string pName)
{
var handler = this.PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(pName));
}
}
// 表示するデータのコレクション(データバインド可能)
public class SampleDataCollection : ObservableCollection<SampleData>
{
public SampleDataCollection()
{
// 初期データ
this.Add(new SampleData(){ Index="1", Value="Red"});
this.Add(new SampleData(){ Index="2", Value="Green"});
this.Add(new SampleData(){ Index="3", Value="Blue"});
}
}
}
Imports System.ComponentModel
Imports System.Collections.ObjectModel
' 表示する個々のデータ(データバインド可能)
Public Class SampleData
Implements INotifyPropertyChanged
' Indexプロパティ
Private _index As String
Public Property Index As String
Get
Return _index
End Get
Set(value As String)
_index = value : OnPropertyChanged("Index")
End Set
End Property
' Valueプロパティ
Private _value As String
Public Property Value As String
Get
Return _value
End Get
Set(value As String)
_value = value : OnPropertyChanged("Value")
End Set
End Property
' INotifyPropertyChangedインターフェースの実装
Public Event PropertyChanged(sender As Object, e As PropertyChangedEventArgs) _
Implements INotifyPropertyChanged.PropertyChanged
Private Sub OnPropertyChanged(pName As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(pName))
End Sub
End Class
' 表示するデータのコレクション(データバインド可能)
Public Class SampleDataCollection
Inherits ObservableCollection(Of SampleData)
Public Sub New()
' 初期データ
Me.Add(New SampleData() With {.Index = "1", .Value = "Red"})
Me.Add(New SampleData() With {.Index = "2", .Value = "Green"})
Me.Add(New SampleData() With {.Index = "3", .Value = "Blue"})
End Sub
End Class
次に、「MainWindow.xaml」ファイルを開き、ファイルの内容を次のコードのように書き換える。
<Window x:Class="dotNetTips1082CS.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title=".NET TIPS #1082" Height="300" Width="350"
MinHeight="200" MinWidth="300"
Loaded="Window_Loaded"
>
<d:DesignProperties.DataContext>
<SampleDataCollection xmlns="clr-namespace:dotNetTips1082CS" />
<!-- 上の行の名前空間の指定(=dotNetTips1082CS)はプロジェクトによって異なる。
前述のSampleDataCollectionクラスの名前空間にする -->
</d:DesignProperties.DataContext>
<Grid x:Name="RootGrid" Background="LightGray">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<!-- DataGrid(画面左側):双方向バインド(UI上での変更がデータに反映される) -->
<DataGrid x:Name="DataGrid1"
AutoGenerateColumns="False" HorizontalAlignment="Left"
ItemsSource="{Binding DataContext, Mode=TwoWay, RelativeSource={RelativeSource Self}}"
SelectionChanged="DataGrid1_SelectionChanged"
>
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Index}" Header="INDEX" MinWidth="50" />
<DataGridTextColumn Binding="{Binding Value}" Header="VALUE" MinWidth="150" />
</DataGrid.Columns>
</DataGrid>
<!-- ListView(画面右側):一方向バインド(UI上での変更はデータに反映されない) -->
<ListView Grid.Column="1" Margin="10,0,0,0"
ItemsSource="{Binding DataContext, Mode=OneWay, RelativeSource={RelativeSource Self}}"
DisplayMemberPath="Value" />
<!-- 画面下部のデータ編集用UI -->
<StackPanel Grid.Row="1" Grid.ColumnSpan="2" Orientation="Horizontal">
<TextBlock VerticalAlignment="Center">INDEX</TextBlock>
<TextBox x:Name="TextBoxIndex" Width="30" />
<TextBlock VerticalAlignment="Center" Margin="10,0,0,0">VALUE</TextBlock>
<TextBox x:Name="TextBoxValue" Width="70" />
<Button Click="Button_Click" Content="Add/Renew" Margin="10,0,0,0" />
</StackPanel>
</Grid>
</Window>
最後に、「MainWindow.xaml.cs/.vb」ファイルを開き、ファイルの内容を次のコードのように書き換える。
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace dotNetTips1082CS
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
// 表示するデータ
private SampleDataCollection _data = new SampleDataCollection();
// 画面が表示されるとき、データを画面にセットする
private void Window_Loaded(object sender, RoutedEventArgs e)
{
// データをそのままセットする
this.RootGrid.DataContext = _data;
}
// DataGrid1で選択行が移動したら、下端のテキストボックスにその値を反映させる
private void DataGrid1_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var selectedData = this.DataGrid1.SelectedItem as SampleData;
if (selectedData == null)
{
this.TextBoxIndex.Text = string.Empty;
this.TextBoxValue.Text = string.Empty;
}
else
{
this.TextBoxIndex.Text = selectedData.Index;
this.TextBoxValue.Text = selectedData.Value;
}
}
// ボタンがクリックされたら、下端のテキストボックスの内容をデータに反映させる
private void Button_Click(object sender, RoutedEventArgs e)
{
string index = this.TextBoxIndex.Text;
if (string.IsNullOrWhiteSpace(index))
return;
string value = this.TextBoxValue.Text;
var currentData = _data.FirstOrDefault(d => d.Index == index);
if (currentData == null)
_data.Add(new SampleData() { Index = index, Value = value });
else
currentData.Value = value;
}
}
}
Class MainWindow
' 表示するデータ
Private _data As SampleDataCollection = New SampleDataCollection()
' 画面が表示されるとき、データを画面にセットする
Private Sub Window_Loaded(sender As Object, e As RoutedEventArgs)
' データをそのままセットする
Me.RootGrid.DataContext = _data
End Sub
' DataGrid1で選択行が移動したら、下端のテキストボックスにその値を反映させる
Private Sub DataGrid1_SelectionChanged(sender As Object, e As SelectionChangedEventArgs)
Dim selectedData = TryCast(Me.DataGrid1.SelectedItem, SampleData)
If (selectedData Is Nothing) Then
Me.TextBoxIndex.Text = String.Empty
Me.TextBoxValue.Text = String.Empty
Else
Me.TextBoxIndex.Text = selectedData.Index
Me.TextBoxValue.Text = selectedData.Value
End If
End Sub
' ボタンがクリックされたら、下端のテキストボックスの内容をデータに反映させる
Private Sub Button_Click(sender As Object, e As RoutedEventArgs)
Dim index As String = Me.TextBoxIndex.Text
If (String.IsNullOrWhiteSpace(index)) Then
Return
End If
Dim value As String = Me.TextBoxValue.Text
Dim currentData = _data.FirstOrDefault(Function(d) d.Index = index)
If (currentData Is Nothing) Then
_data.Add(New SampleData() With {.Index = index, .Value = value})
Else
currentData.Value = value
End If
End Sub
End Class
以上で、プログラムの準備は完了だ。実行すると、冒頭の画像のようにデータが表示される([VALUE]が[Red]→[Green]→[Blue]の順になっている)。これを[VALUE]の昇順にソートして表示([Blue]→[Green]→[Red])にしたいのだ。
ここでちょっと寄り道をして、DataGridコントロールに備わっているソート機能を見ておこう。
DataGridコントロールのヘッダー部分をクリックすると、その列のデータで昇順/降順に並べ替えることができる(次の画像)。
また、ソートした状態であれば、プログラムからデータを追加したときに、正しくソートされた位置に挿入される(次の画像)。
エンドユーザーが操作してソートできればよいのであれば、上記のプログラムでも十分だろう。ただし、ListViewコントロール(画面の右側)には、この機能はない。また、DataGridコントロールであっても、表示したときに(エンドユーザーの操作なしで)ソートしたいのであれば、やはりプログラムで対応する必要がある。
ソートした状態で表示するには、CollectionViewSourceクラスを使えばよい。
正確には、CollectionViewSourceクラスを使って、元のデータコレクションから既定のビューを取り出し、それをコントロールにバインドする。そして、既定のビューに対して並べ替えを設定するのである。具体的には、次のコードのように、「MainWindow.xaml.cs/.vb」ファイルのWindow_Loadedメソッドを変更する。
// 画面が表示されるとき、データを画面にセットする
private void Window_Loaded(object sender, RoutedEventArgs e)
{
// データをそのままセットする
//this.RootGrid.DataContext = _data;
// 上の行を削除し、以下のように書き換える
// 既定のビューを取り出してセットする
var view = CollectionViewSource.GetDefaultView(_data);
this.RootGrid.DataContext = view;
// 既定のビューにソートを指定する
view.SortDescriptions.Add(
new System.ComponentModel.SortDescription(
"Value",
System.ComponentModel.ListSortDirection.Ascending)
);
// DataGridコントロールのヘッダーにソートの印(三角のマーク)を表示する
this.DataGrid1.Columns[1].SortDirection
= System.ComponentModel.ListSortDirection.Ascending;
}
' 画面が表示されるとき、データを画面にセットする
Private Sub Window_Loaded(sender As Object, e As RoutedEventArgs)
' データをそのままセットする
'Me.RootGrid.DataContext = _data
' 上の行を削除し、以下のように書き換える
' 既定のビューを取り出してセットする
Dim view = CollectionViewSource.GetDefaultView(_data)
Me.RootGrid.DataContext = view
' 既定のビューにソートを指定する
view.SortDescriptions.Add(
New System.ComponentModel.SortDescription(
"Value",
System.ComponentModel.ListSortDirection.Ascending)
)
' DataGridコントロールのヘッダーにソートの印(三角のマーク)を表示する
Me.DataGrid1.Columns(1).SortDirection _
= System.ComponentModel.ListSortDirection.Ascending
End Sub
このようにすれば、プログラムを起動したときからソートされた状態で表示される(次の画像)。同様にして、実行中にプログラムからソート順を切り替えることも可能である。
利用可能バージョン:.NET Framework 4.0以降
カテゴリ:WPF 処理対象:DataGridコントロール、ListViewコントロール
使用ライブラリ:CollectionViewSourceクラス(System.Windows.Data名前空間)
Copyright© Digital Advantage Corp. All Rights Reserved.