標準のグリッドにデータを表示するだけなら DataGridView でも事足ります。しかし「この列にプログレスバーを出したい」「ここはスライダーで値を変えたい」「このセルにドロップダウンを仕込みたい」となったとき、標準コントロールでは急激に複雑になります。

ReoGrid はこのような要求に対して CellBody という単一の概念で応えます。CellBody を継承したクラスを作り、セルに割り当てるだけで、描画と操作の両方を完全にコントロールできます。


CellBody とは

ReoGrid では「レンダラー」と「エディター」を別々に定義しません。代わりに CellBody という 1 つのクラスがそれらを兼ねます。

using unvell.ReoGrid.CellTypes;

public class MyCellBody : CellBody
{
    // 描画: セルが再描画されるたびに呼ばれる
    public override void OnPaint(CellDrawingContext dc) { ... }

    // マウスイベント
    public override bool OnMouseDown(CellMouseEventArgs e) { ... }
    public override bool OnMouseMove(CellMouseEventArgs e) { ... }
    public override bool OnMouseEnter(CellMouseEventArgs e) { ... }
    public override bool OnMouseLeave(CellMouseEventArgs e) { ... }

    // 編集開始を許可するかどうか
    public override bool OnStartEdit() { return false; }

    // セルに割り当てられたとき
    public override void OnSetup(Cell cell) { base.OnSetup(cell); }
}

セルに割り当てるのはインデクサへの代入だけです。

var sheet = reoGridControl1.CurrentWorksheet;
sheet["B3"] = new MyCellBody();

組み込みの CellBody 一覧

よく使う用途は最初から用意されています。すべて unvell.ReoGrid.CellTypes 名前空間にあります。

// ボタン
var btn = new ButtonCell("実行");
btn.Click += (s, e) => MessageBox.Show("クリックされました");
sheet["B2"] = btn;

// チェックボックス
var chk = new CheckBoxCell();
chk.CheckChanged += (s, e) => Console.WriteLine(chk.IsChecked);
sheet["B4"] = chk;

// ラジオボタン (グループで連動)
var group = new RadioButtonGroup();
sheet["B6"] = new RadioButtonCell { RadioGroup = group };
sheet["B7"] = new RadioButtonCell { RadioGroup = group };
sheet["B8"] = new RadioButtonCell { RadioGroup = group };

// ドロップダウンリスト
var dd = new DropdownListCell("未対応", "対応中", "完了", "却下");
dd.SelectedItemChanged += (s, e) => Console.WriteLine(dd.SelectedItem);
sheet["B10"] = dd;

// ハイパーリンク
sheet["B12"] = new HyperlinkCell("https://reogrid.net");

// 画像
sheet["B14"] = new ImageCell(Image.FromFile("logo.png"));

これらはすべてそのまま動き、特別な設定は不要です。


例 1: プログレスバーセル

セルの値(0.0〜1.0)を読み取り、幅に応じたグラデーションバーを描画します。最も基本的な CellBody の実装例です。

using unvell.ReoGrid.CellTypes;
using unvell.ReoGrid.Graphics;
using unvell.ReoGrid.Rendering;

public class ProgressBarCell : CellBody
{
    public SolidColor BarColor { get; set; } = SolidColor.SteelBlue;
    public SolidColor BarColorEnd { get; set; } = SolidColor.CornflowerBlue;

    public override void OnPaint(CellDrawingContext dc)
    {
        // セルの値を 0〜1 の double として取得
        double value = Cell.Worksheet.GetCellData<double>(Cell.Position);
        value = Math.Clamp(value, 0, 1);

        var bounds = GetBodyBounds();
        int barWidth = (int)(value * bounds.Width);

        if (barWidth > 0)
        {
            var rect = new Rectangle(bounds.Left, bounds.Top + 1, barWidth, bounds.Height - 2);

            // グラデーション塗りつぶし (WinForms)
            dc.Graphics.FillRectangleLinear(BarColor, BarColorEnd, 90f, rect);
        }

        // パーセント文字列をセルテキストとして描画
        dc.DrawCellText();
    }

