【VB.NET】【WPF】ListView でのソート

MEMO

この記事は「【VB.NET】【WPF】ListView の基本的な使い方」の一部を別記事として独立させたものです。

この記事では下記のようなリストビューのソートについて解説しています。

コラムヘッダーのクリックイベントを用意する

ListView のコラムヘッダーをクリックして ListView のデータをソートするには、まずコラムヘッダーの Click イベントを用意する。

まず、コードに空の Click イベント実行関数を記述する(ListView を含むウィンドウのクラス内に記述)。

Private Sub DayOfTheWeek_Click(sender As Object, e As RoutedEventArgs)

End Sub

Private Sub StartTime_Click(sender As Object, e As RoutedEventArgs)

End Sub

Private Sub EndTime_Click(sender As Object, e As RoutedEventArgs)

End Sub

 

次に、「【VB.NET】【WPF】ListView の基本的な使い方」の記事で示した ListView の xaml に下記の Click イベントを追加します。

<GridViewColumnHeader Content="曜日" Click="DayOfTheWeek_Click"/>
<GridViewColumnHeader Content="開始時刻" Click="StartTime_Click" />
<GridViewColumnHeader Content="終了時刻" Click="EndTime_Click" />

各列のクリックイベントの処理をまとめる

前述のコード(3つのイベント実行関数)から1つのソート処理関数を呼び出すように追記しましょう。次のように書き加えます。

Enum ScheduleSortEnum
    Undefined
    By_DayOfTheWeek
    By_StartTime
    By_EndTime
End Enum

Private Sub DayOfTheWeek_Click(sender As Object, e As RoutedEventArgs)
    'ソート処理関数を呼び出す
    Call ScheduleSort(ScheduleSortEnum.By_DayOfTheWeek)
End Sub

Private Sub StartTime_Click(sender As Object, e As RoutedEventArgs)
    'ソート処理関数を呼び出す
    Call ScheduleSort(ScheduleSortEnum.By_StartTime)
End Sub

Private Sub EndTime_Click(sender As Object, e As RoutedEventArgs)
    'ソート処理関数を呼び出す
    Call ScheduleSort(ScheduleSortEnum.By_EndTime)
End Sub

Friend Sub ScheduleSort(SortBy As ScheduleSortEnum)
    'ここでソート処理を行う
End Sub

クリックイベントの具体的な処理内容を記述する

前述の最後の関数(ScheduleSort)の中身を記述します(下記コードの1-2行目にグローバル変数も2つ追加しています)。

下記コード内の SortClass はまだ定義していないのでエラーになりますが、このあとで定義します。

Dim PreSortDirection As TriState = TriState.UseDefault
Dim PreSortBy As ScheduleSortEnum = ScheduleSortEnum.Undefined

Friend Sub ScheduleSort(SortBy As ScheduleSortEnum)

	'ここで各列がクリックされたときのソート処理を行う

	Dim lv As ListView = Me.ScheduleListView

	If Me.ScheduleListView.Items.Count = 0 Then
	    Return
	End If

	'デフォルトのビューを取得する
	Dim oView As ListCollectionView = CType(CollectionViewSource.GetDefaultView(lv.ItemsSource), ListCollectionView)

	'最初のソートの場合、またはソート対象のコラムが前回と違う場合は前回値を初期化
	If oView.CustomSort Is Nothing OrElse PreSortBy <> SortBy Then
	    PreSortBy = ScheduleSortEnum.Undefined
	    PreSortDirection = TriState.UseDefault
	End If

	'ソート方向(デフォルト・昇順・降順)を決定
	Dim SortDirection As TriState = TriState.UseDefault
	If PreSortDirection = TriState.True Then
	    SortDirection = TriState.False
	ElseIf PreSortDirection = TriState.False Then
	    SortDirection = TriState.UseDefault
	ElseIf PreSortDirection = TriState.UseDefault Then
	    SortDirection = TriState.True
	End If

	'ソート
	If SortDirection = TriState.UseDefault Then
	    oView.CustomSort = Nothing 'ソート解除
	Else
	    oView.CustomSort = New SortClass(SortDirection, SortBy) ' <-- SortClass はこの後で定義する。
	End If

	PreSortBy = SortBy
	PreSortDirection = SortDirection

