標準のグリッドにデータを表示するだけなら 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: スライダーセル
マウス操作でセルの値を変更できるスライダーです。OnMouseDown と OnMouseMove を使って、カーソル位置から値を逆算します。
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 をオーバーライド |
| 直接テキスト入力は禁止したい | OnStartEdit で false を返す |
| WinForms / WPF 両方で使いたい | CellBody の継承クラスをクラスライブラリに置く |
| 範囲に一括適用したい | Clone() をオーバーライドしてインスタンスを複製可能にする |
CellBody は「見た目だけ変える」のではなく、「セルそのものを独自の UI コンポーネントに変える」ための仕組みです。プログレスバーやスライダーから、カスタムのカレンダーピッカーや星評価セルまで、同じパターンで作ることができます。
