Posted at

[WPF]ExcelのようにDataGridのセルを数値に応じて色付けする


[WPF]ExcelのようにDataGridのセルを数値に応じて色付けする


はじめに

WPFでDataGridの各セルを数値に応じて色付けする方法についてまとめます。

Excelで言うところの条件付き書式ルールのような表示です。

下記画像はイメージです。

excel_rule.png


全体構成

以下の流れで説明します。


  1. ViewModelに二次元配列を用意する

  2. DataGridを継承した自作コントロールを定義する

  3. 自作コントロールに二次元配列をバインドする

  4. バインドした二次元配列をConverterで加工する


1.ViewModelに二次元配列を用意する

各セルに表示する情報と色をまとめたクラスを作成し、二次元配列として定義します。

今回は色指定をViewModelで行いましたが、色指定のルールが単一であれば、Viewに色管理を任せた方がスッキリすると思います。


セルの表示情報クラス

class ColoredObject

{
public object Object { get; }
public Brush Foreground { get; }
public Brush Background { get; }

public ColoredObject(int value, int max)
{
Object = value;
var b = (byte)Math.Min(value * 255 / max, 0xff);
var f = (byte)~b;
Background = new SolidColorBrush(Color.FromRgb(b, b, 0x00));
Foreground = new SolidColorBrush(Color.FromRgb(f, f, f));
}
}


ViewModelの2次元配列

初期値のGetSortedData()では、乱数により行列数を決めて、1から順にセル値を設定しています。(割愛)

class MainWindowViewModel : BindableBase

{
private ColoredObject[,] _Array2d = GetSortedData();
public ColoredObject[,] Array2d
{
get => _Array2d;
private set => SetProperty(ref _Array2d, value);
}
}


2.DataGridを継承した自作コントロールを定義する

ItemsSourceの更新に合わせてセル色も更新するため、DataGridを継承したコントロールを定義し、OnItemsSourceChanged() をoverrideします。

この辺りのBindingはxamlに押し込みたかったのですが、今の私には何ともできませんでした… スマートな実装あればご教授下さいませ。

public class ColoredDataGrid : DataGrid

{
protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
{
base.OnItemsSourceChanged(oldValue, newValue);

if (!(newValue is IEnumerable<ColoredObjectRow> newItems)) return;

var col = newItems.First().ItemsSource.Count;
this.Columns.Clear();
for (int c = 0; c < col; c++)
{
var bindingTarget = $"{nameof(ColoredObjectRow.ItemsSource)}[{c}].";

var style = new Style(typeof(TextBlock));

style.Setters.Add(new Setter(TextBlock.BackgroundProperty,
new Binding($"{bindingTarget}{nameof(ColoredObject.Background)}") { Mode = BindingMode.OneTime }));

style.Setters.Add(new Setter(TextBlock.ForegroundProperty,
new Binding($"{bindingTarget}{nameof(ColoredObject.Foreground)}") { Mode = BindingMode.OneTime }));

this.Columns.Add(new DataGridTextColumn()
{
Binding = new Binding($"{bindingTarget}{nameof(ColoredObject.Object)}") { Mode = BindingMode.OneTime },
ElementStyle = style,
});
}
}
}


3.自作コントロールに二次元配列をバインドする

自作コントロールのItemsSourceにViewModelの二次元配列をBindingします。(Converterは後述)

ポイントは『AutoGenerateColumns="False"』で、Column追加をオートに任せず、コードビハインドにより自力で対応しています。

その他諸々のプロパティはええ感じに変更して下さい。


<v:ColoredDataGrid SnapsToDevicePixels="True"
IsReadOnly="True"
CanUserReorderColumns="False"
CanUserSortColumns="False"
CanUserResizeRows="False"
CanUserResizeColumns="False"
SelectionMode="Extended"
SelectionUnit="Cell"
HeadersVisibility="None"
AutoGenerateColumns="False"
ItemsSource="{Binding Array2d, Converter={StaticResource ColoredObjectBindingConverter}, Mode=OneWay}" />


4.バインドした二次元配列をConverterで加工する

自作コントロールでデータを扱うため、二次元配列を行単位にまとめます。


バインドデータの加工クラス

class ColoredObjectRow

{
public IReadOnlyList<ColoredObject> ItemsSource { get; }

public ColoredObjectRow(int r, ColoredObject[,] source)
{
int colLength = source.GetLength(1);
var items = new List<ColoredObject>(colLength);
for (var c = 0; c < items.Capacity; c++)
items.Add(source[r, c]);
ItemsSource = items;
}
}


コンバータ

class ColoredObjectBindingConverter : IValueConverter

{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is ColoredObject[,] data)
return ConvertColoredObjectRows(data);
return null;
}

private static IReadOnlyList<ColoredObjectRow> ConvertColoredObjectRows(ColoredObject[,] source)
{
var rowLength = source.GetLength(0);
if (rowLength == 0) throw new ArgumentException(nameof(rowLength));

var columnLength = source.GetLength(1);
if (columnLength == 0) throw new ArgumentException(nameof(columnLength));

var rows = new List<ColoredObjectRow>(rowLength);
for (var r = 0; r < rows.Capacity; r++)
rows.Add(new ColoredObjectRow(r, source));
return rows;
}
}

対応は以上です。


実行結果

Reflashボタンをクリックすると行列数がランダムに変化し、セルの値に応じて色が変わっています。

セル色は行列数により正規化しているので、常に左上(最小値)と右下(最大値)は同じ色になっています。

app_result.gif


まとめ

WPFのDataGridセルをExcelのように数値に応じて色付けする方法を紹介しました。

xamlのみで実装できなかった(コードビハインドを使っちゃた)のは少し悔しいのですが、やりたいことは実現できたので良しとします。

ソースコードはGithubにて公開しています。

hsytkm/ColoredDataGridCell


環境

Visual Studio Community 2019

.NET Framework 4.6.1