    // 直接編集は無効にする (値は隣のセルや API 経由で設定)
    public override bool OnStartEdit() => false;
}

使い方:

// B 列をプログレスバー列にする
for (int r = 2; r <= 11; r++)
{
    sheet[r, 1] = new ProgressBarCell();
}

// 値を設定 (0.0〜1.0)
sheet[2, 1] = 0.75;
sheet[3, 1] = 0.42;

// パーセント表示書式を当てれば「75%」「42%」と描画される
sheet.SetRangeDataFormat(2, 1, 10, 1,
    DataFormat.CellDataFormatFlag.Percent,
    new DataFormat.NumberDataFormatter.NumberFormatArgs { DecimalPlaces = 0 });

数式で別セルの値を参照することもできます。

// C 列の実績 / D 列の目標 → B 列に自動反映
sheet[2, 1] = "=C2/D2";

例 2: スライダーセル

マウス操作でセルの値を変更できるスライダーです。OnMouseDownOnMouseMove を使って、カーソル位置から値を逆算します。

public class SliderCell : CellBody
{
    private bool isHover = false;

    public override void OnSetup(Cell cell)
    {
        base.OnSetup(cell);
    }

    public override void OnPaint(CellDrawingContext dc)
    {
        float value = 0;
        float.TryParse(dc.Cell.DisplayText, out value);
        value = Math.Clamp(value, 0f, 1f);

        var bounds = Cell.GetBounds();
        var g = dc.Graphics;

        int halfH = (int)(bounds.Height / 2f);
        int sliderH = (int)Math.Min(bounds.Height - 4, 20);

        // バー背景
        g.FillRectangle(4, halfH - 3, bounds.Width - 8, 6, SolidColor.Gainsboro);

        // サムの X 座標
        int thumbX = 2 + (int)(value * (bounds.Width - 12));
        var thumbRect = new Rectangle(thumbX, halfH - sliderH / 2, 8, sliderH);

        // ホバー時は濃い色
        g.FillRectangle(thumbRect, isHover ? SolidColor.MediumSeaGreen : SolidColor.LightGreen);
    }

    public override bool OnMouseDown(CellMouseEventArgs e)
    {
        SetValueFromX(e.RelativePosition.X);
        return true; // イベントを処理済みにする
    }

    public override bool OnMouseMove(CellMouseEventArgs e)
    {
        if (e.Buttons == unvell.ReoGrid.Interaction.MouseButtons.Left)
            SetValueFromX(e.RelativePosition.X);
        return false;
    }

    public override bool OnMouseEnter(CellMouseEventArgs e)
    {
        isHover = true;
        return true;
    }

    public override bool OnMouseLeave(CellMouseEventArgs e)
    {
        isHover = false;
        return true;
    }

    public override bool OnStartEdit() => false; // テキスト入力は無効

    private void SetValueFromX(float x)
    {
        float value = x / (Cell.GetBounds().Width - 2f);
        value = Math.Clamp(value, 0f, 1f);
        Cell.Worksheet.SetCellData(Cell.Position, Math.Round(value, 2));
    }
}

スライダーの値を別セルのプログレスバーに連動させることもできます。

// E5 にスライダー
sheet["E5"] = new SliderCell();
sheet["E5"] = 0.5;

// D5 に連動するプログレスバー
sheet["D5"] = new ProgressBarCell();
sheet["D5"] = "=E5"; // 数式で参照

// C5 に数値表示
sheet["C5"] = "=E5";
sheet.SetRangeDataFormat("C5", DataFormat.CellDataFormatFlag.Percent,
    new DataFormat.NumberDataFormatter.NumberFormatArgs { DecimalPlaces = 0 });

例 3: ステータスバッジセル

業務アプリでよくある「ステータス」列を、色付きのラベル(バッジ)として描画します。テキストを読み取り、値に応じて背景色を切り替えます。