End Sub

IComparer を実装したソートクラスを定義する

そしてソートの心臓部である SortClass クラスを記述します。

Private Class SortClass
    Implements System.Collections.IComparer

    Dim SortDirection As TriState
    Dim SortBy As ScheduleSortEnum

    Sub New(_SortDirection As TriState, _SortBy As ScheduleSortEnum)
        SortDirection = _SortDirection
        SortBy = _SortBy
    End Sub

    Public Function Compare(_x As Object, _y As Object) As Integer _
         Implements System.Collections.IComparer.Compare

        Dim x As ScheduleItemClass = _x
        Dim y As ScheduleItemClass = _y

        Dim result As Integer

        If SortBy = ScheduleSortEnum.By_DayOfTheWeek Then
            result = x.DayOfTheWeek.CompareTo(y.DayOfTheWeek)
        ElseIf SortBy = ScheduleSortEnum.By_StartTime Then
            result = x.StartTime.CompareTo(y.StartTime)
        ElseIf SortBy = ScheduleSortEnum.By_EndTime Then
            result = x.EndTime.CompareTo(y.EndTime)
        End If

        If result > 0 Then
            If sortDirection = TriState.True Then
                Return 1
            Else
                Return -1
            End If
        ElseIf result < 0 Then
            If sortDirection = TriState.True Then
                Return -1
            Else
                Return 1
            End If
        Else
            Return 0
        End If

    End Function
End Class

これで ListView の各列をクリックするとソートできるはずです。実行して試してみてください。

月火水木金土日を正しくソートする

ソートできましたか?

えっ?「ソートできたけど、曜日の列のソート順が変だよ」って?

・・・そうでした。

「月火水木金土日」を普通にソートしても意図した通りに並んでくれないんですよね。

というわけで、意図した通りに並ぶように、前述の SortClass クラスの中に下記のディクショナリを追記して・・・

Dim YobiDic As New Dictionary(Of String, Integer) From {
    {"月", 0}, 
    {"火", 1},
    {"水", 2},
    {"木", 3},
    {"金", 4},
    {"土", 5},
    {"日", 6}
}

前述の SortClass の21行目を下記のように書き換えましょう。

result = YobiDic(x.DayOfTheWeek).CompareTo(YobiDic(y.DayOfTheWeek))

これで意図した通りに曜日がソートされるようになりましたね。

コラムヘッダーに昇順・降順を示す▲▼を表示する

でもまだ何か足りないものがありますね。

このままだと、列ヘッダをクリックしても、現在どういう状態(昇順? 降順?)でソートされているのか分かりにくい。

というわけで、列ヘッダに昇順・降順を示す▲▼が表示されるようにしてみましょう。

リソースディクショナリに▲▼の記号を定義する

まず Visual Basic の上部メニューで[プロジェクト]>[リソース ディクショナリの追加]を選択してリソースディクショナリを追加します。

リソースディクショナリ(ここでは Dictionary1.xaml とします)に▲▼の記号を表す下記の xaml を記述します。

<!-- ListView のコラムヘッダで使用するソートの昇順・降順マーク -->
<DataTemplate x:Key="HeaderTemplateArrowUp">
<DockPanel>
    <TextBlock HorizontalAlignment="Center" Text="{Binding}"/>
    <Path x:Name="arrow" StrokeThickness = "1" Fill = "gray" Data = "M 6,12 L 14,12 L 10,4 L 6,12"/>
</DockPanel>
</DataTemplate>
<DataTemplate x:Key="HeaderTemplateArrowDown">
<DockPanel>
    <TextBlock HorizontalAlignment="Center" Text="{Binding}"/>
    <Path x:Name="arrow" StrokeThickness = "1" Fill = "gray" Data = "M 6,4 L 10,12 L 14,4 L 6,4"/>
</DockPanel>
</DataTemplate>

