LoginSignup
4

More than 3 years have passed since last update.

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

Posted at

[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

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4