public class StatusBadgeCell : CellBody
{
    private static readonly Dictionary<string, SolidColor> StatusColors = new()
    {
        ["未対応"] = SolidColor.Tomato,
        ["対応中"] = SolidColor.SteelBlue,
        ["完了"]   = SolidColor.MediumSeaGreen,
        ["却下"]   = SolidColor.Gray,
    };

    public override void OnPaint(CellDrawingContext dc)
    {
        string text = Cell.DisplayText;
        var bounds = GetBodyBounds();

        // ステータスに対応する色を選ぶ (未定義なら薄灰色)
        var bgColor = StatusColors.TryGetValue(text, out var c) ? c
            : new SolidColor(200, SolidColor.LightGray);

        // 角丸矩形を描画
        var rect = new Rectangle(
            bounds.Left + 2, bounds.Top + 2,
            bounds.Width - 4, bounds.Height - 4);
        dc.Graphics.FillRoundedRectangle(rect, 4, bgColor);

        // テキスト (白)
        dc.Graphics.DrawText(
            text,
            dc.Cell.CalcedStyle?.FontName ?? "Segoe UI",
            dc.Cell.CalcedStyle?.FontSize ?? 9f,
            SolidColor.White,
            bounds,
            ReoGridHorAlign.Center,
            ReoGridVerAlign.Middle);
    }

    // 直接入力も許可する (ドロップダウンと組み合わせる場合は false に)
    public override bool OnStartEdit() => true;
}

ドロップダウンとバッジを同じセルに持たせたい場合は、DropdownListCell を継承して OnPaint をオーバーライドすることができます。


CellBody と WPF

WPF 版 (unvell.ReoGridWPF.dll) でも CellBody の継承構造は同じです。違いは CellDrawingContext.Graphics が WPF の描画命令を受け付けるラッパーになっている点だけで、API の形は共通です。

// WinForms: System.Drawing.Graphics に委譲
dc.Graphics.FillRectangleLinear(color1, color2, 90f, rect);

// WPF: System.Windows.Media の DrawingContext に委譲
// (同じ呼び出し — ラッパーが吸収する)
dc.Graphics.FillRectangleLinear(color1, color2, 90f, rect);

プラットフォームごとにクラスを書き直す必要はありません。共通の CellBody サブクラスを WinForms / WPF プロジェクトどちらにも参照できるクラスライブラリに置くと、再利用性が高まります。


CellBody を複数セルに一括適用する

同じ CellBody クラスを複数のセルに適用したい場合、Clone() をオーバーライドしておくと ReoGrid がセルごとに独立したインスタンスを使えるようになります。

public class ProgressBarCell : CellBody
{
    // ... (描画コード)

    public override ICellBody Clone()
    {
        return new ProgressBarCell
        {
            BarColor    = this.BarColor,
            BarColorEnd = this.BarColorEnd,
        };
    }
}

// 範囲に一括設定するユーティリティ
for (int r = 1; r <= 20; r++)
{
    sheet[r, 2] = new ProgressBarCell { BarColor = SolidColor.Coral };
}

Clone() を実装しておくと、SetRangeData などで範囲に一括割り当てしたときも問題が起きません。


ここまでのまとめ

やりたいこと方法
既製のドロップダウン・チェックボックスを使いたいDropdownListCell / CheckBoxCell などをそのまま使う
セルの値をグラフィックで可視化したいCellBody.OnPaint をオーバーライド
マウス操作でセルの値を変えたいOnMouseDown / OnMouseMove をオーバーライド
直接テキスト入力は禁止したいOnStartEditfalse を返す
WinForms / WPF 両方で使いたいCellBody の継承クラスをクラスライブラリに置く
範囲に一括適用したいClone() をオーバーライドしてインスタンスを複製可能にする

CellBody は「見た目だけ変える」のではなく、「セルそのものを独自の UI コンポーネントに変える」ための仕組みです。プログレスバーやスライダーから、カスタムのカレンダーピッカーや星評価セルまで、同じパターンで作ることができます。


さらに読む