リソースディクショナリへの参照を定義する

次に、ウィンドウの xaml に下記(リソースディクショナリへの参照)を記述します(<Window ...> タグの下あたりに記述するのが良いでしょう)。

<Window.Resources>
<ResourceDictionary>
    <ResourceDictionary.MergedDictionaries>
        <!-- 外部リソースの取り込み -->
        <ResourceDictionary Source="Dictionary1.xaml" />
    </ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>

コードの追加

そして記号を表示できるように ScheduleSort 関数を書き換えます。下記のハイライト部分が変更箇所です。

Dim PreSortDirection As TriState = TriState.UseDefault
Dim PreSortBy As ScheduleSortEnum = ScheduleSortEnum.Undefined
Dim PreSortColumnHeader As GridViewColumnHeader = Nothing

Friend Sub ScheduleSort(oColumnHeader As GridViewColumnHeader, SortBy As ScheduleSortEnum)

'ここで各列がクリックされたときのソート処理を行う

Dim lv As ListView = Me.ScheduleListView

If Me.ScheduleListView.Items.Count = 0 Then
    Return
End If

'デフォルトのビューを取得する
Dim oView As ListCollectionView = CType(CollectionViewSource.GetDefaultView(lv.ItemsSource), ListCollectionView)

'最初のソートの場合、またはソート対象のコラムが前回と違う場合は前回値を初期化
If oView.CustomSort Is Nothing OrElse PreSortBy <> SortBy Then
    PreSortBy = ScheduleSortEnum.Undefined
    PreSortDirection = TriState.UseDefault
    If preSortColumnHeader IsNot Nothing Then
     'ソート記号(▼▲)を消す
        preSortColumnHeader.Column.HeaderTemplate = Nothing
    End If
End If

'ソート方向(デフォルト・昇順・降順)を決定
Dim SortDirection As TriState = TriState.UseDefault
If PreSortDirection = TriState.True Then
    SortDirection = TriState.False
ElseIf PreSortDirection = TriState.False Then
    SortDirection = TriState.UseDefault
ElseIf PreSortDirection = TriState.UseDefault Then
    SortDirection = TriState.True
End If

'ソート対象のコラムのソート記号(▼▲)を決定する
If SortDirection = TriState.True Then
    oColumnHeader.Column.HeaderTemplate = DirectCast(FindResource("HeaderTemplateArrowUp"), DataTemplate)
ElseIf SortDirection = TriState.False Then
    oColumnHeader.Column.HeaderTemplate = DirectCast(FindResource("HeaderTemplateArrowDown"), DataTemplate)
Else
    'ソート記号(▼▲)を消す
    oColumnHeader.Column.HeaderTemplate = Nothing
End If

'ソート
If SortDirection = TriState.UseDefault Then
    oView.CustomSort = Nothing 'ソート解除
Else
    oView.CustomSort = New SortClass(SortDirection, SortBy)
End If

PreSortBy = SortBy
PreSortDirection = SortDirection
PreSortColumnHeader = oColumnHeader

End Sub

 

3つのクリックイベントの実行関数も修正して、ScheduleSort の第1引数に sender (実体は GridViewColumnHeader)を指定します。

Private Sub DayOfTheWeek_Click(sender As Object, e As RoutedEventArgs)
    'ソート処理関数を呼び出す
    Call ScheduleSort(sender, ScheduleSortEnum.By_DayOfTheWeek)
End Sub

Private Sub StartTime_Click(sender As Object, e As RoutedEventArgs)
    'ソート処理関数を呼び出す
    Call ScheduleSort(sender, ScheduleSortEnum.By_StartTime)
End Sub

Private Sub EndTime_Click(sender As Object, e As RoutedEventArgs)
    'ソート処理関数を呼び出す
    Call ScheduleSort(sender, ScheduleSortEnum.By_EndTime)
End Sub

 

これで、まぁまぁイイ感じにソートできるようになったと思います。

ソート前

ソート後

 

 

購読する
通知を受け取る対象
guest
0 Comments
Inline Feedbacks
View all comments