DataGridのソートが使いづらい
どうも、コードビハインドに1行でも書いたら死ぬ呪いにかかっているデンパ時計と申します。
皆さんはWPFでいろんなデータを表示するときに一度はDataGridを使ったことがあるかと思います。
上のカラムをぽちっと押すといい感じにソートしてくれるのですが、一度ソートするとなぜか戻せない罠が存在するんですよね。
初心者殺し過ぎて泣けます。
コードビハインドからとか、ViewModel側からとかで元に戻す方法もあるのですが大量に画面作ってるとその一個一個に書いていくのめんどくさいのでUserControlカスタムコントロールとして作成します。
using System.Windows.Controls;
namespace CustomDataGridSample
{
public class CustomDataGrid : DataGrid
{
protected override void OnSorting(DataGridSortingEventArgs eventArgs)
{
if (eventArgs.Column.SortDirection == System.ComponentModel.ListSortDirection.Descending) {
eventArgs.Handled = true;
eventArgs.Column.SortDirection = null;
this.Items.SortDescriptions.Clear();
return;
}
base.OnSorting(eventArgs);
}
}
}
なんてことないですね。DataGridを継承してソート用のOnSortingをオーバーライドするだけです。
カラムを押したときはListSortDirectionがAscending→Descending→Ascending→Descending…と繰り返すので、Descendingの後に元に戻す処理を書いて既存の処理をすっ飛ばすようなコードになってます。
後は適当にxamlとかViewModelを書いて終わりです。
<Window x:Class="CustomDataGridSample.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"
xmlns:local="clr-namespace:CustomDataGridSample"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<DataGrid Grid.Column="0" ItemsSource="{Binding SortTargets}" AutoGenerateColumns="True">
</DataGrid>
<local:CustomDataGrid Grid.Column="1" ItemsSource="{Binding SortTargets}" AutoGenerateColumns="True">
</local:CustomDataGrid>
</Grid>
</Window>
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace CustomDataGridSample
{
public class MainWindowViewModel : INotifyPropertyChanged
{
public class SortTargetEntity
{
public int ID { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public DateTime BirthDay { get; set; }
}
public event PropertyChangedEventHandler PropertyChanged;
private bool SetProperty<T>(ref T field, T value, [CallerMemberName]string name = "")
{
if (EqualityComparer<T>.Default.Equals(field, value)) {
return false;
}
field = value;
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
return true;
}
private ObservableCollection<SortTargetEntity> _sortTargets;
public ObservableCollection<SortTargetEntity> SortTargets
{
get => this._sortTargets;
set => this.SetProperty(ref this._sortTargets, value);
}
public MainWindowViewModel()
{
this._sortTargets = new ObservableCollection<SortTargetEntity>();
for (int i = 0; i < 10; i++) {
this._sortTargets.Add(new SortTargetEntity()
{
ID = i + 1,
Name = $"SampleName{i}",
Age = 10 * i,
BirthDay = DateTime.Now.AddYears(i)
});
}
}
}
}
実際に実行するとこんな感じになります。
左のDataGridがWPF標準のもので一回ソートしたらソートされっぱなし。
右が今回作成したDataGridでカラムを3回押すとソートが元に戻ります。
余談ですがshiftキーを押しながらカラムを押すと複数条件でソートできるみたいですね。
今回作成した コード(github)
(2021/07/11 追記)
添付プロパティの方が良いではないか?とのコメントを頂きました。
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
namespace CustomDataGridSample
{
public class RefreshSorting
{
public static readonly DependencyProperty CanRefreshSorting = DependencyProperty.RegisterAttached(
"CanRefreshProperty",
typeof(bool),
typeof(RefreshSorting),
new PropertyMetadata(OnCanRefreshSprtingChanged));
public static bool GetCanRefreshSorting(DependencyObject dependencyObject)
{
return (bool)dependencyObject.GetValue(CanRefreshSorting);
}
public static void SetCanRefreshSorting(DependencyObject dependencyObject, bool value)
{
dependencyObject.SetValue(CanRefreshSorting, value);
}
private static void OnCanRefreshSprtingChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
if (sender is DataGrid dataGrid) {
if ((bool)e.OldValue == true) {
dataGrid.Sorting -= OnDataGridSorting;
}
if ((bool)e.NewValue == true) {
dataGrid.Sorting += OnDataGridSorting;
}
}
}
private static void OnDataGridSorting(object sender, DataGridSortingEventArgs e)
{
if (sender is DataGrid dataGrid) {
if (e.Column.SortDirection == System.ComponentModel.ListSortDirection.Descending) {
e.Handled = true;
e.Column.SortDirection = null;
dataGrid.Items.SortDescriptions.Clear();
}
}
}
}
}
<Window x:Class="CustomDataGridSample.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"
xmlns:local="clr-namespace:CustomDataGridSample"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<DataGrid Grid.Column="0" ItemsSource="{Binding SortTargets}" AutoGenerateColumns="True">
</DataGrid>
<!--<local:CustomDataGrid Grid.Column="1" ItemsSource="{Binding SortTargets}" AutoGenerateColumns="True">
</local:CustomDataGrid>-->
<!--添付プロパティバージョン-->
<DataGrid Grid.Column="1" ItemsSource="{Binding SortTargets}" AutoGenerateColumns="True" local:RefreshSort.CanRefreshSorting="True">
</DataGrid>
</Grid>
</Window>
スタイル等をDataGirdでいじっている場合は既存のDataGridのまま機能を追加できるのでこちらの方がいいですね。