[WPF]ExcelのようにDataGridのセルを数値に応じて色付けする
はじめに
WPFでDataGridの各セルを数値に応じて色付けする方法についてまとめます。
Excelで言うところの条件付き書式ルールのような表示です。
全体構成
以下の流れで説明します。
- ViewModelに二次元配列を用意する
- DataGridを継承した自作コントロールを定義する
- 自作コントロールに二次元配列をバインドする
- バインドした二次元配列を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ボタンをクリックすると行列数がランダムに変化し、セルの値に応じて色が変わっています。
セル色は行列数により正規化しているので、常に左上(最小値)と右下(最大値)は同じ色になっています。
まとめ
WPFのDataGridセルをExcelのように数値に応じて色付けする方法を紹介しました。
xamlのみで実装できなかった(コードビハインドを使っちゃた)のは少し悔しいのですが、やりたいことは実現できたので良しとします。
ソースコードはGithubにて公開しています。
環境
Visual Studio Community 2019
.NET Framework 4.6.1