WPFのDataGridのソートを初期化する方法

WPF DataGrid のソート状態を初期化する代表的な方法を整理し、単一列ソートと複数列ソートの両方で使える実装例を示す。

WPF の DataGrid は便利なソート機能を持っていますが、要件によっては「初期状態に戻す(ソートを解除する)」動作を明示的に実装したいことがあります。
本記事では、DataGrid のソート初期化を実現する代表的な方法を整理します。

概要

本記事では、以下の方法で DataGrid のソート初期化を扱います。

前提・対象環境

問題

WPF DataGrid では、業務要件として「現在の並び替え状態を初期状態へ戻す」操作が必要になる場合があります。
このとき、標準操作だけでは意図したタイミングでの初期化を統一しづらいケースがあります。

原因・背景

まず前提として、WPF DataGridShift + 列ヘッダークリック は、標準では複数列ソートの追加です。
つまり、既存ソートに列を積み増すための操作であり、ソート解除のショートカットではありません。

そのため、「初期状態に戻す」にはコードによる制御が必要です。

解決方法

以下の方針で要件に応じて初期化方法を選択します。

実装例

コードで明示的にソートをクリアする

最もシンプルなのは、SortDescriptions と列ヘッダー矢印の両方をクリアする方法です。

using System.Windows.Controls;

public static class DataGridSortHelper
{
    public static void ClearDataGridSort(DataGrid dataGrid)
    {
        if (dataGrid == null) return;

        // データ側のソート条件をクリア
        dataGrid.Items.SortDescriptions.Clear();

        // ヘッダー矢印をクリア
        foreach (var column in dataGrid.Columns)
        {
            column.SortDirection = null;
        }

        // 表示更新
        dataGrid.Items.Refresh();
    }
}

ポイント

3回目のクリックで自動的に初期化する(カスタム挙動)

「昇順 → 降順 → 未ソート」の3状態にしたい場合は、Sorting イベントを使って制御します。
3つの状態を実際に表示すると次のようになります。

3 つの DataGrid を並べた画面。左は Name 列に昇順の矢印が付き名前順、中央は降順の矢印が付き逆順、右は矢印が無くデータ本来の並びになっている。
同じデータに対する 3 状態の表示。未ソート(右)では対象列の SortDescription とヘッダーの矢印がどちらも消える。この例は 1 列だけでソートしているため、解除するとコレクション本来の並びに戻る。複数列でソートしている場合は他の列の条件が残るため、必ずしも元の並びには戻らない。

XAML

<DataGrid x:Name="MyDataGrid"
          Sorting="DataGrid_Sorting" />

この設定により、列ヘッダークリック時の既定ソート処理へ独自ロジックを差し込める。
また、DataGridTemplateColumn などで列ソートを解除対象にする場合は、事前に SortMemberPath を設定しておく必要がある。

C#

using System.ComponentModel;
using System.Linq;
using System.Windows.Controls;

private void DataGrid_Sorting(object sender, DataGridSortingEventArgs e)
{
    if (sender is not DataGrid dataGrid) return;

    if (e.Column.SortDirection == ListSortDirection.Descending)
    {
        e.Handled = true; // 標準処理をキャンセル

        // 対象列の SortDescription のみ削除
        var target = dataGrid.Items.SortDescriptions
            .FirstOrDefault(sd => sd.PropertyName == e.Column.SortMemberPath);

        if (!string.IsNullOrEmpty(target.PropertyName))
        {
            dataGrid.Items.SortDescriptions.Remove(target);
        }

        e.Column.SortDirection = null;
        dataGrid.Items.Refresh();
    }
}

この実装では、降順状態でさらにクリックされた場合のみ対象列のソートを解除し、複数列ソート時でも他列の条件は維持される。

CollectionView を使って ViewModel から初期化する

MVVM 構成では、DataGrid を直接操作せず ICollectionView を使うと管理しやすくなります。

using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows.Data;

public class SampleViewModel
{
    public ObservableCollection<RowItem> Items { get; } = new();
    public ICollectionView ItemsView { get; }

    public SampleViewModel()
    {
        ItemsView = CollectionViewSource.GetDefaultView(Items);
    }

    public void ClearSort()
    {
        ItemsView.SortDescriptions.Clear();
        ItemsView.Refresh();
    }
}

public class RowItem
{
    public string Name { get; set; } = "";
    public int Value { get; set; }
}

SortDescriptionsItemsView 側で管理することで、UI コンポーネントへの依存を減らし、テスト容易性を確保できる。

<DataGrid ItemsSource="{Binding ItemsView}" />

ビューは ItemsView を表示するだけに限定されるため、ソート初期化の責務を ViewModel 側に集約できる。

Behaviorクラスの作成

同じカスタムソート挙動を複数画面で使うなら、Behavior 化が有効です。
以下は Microsoft.Xaml.Behaviors.Wpf を利用する例です。

using Microsoft.Xaml.Behaviors;
using System.ComponentModel;
using System.Linq;
using System.Windows.Controls;

public class TriStateSortBehavior : Behavior<DataGrid>
{
    protected override void OnAttached()
    {
        base.OnAttached();
        AssociatedObject.Sorting += OnSorting;
    }

    protected override void OnDetaching()
    {
        AssociatedObject.Sorting -= OnSorting;
        base.OnDetaching();
    }

    private void OnSorting(object sender, DataGridSortingEventArgs e)
    {
        if (sender is not DataGrid grid) return;

        if (e.Column.SortDirection == ListSortDirection.Descending)
        {
            e.Handled = true;

            var sd = grid.Items.SortDescriptions
                .FirstOrDefault(x => x.PropertyName == e.Column.SortMemberPath);

            if (!string.IsNullOrEmpty(sd.PropertyName))
            {
                grid.Items.SortDescriptions.Remove(sd);
            }

            e.Column.SortDirection = null;
            grid.Items.Refresh();
        }
    }
}

Behavior 化により、同じ三状態ソート解除ロジックを画面ごとに重複実装せず適用できる。

<Window
    xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
    xmlns:local="clr-namespace:YourApp.Behaviors">
    <DataGrid>
        <i:Interaction.Behaviors>
            <local:TriStateSortBehavior />
        </i:Interaction.Behaviors>
    </DataGrid>
</Window>

XAML 側は Behavior を宣言するだけで済むため、利用画面追加時の実装コストを抑制しやすい。

注意点

代替案・比較

方法 メリット デメリット 適するケース
明示クリア(SortDescriptions.Clear + SortDirection = null 実装が単純で導入が速い。 DataGrid 参照が必要で MVVM 純度は下がる。 画面単位で即時に全解除したい場合。
Sorting イベントで三状態制御 UX を「昇順→降順→未ソート」に統一できる。 イベント処理が複雑化しやすく列単位要件の整理が必要。 ヘッダークリックだけで完結する操作性を重視する場合。
ICollectionView で ViewModel 管理 テストしやすく UI 依存を最小化できる。 View と ViewModel の責務分離設計が前提となる。 MVVM を徹底し、コマンド経由で初期化する場合。
Behavior 化 複数画面へ横展開しやすく重複コードを削減できる。 画面差分要件を吸収する拡張ポイント設計が必要。 同一ルールのソート挙動を複数 DataGrid へ適用する場合。

まとめ

WPF DataGrid のソート初期化は、用途に応じて実装方法を選べます。

要件が単純ならヘルパーメソッド、画面横断で使うなら Behavior 化、MVVM 徹底なら CollectionView 中心で設計するのが実践的です。

関連記事