全選択チェックボックスヘッダーの作成

チェックボックスセルを持つ列のヘッダーに、全選択/全解除を操作するチェックボックスを配置する方法を説明します。 ヘッダーのチェックボックスは、列内のセルの状態を反映して 未選択 / 選択済み / 不確定 の3状態を表示します。

この機能は HeaderBody クラスを継承してカスタムヘッダーボディを作成することで実現します。

前提知識:

完成イメージ

列の一番左にチェックボックス付きのヘッダーを配置します。

  • ヘッダーのチェックボックスをクリックすると列内の全セルを一括 ON/OFF できる
  • 列内のセルが一部だけチェックされている場合は 不確定( - 状態を表示する
  • セルのチェック状態が変わると、ヘッダーが自動的に更新される

CheckboxHeaderCell の実装

HeaderBody を継承した CheckboxHeaderCell クラスを作成します。

using unvell.ReoGrid.Actions;
using unvell.ReoGrid.CellTypes;
using unvell.ReoGrid.Core.Header;
using unvell.ReoGrid.Events;
using unvell.ReoGrid.Graphics;
using unvell.ReoGrid.Interaction;
using unvell.ReoGrid.Rendering;

/// <summary>
/// 列内の CheckBoxCell を全選択/全解除するヘッダーチェックボックス。
/// Unchecked / Checked / Indeterminate の3状態を表示します。
/// </summary>
class CheckboxHeaderCell : HeaderBody
{
    public HeaderCell HeaderCell { get; private set; }

    private enum CheckState { Unchecked, Checked, Indeterminate }
    private CheckState state = CheckState.Unchecked;

    private Rectangle boxRect = Rectangle.Zero;

    public ReoGridControl Grid { get; private set; }

    public CheckboxHeaderCell(ReoGridControl grid)
    {
        this.Grid = grid;
    }

    // ヘッダーセルにアタッチされたときに呼ばれる
    public override void OnSetup(IHeader header)
    {
        base.OnSetup(header);
        HeaderCell = header as HeaderCell;
    }

    // ヘッダーサイズが変わったとき、チェックボックスの描画領域を再計算する
    public override void OnSizeChanged(Size size)
    {
        base.OnSizeChanged(size);
        var side = Math.Min(size.Width, size.Height) - 4; // padding
        if (side < 10) side = 10;
        boxRect = new Rectangle((size.Width - side) / 2f, (size.Height - side) / 2f, side, side);
    }

    // チェックボックスを描画する
    public override void OnPaint(CellDrawingContext dc, Size renderSize)
    {
        var g = dc.Graphics;

        // ズーム倍率を考慮したスケール後の矩形
        var renderBoxRect = boxRect * dc.Worksheet.ScaleFactor;

        // 背景と枠線
        g.FillRectangle(renderBoxRect, SolidColor.WhiteSmoke);
        g.DrawRectangle(renderBoxRect, SolidColor.Gray, 1, LineStyles.Solid);

        if (state == CheckState.Checked)
        {
            // チェックマーク(レ点)
            var x = renderBoxRect.X; var y = renderBoxRect.Y;
            var w = renderBoxRect.Width; var h = renderBoxRect.Height;
            var p1 = new Point(x + w * 0.18f, y + h * 0.55f);
            var p2 = new Point(x + w * 0.42f, y + h * 0.75f);
            var p3 = new Point(x + w * 0.82f, y + h * 0.22f);
            var pts = new[] { p1, p2, p3 };
            var lineWidth = Math.Max(1f, w * 0.12f);
            g.DrawLines(pts, 0, pts.Length, SolidColor.Black, lineWidth, LineStyles.Solid);
        }
        else if (state == CheckState.Indeterminate)
        {
            // 不確定状態(横棒)
            var bar = new Rectangle(
                renderBoxRect.X + renderBoxRect.Width * 0.2f,
                renderBoxRect.Y + renderBoxRect.Height * 0.45f,
                renderBoxRect.Width * 0.6f,
                renderBoxRect.Height * 0.15f);
            g.FillRectangle(bar, SolidColor.Black);
        }
    }

    // マウスボタンを押したとき(チェックボックス内ならイベントをキャプチャ)
    public override void OnMouseDown(HeaderCellMouseEventArgs e)
    {
        if (boxRect.Contains(e.RelativePosition))
        {
            e.IsCancelled = true; // OnMouseUp のみで処理するため列選択を無効化
        }
    }

    // マウスボタンを離したとき(チェックボックス内ならトグル)
    public override void OnMouseUp(HeaderCellMouseEventArgs e)
    {
        if (boxRect.Contains(e.RelativePosition))
        {
            Toggle();
            e.IsCancelled = true;
        }
    }

    // チェックボックス上でのカーソル変更
    public override void OnMouseMove(HeaderCellMouseEventArgs e)
    {
        base.OnMouseMove(e);
        this.HeaderCell.Worksheet.ChangeCursor(CursorStyle.PlatformDefault);
        e.IsCancelled = true;
    }

    // 列内のセルデータが変化したとき、ヘッダーの状態を更新する
    public override void OnDataChange(int startRow, int endRow)
    {
        var headerCell = OwnerHeader as HeaderCell;
        if (headerCell == null) return;

        var ws = headerCell.Worksheet;
        int col = headerCell.ColumnIndex;
        if (col < 0 || col >= ws.ColumnCount) return;

        bool anyTrue = false, anyFalse = false;
        for (int r = 0; r < ws.RowCount; r++)
        {
            var data = ws.GetCellData(r, col) as bool?;
            if (data == true) anyTrue = true; else anyFalse = true;
            if (anyTrue && anyFalse) break; // どちらもあれば不確定で確定
        }

        state = anyTrue && anyFalse
            ? CheckState.Indeterminate
            : (anyTrue ? CheckState.Checked : CheckState.Unchecked);
    }

    private void Toggle()
    {
        var headerCell = OwnerHeader as HeaderCell;
        if (headerCell == null) return;
        bool target = state != CheckState.Checked; // 不確定 → チェック済みに移行
        ApplyStateToColumn(headerCell, target);
    }

    private void ApplyStateToColumn(HeaderCell headerCell, bool value)
    {
        var ws = headerCell.Worksheet;
        int col = headerCell.ColumnIndex;

        if (Grid.UseCellUpdateActionForBuiltinCellTypes)
        {
            // Undo/Redo をサポートするアクション経由で更新
            var action = new UpdateAllCellsCheckboxAction(col, value);
            Grid.DoAction(ws, action);
        }
        else
        {
            for (int r = 0; r < ws.RowCount; r++)
            {
                ws.SetCellData(r, col, value);
            }
        }

        state = value ? CheckState.Checked : CheckState.Unchecked;
    }

    public override IHeaderBody Clone() => new CheckboxHeaderCell(Grid) { state = this.state };
}

ポイント:

  • OnSetupOwnerHeader から HeaderCell を取得し、ColumnIndexWorksheet にアクセスできるようにしておきます。
  • OnSizeChanged でズーム変化に対応した描画領域を計算します。
  • OnDataChange はセルのデータが変わるたびに呼ばれるので、ここでヘッダーの状態を同期します。
  • e.IsCancelled = true を設定することで、クリック時に列全体が選択状態になるデフォルト動作をキャンセルします。

Undo/Redo 対応アクション

一括チェック操作を Undo/Redo に対応させるには、WorksheetAction を継承したアクションを作成し、Grid.DoAction() に渡します。

class UpdateAllCellsCheckboxAction : WorksheetAction
{
    private int columnIndex;
    private bool value;
    private Dictionary<Cell, bool> oldValues = new Dictionary<Cell, bool>();

    public UpdateAllCellsCheckboxAction(int columnIndex, bool value)
    {
        this.columnIndex = columnIndex;
        this.value = value;
    }

    public override void Do()
    {
        for (int r = 0; r < Worksheet.RowCount; r++)
        {
            var cell = Worksheet.GetCell(r, columnIndex);
            if (cell != null)
            {
                oldValues[cell] = Worksheet.GetCellData(r, columnIndex) as bool? ?? false;
                Worksheet.SetCellData(r, columnIndex, value);
            }
        }
    }

    public override void Undo()
    {
        foreach (var kv in oldValues)
        {
            kv.Key.Worksheet.SetCellData(kv.Key.Row, kv.Key.Column, kv.Value);
        }
    }

    public override string GetName() => "Update Cell Checkbox Values";
}

Grid.UseCellUpdateActionForBuiltinCellTypestrue の場合は自動的にアクション経由で操作が記録されます。 Undo/Redo が不要であれば、このクラスは省略して SetCellData を直接呼ぶだけで構いません。

ヘッダーへのセット

作成した CheckboxHeaderCell を列ヘッダーの Body プロパティに割り当てます。あわせて列の DefaultCellBodyCheckBoxCell を設定することで、その列のセルを自動的にチェックボックス型にできます。

var sheet = grid.CurrentWorksheet;

sheet.Rows = 10;

// 0列目: チェックボックスヘッダーを設定
sheet.ColumnHeaders[0].Body = new CheckboxHeaderCell(grid);
sheet.ColumnHeaders[0].Width = 28;

// 0列目のセルをすべてチェックボックス型にする
sheet.ColumnHeaders[0].DefaultCellBody = typeof(CheckBoxCell);

// その他の列ヘッダーのテキスト
sheet.ColumnHeaders[1].Text = "ID";
sheet.ColumnHeaders[2].Text = "名前";
sheet.ColumnHeaders[3].Text = "スコア";
// ...

// セルにデータを設定
for (int r = 0; r < 10; r++)
{
    sheet[r, 0] = (r % 2 == 0); // 偶数行だけチェック
    sheet[r, 1] = $"S{r + 1:000}";
    sheet[r, 2] = $"生徒 {r + 1}";
}

DefaultCellBody を設定すると、その列のすべてのセルが自動的に指定されたセルボディ型(ここでは CheckBoxCell )になります。 詳しくは 列全体へのセル型適用 を参照してください。

関連トピック


ページの内容は役に立ちましたか?