2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

C# の自作テーブル

Last updated at Posted at 2025-08-11

実装機能

TableCell.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
using NewTableCell_test.MyControls.Cells;

namespace CalculationForms2.MyControls.TableCellTextPainted {
    /// <summary>
    /// 軽量 OnPaint テーブル(既存 TableCellText 互換APIの最小実装を網羅)
    /// </summary>
    [ToolboxItem(true)]
    public class TableCell : Control {

        #region // ===== レイアウト フィールド =====

        private const string DictKeyText = "Text";


        private int _rowCount = 30;
        [Category("Layout")]
        public int RowCount {
            get => _rowCount;
            set {
                var v = Math.Max(1, value);
                if (_rowCount == v) return;
                _rowCount = v;
                RecalcLayoutAndInvalidate();
            }
        }

        private int _colCount = 10;
        [Category("Layout")]
        public int ColumnCount {
            get => _colCount;
            set {
                var v = Math.Max(1, value);
                if (_colCount == v) return;
                _colCount = v;
                RecalcLayoutAndInvalidate();
            }
        }


        private int[] _columnWidths = Array.Empty<int>();
        [Category("Layout"), DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        public int[] ColumnWidths {
            get => _columnWidths;
            set { _columnWidths = value ?? Array.Empty<int>(); RecalcLayoutAndInvalidate(); }
        }

        private int[] _rowHeights = Array.Empty<int>();
        [Category("Layout"), DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        public int[] RowHeights {
            get => _rowHeights;
            set { _rowHeights = value ?? Array.Empty<int>(); RecalcLayoutAndInvalidate(); }
        }

        // 行の非表示管理
        private readonly HashSet<int> _hiddenRows = new HashSet<int>();
        // 列の非表示管理
        private readonly HashSet<int> _hiddenCols = new HashSet<int>();
        // 列・行のレイアウトキャッシュ
        private Rectangle[] _colRects = new Rectangle[0]; // X/Width を保持(Y/Heightは行側で)
        private int[] _rowTops = new int[0];              // 各行のY開始(最後に合計高さ)

        private readonly Dictionary<(int r, int c), Cell> _cells = new Dictionary<(int r, int c), Cell>();


        protected override void OnCreateControl() {
            base.OnCreateControl();
            RecalcLayoutAndInvalidate(); // ★ 初回レイアウト計算を必ず実行
        }

        private CellStyle DefaultStyle() {
            CellStyle s = new CellStyle();
            s.Align = ContentAlignment.MiddleRight;
            s.BackColor = Color.White;
            s.ForeColor = Color.Black;
            s.Bold = false;
            return s;
        }
        private Cell GetCell(int r, int c)
        {
            if (_cells.TryGetValue((r, c), out var cell)) return cell;
            cell = new Cell(r, c); // 既定スタイル付きで作成
            _cells[(r, c)] = cell;
            return cell;
        }
        #endregion // ===== レイアウト =====


        #region ------------------- セルに載せるコントロールの -------------------
        // TableCellTextPaintedLite.cs 内にユーティリティ関数を追加
        private static OverlayKind DetectOverlayKind(Control control, out string typeName)
        {
            typeName = null;
            if (control == null) return OverlayKind.None;

            // 既知の型を判定
            if (control is TextBox) { typeName = typeof(TextBox).FullName; return OverlayKind.TextBox; }
            if (control is ComboBox) { typeName = typeof(ComboBox).FullName; return OverlayKind.ComboBox; }
            if (control is CheckBox) { typeName = typeof(CheckBox).FullName; return OverlayKind.CheckBox; }
            if (control is Button) { typeName = typeof(Button).FullName; return OverlayKind.Button; }
            if (control is Label) { typeName = typeof(Label).FullName; return OverlayKind.Label; }
            if (control is DateTimePicker) { typeName = typeof(DateTimePicker).FullName; return OverlayKind.DateTimePicker; }
            if (control is NumericUpDown) { typeName = typeof(NumericUpDown).FullName; return OverlayKind.NumericUpDown; }
            if (control is TrackBar) { typeName = typeof(TrackBar).FullName; return OverlayKind.TrackBar; }

            // 既知以外は Custom として実型名を入れる
            typeName = control.GetType().FullName;
            return OverlayKind.Custom;
        }

        // 既存の SetCellControl を修正
        public void SetCellControl(int row, int col, Control control, Dictionary<string, string> myDictionary)
        {
            EnsureInside(row, col);
            // 結合セルならアンカーへ
            int ar, ac;
            if (TryGetMergeAnchor(row, col, out ar, out ac)) { row = ar; col = ac; }

            var cd = GetCell(row, col);

            // 既存を外す
            if (cd.Overlay != null)
            {
                Controls.Remove(cd.Overlay);
                cd.Overlay.Dispose();
                cd.Overlay = null;
                // ★ 種別クリア
                cd.OverlayType = OverlayKind.None;
                cd.OverlayTypeName = null;
            }

            cd.Dict = myDictionary;

            // 表示へ反映:dict["Text"] があればセルの Text に反映
            string textFromDict;
            if (cd.Dict != null && cd.Dict.TryGetValue("Text", out textFromDict))
            {
                cd.Text = textFromDict ?? string.Empty;
            }

            if (control != null)
            {
                cd.Overlay = control;

                // ★ 種別を自動判定して保存
                string tn;
                cd.OverlayType = DetectOverlayKind(control, out tn);
                cd.OverlayTypeName = tn;

                // 位置とレイアウト
                var rc = GetMergedRect(row, col);
                if (rc.IsEmpty) rc = GetCellRect(row, col);
                control.Bounds = Rectangle.Inflate(rc, -1, -1);
                control.Margin = Padding.Empty;
                control.Anchor = AnchorStyles.Left | AnchorStyles.Top;

                Controls.Add(control);
                control.BringToFront();
            }

            InvalidateCell(row, col);
        }


        public OverlayKind GetCellOverlayKind(int row, int col, out string typeName)
        {
            EnsureInside(row, col);
            int ar, ac;
            if (TryGetMergeAnchor(row, col, out ar, out ac)) { row = ar; col = ac; }

            Cell cd;
            if (_cells.TryGetValue((row, col), out cd) && cd != null)
            {
                typeName = cd.OverlayTypeName;
                return cd.OverlayType;
            }
            typeName = null;
            return OverlayKind.None;
        }
        #endregion


        #region --------------------------- ==== コンストラクタ ===== -----------------------------------

        public TableCell()
        {
            SetStyle(ControlStyles.AllPaintingInWmPaint |
                     ControlStyles.OptimizedDoubleBuffer |
                     ControlStyles.UserPaint |
                     ControlStyles.ResizeRedraw, true);
            BackColor = Color.White;
        }
        #endregion --------------------------- ==== コンストラクタ ===== -----------------------------------


        #region --------------------------- セルごとのイベント登録 -----------------------------------
        // セル(結合アンカーに解決後)にハンドラを追加/削除
        public void AddCellValueChangedHandler(int row, int col, EventHandler<CellValueChangedEventArgs> handler)
        {
            EnsureInside(row, col);
            ResolveAnchor(ref row, ref col); // 結合セルはアンカーへ
            var cell = GetCell(row, col);
            cell.ValueChanged += handler;
        }

        public void RemoveCellValueChangedHandler(int row, int col, EventHandler<CellValueChangedEventArgs> handler)
        {
            EnsureInside(row, col);
            ResolveAnchor(ref row, ref col);
            Cell cell;
            if (_cells.TryGetValue((row, col), out cell) && cell != null)
                cell.ValueChanged -= handler;
        }
        #endregion  -------------------- セルごとのイベント登録 ---------------------------

        #region ------------------------- 編集可能かどうか --------------------------------
        // TableCellTextPaintedLite.cs のメンバに追加
        public void SetCellEditable(int row, int col, bool editable)
        {
            EnsureInside(row, col);
            // 結合セルならアンカーに寄せる(あなたの実装に合わせて)
            int ar, ac;
            if (TryGetMergeAnchor(row, col, out ar, out ac)) { row = ar; col = ac; }

            Cell cd = GetCell(row, col);
            cd.IsEditable = editable;
        }

        public void SetRowEditable(int row, bool editable)
        {
            EnsureInside(row, 0);
            for (int c = 0; c < ColumnCount; c++)
            {
                SetCellEditable(row, c, editable);
            }
        }

        public void SetColumnEditable(int col, bool editable)
        {
            EnsureInside(0, col);
            for (int r = 0; r < RowCount; r++)
            {
                SetCellEditable(r, col, editable);
            }
        }
        #endregion

        #region -------------------- セル結合 -----------------------------
        // ===== 結合セル =====
        public class MergedCell
        {
            public int Row { get; set; }
            public int Column { get; set; }
            public int RowSpan { get; set; } = 1;
            public int ColSpan { get; set; } = 1;
            public override string ToString() { return "(" + Row + "," + Column + ") " + ColSpan + "x" + RowSpan; }
        }

        [Category("Layout")]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        public List<MergedCell> MergedCells { get; private set; } = new List<MergedCell>();


        public void ClearMergedCells()
        {
            MergedCells.Clear();
            RecalcLayoutAndInvalidate();
        }
        // 既存 MergedCells を使って重複・交差を防ぎつつ追加
        public bool MergeCells(int row, int col, int rowSpan, int colSpan)
        {
            EnsureInside(row, col);
            rowSpan = Math.Max(1, rowSpan);
            colSpan = Math.Max(1, colSpan);

            // 範囲クリップ
            int r2 = Math.Min(RowCount - 1, row + rowSpan - 1);
            int c2 = Math.Min(ColumnCount - 1, col + colSpan - 1);

            // 交差チェック(既存結合と重なるなら拒否)
            for (int i = 0; i < MergedCells.Count; i++)
            {
                var m = MergedCells[i];
                var r1a = row; var c1a = col; var r2a = r2; var c2a = c2;
                var r1b = m.Row; var c1b = m.Column; var r2b = m.Row + m.RowSpan - 1; var c2b = m.Column + m.ColSpan - 1;
                bool overlap = !(r2a < r1b || r2b < r1a || c2a < c1b || c2b < c1a);
                if (overlap) return false;
            }

            MergedCells.Add(new MergedCell { Row = row, Column = col, RowSpan = r2 - row + 1, ColSpan = c2 - col + 1 });
            RecalcLayoutAndInvalidate();
            return true;
        }

        // アンカーを指定して解除
        public bool UnmergeAt(int row, int col)
        {
            for (int i = 0; i < MergedCells.Count; i++)
            {
                var m = MergedCells[i];
                if (m.Row == row && m.Column == col)
                {
                    MergedCells.RemoveAt(i);
                    RecalcLayoutAndInvalidate();
                    return true;
                }
            }
            return false;
        }

        // セルがどの結合に含まれるか(見つかればアンカー返却)
        public bool TryGetMergeAnchor(int row, int col, out int anchorRow, out int anchorCol)
        {
            for (int i = 0; i < MergedCells.Count; i++)
            {
                var m = MergedCells[i];
                int r2 = m.Row + m.RowSpan - 1, c2 = m.Column + m.ColSpan - 1;
                if (row >= m.Row && row <= r2 && col >= m.Column && col <= c2)
                { anchorRow = m.Row; anchorCol = m.Column; return true; }
            }
            anchorRow = row; anchorCol = col;
            return false;
        }


        private void ResolveAnchor(ref int row, ref int col)
        {
            int ar, ac;
            if (TryGetMergeAnchor(row, col, out ar, out ac)) { row = ar; col = ac; }
        }


        public void ClearAndDemoMerges()
        {
            ClearMergedCells();
            // 例: (1,1) から 横2×縦1
            MergeCells(1, 1, 1, 2);
            SetCellText(1, 1, "2x1");

            // 例: (3,0) から 横3×縦2
            MergeCells(3, 0, 2, 3);
            SetCellText(3, 0, "3x2");
        }

        // 互換API:Cell_Joint(2種)
        public void Cell_Joint(int column, int row, int colSpan, int rowSpan)
        {
            MergedCells.Add(new MergedCell { Column = column, Row = row, ColSpan = Math.Max(1, colSpan), RowSpan = Math.Max(1, rowSpan) });
            RecalcLayoutAndInvalidate();
        }
        // 背景色を指定できる版(C# 7.3 でも OK)
        public void Cell_Joint(int column, int row, int colSpan, int rowSpan,
                               Color? backColor = null, bool editable = false)
        {
            // 結合情報を追加
            MergedCells.Add(new MergedCell
            {
                Column = column,
                Row = row,
                ColSpan = Math.Max(1, colSpan),
                RowSpan = Math.Max(1, rowSpan)
            });

            // アンカーセル(左上セル)を取得
            var anchor = GetCell(row, column);

            // 背景色設定
            if (backColor.HasValue)
            {
                var st = anchor.Style;
                st.BackColor = backColor.Value;
                anchor.Style = st;
            }

            // 編集可否設定
            anchor.IsEditable = editable;

            RecalcLayoutAndInvalidate();
        }

        public void Cell_Joint(Control control, int column, int row, int colSpan, int rowSpan)
        {
            // 互換:コントロールを載せつつ結合も記録
            SetCellControl(row, column, control, null);
            Cell_Joint(column, row, colSpan, rowSpan);
        }
        #endregion ----------------------セル結合----------------------

        #region ---------------------- セルに値を格納 -----------------------
        // ===== 互換API:セル操作 =====
        #region ---------------------- SetCellText ----------------------- 
        public void SetCellText(int row, int col, string text)
        {
            EnsureInside(row, col);
            ResolveAnchor(ref row, ref col);
            var cd = GetCell(row, col);

            string oldText = cd.Text ?? "";
            string newText = text ?? "";

            if (oldText != newText)  // 変更時のみ
            {
                cd.Text = newText;
                InvalidateCell(row, col);
                cd.RaiseValueChanged(oldText, newText);
            }
            else
            {
                InvalidateCell(row, col); // 再描画だけ必要なら
            }
        }

        public void SetCellText(int row, int col, string text, ContentAlignment align)
        {
            EnsureInside(row, col);
            Cell cd = GetCell(row, col);
            cd.Text = text ?? "";
            CellStyle st = cd.Style;
            st.Align = align; cd.Style = st;
            InvalidateCell(row, col);
        }

        public void SetCellText(int row, int col, string text, ContentAlignment align, Color backColor)
        {
            EnsureInside(row, col);
            Cell cd = GetCell(row, col);
            cd.Text = text ?? "";
            CellStyle st = cd.Style; st.Align = align;
            st.BackColor = backColor; cd.Style = st;
            InvalidateCell(row, col);
        }
        #endregion ---------------------- setCellText-----------------------

        // 新しい本体(editable 追加、既定 false)
        public void SetCellDictionary(int row, int col,
                                      Dictionary<string, string> dict, ContentAlignment align,
                                      bool editable = false,
                                      bool clone = true,
                                      Color? backColor = null)   // ★ 追加:引数で背景色指定可
        {
            EnsureInside(row, col);
            ResolveAnchor(ref row, ref col);
            var cd = GetCell(row, col);

            // ① 編集可否
            cd.IsEditable = editable;

            // ② ディクショナリ
            cd.Dict = (dict == null) ? null : (clone ? new Dictionary<string, string>(dict) : dict);

            // ③ 背景色(引数優先 → dict["BackColor"] → 既定のまま)
            var oldColor = cd.Style.BackColor;  // 変更検知用
            var st = cd.Style;                  // (CellStyle は参照型 or 値型どちらでもOKな書き方)
            if (backColor.HasValue)
            {
                st.BackColor = backColor.Value;
            }
            else if (cd.Dict != null && cd.Dict.TryGetValue("BackColor", out var colStr))
            {
                Color parsed;
                if (TryParseColor(colStr, out parsed))
                    st.BackColor = parsed;
            }
            // ★ 追加: テキスト位置を反映
            st.Align = align;
            cd.Style = st;

            // ④ Text 反映 & 変更イベント
            string oldText = cd.Text ?? "";
            string newText = oldText;
            string v;
            if (cd.Dict != null && cd.Dict.TryGetValue(DictKeyText, out v))
                newText = v ?? string.Empty;

            bool textChanged = oldText != newText;
            bool colorChanged = oldColor != cd.Style.BackColor;

            if (textChanged)
            {
                cd.Text = newText;
                InvalidateCell(row, col);
                cd.RaiseValueChanged(oldText, newText);
            }
            else if (colorChanged)
            {
                InvalidateCell(row, col);
            }
            else
            {
                InvalidateCell(row, col);
            }
        }

        private static bool TryParseColor(string s, out Color color)
        {
            color = Color.Empty;
            if (string.IsNullOrWhiteSpace(s)) return false;
            s = s.Trim();

            // #RRGGBB or #AARRGGBB
            if (s.StartsWith("#"))
            {
                s = s.Substring(1);
                if (s.Length == 6 || s.Length == 8)
                {
                    byte a = 255, r, g, b;
                    int idx = 0;
                    if (s.Length == 8) { a = Convert.ToByte(s.Substring(idx, 2), 16); idx += 2; }
                    r = Convert.ToByte(s.Substring(idx, 2), 16); idx += 2;
                    g = Convert.ToByte(s.Substring(idx, 2), 16); idx += 2;
                    b = Convert.ToByte(s.Substring(idx, 2), 16);
                    color = Color.FromArgb(a, r, g, b);
                    return true;
                }
                return false;
            }

            // R,G,B[,A]
            var parts = s.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
            if (parts.Length == 3 || parts.Length == 4)
            {
                int r = int.Parse(parts[0]);
                int g = int.Parse(parts[1]);
                int b = int.Parse(parts[2]);
                int a = (parts.Length == 4) ? int.Parse(parts[3]) : 255;
                color = Color.FromArgb(a, r, g, b);
                return true;
            }

            // 名前色
            var named = Color.FromName(s);
            if (named.A != 0 || string.Equals(s, named.Name, StringComparison.OrdinalIgnoreCase))
            {
                color = named;
                return true;
            }

            return false;
        }

        public void SetCellForeColor(int row, int col, Color color, bool? bold = null)
        {
            EnsureInside(row, col);
            Cell cd = GetCell(row, col);
            CellStyle st = cd.Style;
            st.ForeColor = color;
            if (bold.HasValue) st.Bold = bold.Value;
            cd.Style = st;
            InvalidateCell(row, col);
        }

        public void SetCellBackColor(int row, int col, Color color)
        {
            EnsureInside(row, col);
            Cell cd = GetCell(row, col);
            CellStyle st = cd.Style; st.BackColor = color; cd.Style = st;
            InvalidateCell(row, col);
        }

        /// <summary>
        /// セルのスタイルをまとめて設定する
        /// </summary>
        public void SetCellStyle(int row, int col,
                                 ContentAlignment align,
                                 bool editable = false,
                                 bool clone = true,
                                 Color? backColor = null,
                                 Color? foreColor = null,
                                 bool? bold = null,
                                 float? fontSize = null)
        {
            EnsureInside(row, col);
            ResolveAnchor(ref row, ref col);
            var cd = GetCell(row, col);

            // ① 編集可否
            cd.IsEditable = editable;

            // ② スタイル変更
            var st = cd.Style;
            st.Align = align;
            if (backColor.HasValue) st.BackColor = backColor.Value;
            if (foreColor.HasValue) st.ForeColor = foreColor.Value;
            if (bold.HasValue) st.Bold = bold.Value;
            if (fontSize.HasValue && fontSize.Value > 0f) st.FontSize = fontSize.Value;

            cd.Style = st;

            // ③ 再描画
            InvalidateCell(row, col);
        }
        #endregion

        #region ------------ セル情報取得 系互換API(新規追加) ===----------

        #region -------------- セルのテキストと辞書配列 コントロールの取得
        public string GetCellText(int row, int col) {
            Cell cd;
            return _cells.TryGetValue((row, col), out cd) ? (cd.Text ?? "") : "";
        }

        public Dictionary<string, string> GetCellDictionary(int row, int col) {
            Cell cd;
            return _cells.TryGetValue((row, col), out cd) ? cd.Dict : null;
        }

        public Control GetControlAt(int row, int col) {
            Cell cd;
            if (_cells.TryGetValue((row, col), out cd)) return cd.Overlay;
            return null;
        }
        #endregion

        // === 取得系互換API(新規追加) ===
        public Color GetCellBackColor(int row, int col) {
            EnsureInside(row, col);

            // 結合セルはアンカーへ寄せる
            int ar, ac;
            if (TryGetMergeAnchor(row, col, out ar, out ac)) { row = ar; col = ac; }

            Cell cd;
            if (_cells.TryGetValue((row, col), out cd) && cd != null && cd.Style != null) {
                // セルに個別設定があればそれを返す
                return cd.Style.BackColor;
            }
            // 何も無ければテーブルの既定背景色
            return this.BackColor;
        }

        // 必要に応じて前景色や太字なども取得したい場合
        public Color GetCellForeColor(int row, int col) {
            EnsureInside(row, col);
            int ar, ac;
            if (TryGetMergeAnchor(row, col, out ar, out ac)) { row = ar; col = ac; }
            Cell cd;
            if (_cells.TryGetValue((row, col), out cd) && cd != null && cd.Style != null)
                return cd.Style.ForeColor;
            return this.ForeColor;
        }

        public bool GetCellBold(int row, int col) {
            EnsureInside(row, col);
            int ar, ac;
            if (TryGetMergeAnchor(row, col, out ar, out ac)) { row = ar; col = ac; }
            Cell cd;
            if (_cells.TryGetValue((row, col), out cd) && cd != null && cd.Style != null)
                return cd.Style.Bold;
            return false;
        }

        public ContentAlignment GetCellAlignment(int row, int col) {
            EnsureInside(row, col);
            int ar, ac;
            if (TryGetMergeAnchor(row, col, out ar, out ac)) { row = ar; col = ac; }
            Cell cd;
            if (_cells.TryGetValue((row, col), out cd) && cd != null && cd.Style != null)
                return cd.Style.Align;
            return ContentAlignment.MiddleRight;
        }
        #endregion

        #region --------------- フォントデータ取得  -----------------------
        // TableCell.cs に追加
        public float GetCellFontSize(int row, int col) {
            EnsureInside(row, col);
            int ar, ac;
            if (TryGetMergeAnchor(row, col, out ar, out ac)) { row = ar; col = ac; }

            Cell cd;
            if (_cells.TryGetValue((row, col), out cd) && cd?.Style != null) {
                // 個別サイズが正なら優先
                if (cd.Style.FontSize > 0) return cd.Style.FontSize;
            }
            // 既定(コントロールの Font.Size)
            return this.Font?.Size ?? 9f; // デフォルトはお好みで
        }

        // CellStyle に FontName がある場合はそれを使う。
        // 無い場合はテーブル既定の Font.Name を返す。
        public string GetCellFontName(int row, int col) {
            EnsureInside(row, col);
            int ar, ac;
            if (TryGetMergeAnchor(row, col, out ar, out ac)) { row = ar; col = ac; }

            Cell cd;
            if (_cells.TryGetValue((row, col), out cd) && cd?.Style != null) {
                // もし CellStyle に FontName プロパティを増やしたならこちらを優先
                if (!string.IsNullOrEmpty(cd.Style.FontName)) return cd.Style.FontName;
            }
            // 既定(コントロールの Font.Name)
            return this.Font?.Name ?? "Meiryo";
        }
        #endregion

        #region -------------------- 行指定してまとめて設定 -----------------------
        public void SetRowValuesAsLabels(
            int rowIndex,
            string[] values,
            ContentAlignment align = ContentAlignment.MiddleCenter,
            Color? backColor = null,
            Color? foreColor = null,
            float? fontSize = 14f,
            bool? bold = null)
        {
            if (values == null) return;
            for (int c = 0; c < values.Length && c < ColumnCount; c++)
            {
                Cell cd = GetCell(rowIndex, c);
                cd.Text = values[c] ?? "";
                CellStyle st = cd.Style;
                st.Align = align;
                if (backColor.HasValue) st.BackColor = backColor.Value;
                if (foreColor.HasValue) st.ForeColor = foreColor.Value;
                if (bold.HasValue) st.Bold = bold.Value;
                cd.Style = st;
            }
            Invalidate(GetRowBounds(rowIndex));
        }

        /// <summary>
        /// 指定行の [startCol..endCol] に values を流し込む。
        /// values は startCol から順に割り当てられ、足りなければ途中で終了、余れば切り捨て。
        /// editable でその範囲のセルの直接編集可否もまとめて設定できる。
        /// </summary>
        public void SetRowValuesAsLabels(
            int rowIndex,
            int startCol,
            int endCol,
            string[] values,
            ContentAlignment align = ContentAlignment.MiddleCenter,
            Color? backColor = null,
            Color? foreColor = null,
            float? fontSize = 14f,
            bool? bold = null,
            bool editable = false)
        {
            if (values == null) return;

            // 範囲をクリップ・検証
            if (rowIndex < 0 || rowIndex >= RowCount) return;
            if (startCol < 0) startCol = 0;
            if (endCol >= ColumnCount) endCol = ColumnCount - 1;
            if (startCol > endCol) return;

            // values のインデックスは startCol に合わせる
            int vi = 0;
            for (int c = startCol; c <= endCol; c++)
            {
                if (vi >= values.Length) break;

                Cell cd = GetCell(rowIndex, c);
                cd.Text = values[vi] ?? string.Empty;
                cd.IsEditable = editable;

                CellStyle st = cd.Style;
                st.Align = align;
                if (backColor.HasValue) st.BackColor = backColor.Value;
                if (foreColor.HasValue) st.ForeColor = foreColor.Value;
                if (bold.HasValue) st.Bold = bold.Value;
                if (fontSize.HasValue && fontSize.Value > 0f) st.FontSize = fontSize.Value;
                cd.Style = st;

                vi++;
            }

            // 指定範囲だけ再描画
            Rectangle rowBounds = GetRowBounds(rowIndex);
            if (!rowBounds.IsEmpty) Invalidate(rowBounds);
        }
        #endregion

        #region ===== レイアウト再計算と無効化 =====
        private void RecalcLayoutAndInvalidate() {
            int W = ClientSize.Width;
            int H = ClientSize.Height;

            // 列幅
            _colRects = new Rectangle[Math.Max(0, ColumnCount)];
            int fixedSum = 0, autoCols = 0;

            // 1) 自動配分の下準備(非表示列は除外)
            for (int c = 0; c < ColumnCount; c++)
            {
                if (_hiddenCols.Contains(c)) continue;              // 非表示は計算対象外
                int w = (ColumnWidths != null && c < ColumnWidths.Length) ? ColumnWidths[c] : -1;
                if (w >= 0) fixedSum += w; else autoCols++;
            }

            int autoW = Math.Max(0, autoCols > 0 ? (ClientSize.Width - fixedSum) / autoCols : 0);

            // 2) 実際の割り当て(非表示列は幅0/xは進めない)
            int x = 0;
            for (int c = 0; c < ColumnCount; c++)
            {
                int w;
                if (_hiddenCols.Contains(c))
                {
                    w = 0;  // ★ 非表示:幅0
                }
                else
                {
                    w = (ColumnWidths != null && c < ColumnWidths.Length && ColumnWidths[c] >= 0)
                        ? ColumnWidths[c]
                        : autoW;
                }

                _colRects[c] = new Rectangle(x, 0, Math.Max(0, w), ClientSize.Height);
                if (w > 0) x += w;   // ★ 幅0の列はxを進めない(画面上で“詰める”)
            }


            // 行高
            _rowTops = new int[Math.Max(0, RowCount + 1)];
            int fixedH = 0, autoRows = 0;
            for (int r = 0; r < RowCount; r++) {
                if (_hiddenRows.Contains(r)) continue;
                int h = (RowHeights != null && r < RowHeights.Length) ? RowHeights[r] : -1;
                if (h >= 0) fixedH += h; else autoRows++;
            }
            int remain = Math.Max(0, H - fixedH);
            int autoH = Math.Max(0, autoRows > 0 ? remain / autoRows : 0);

            int y = 0;
            for (int r = 0; r < RowCount; r++) {
                _rowTops[r] = y;
                int h = 0;
                if (!_hiddenRows.Contains(r)) {
                    int rh = (RowHeights != null && r < RowHeights.Length) ? RowHeights[r] : -1;
                    h = (rh >= 0) ? rh : autoH;
                }
                y += h;
            }
            _rowTops[RowCount] = y;

            // 載せているオーバレイの再配置
            foreach (KeyValuePair<(int, int), Cell> kv in _cells) {
                if (kv.Value.Overlay != null) {
                    Rectangle rc = GetMergedRect(kv.Key.Item1, kv.Key.Item2);
                    if (rc.IsEmpty) rc = GetCellRect(kv.Key.Item1, kv.Key.Item2);
                    kv.Value.Overlay.Bounds = Rectangle.Inflate(rc, -1, -1);
                }
            }

            Invalidate();
        }
        #endregion

        #region  ===== 描画 =====
        protected override void OnPaint(PaintEventArgs e) {
            base.OnPaint(e);
            Graphics g = e.Graphics;

            using (Pen pen = new Pen(Color.Silver)) {
                for (int r = 0; r < RowCount; r++) {
                    if (_rowTops.Length == 0) break;
                    int top = _rowTops[r];
                    int bottom = _rowTops[r + 1];
                    int h = Math.Max(0, bottom - top);
                    if (h <= 0) continue;

                    for (int c = 0; c < ColumnCount; c++) {
                        Rectangle cellRect = GetCellRect(r, c);
                        if (cellRect.Width <= 0 || cellRect.Height <= 0) continue;

                        // アンカーでなければ描かない(結合の重複描画防止)
                        ValueTuple<int, int> anchor = GetMergeAnchor(r, c);
                        if (!(anchor.Item1 == r && anchor.Item2 == c)) continue;

                        Rectangle mergedRect = GetMergedRect(r, c);

                        Cell cd = GetCell(r, c);

                        using (SolidBrush bg = new SolidBrush(cd.Style.BackColor))
                            g.FillRectangle(bg, mergedRect);

                        using (StringFormat sf = MakeStringFormat(cd.Style.Align))
                        using (SolidBrush fore = new SolidBrush(cd.Style.ForeColor)) {
                            // 置き換え前:
                            // Font f = cd.Style.Bold ? new Font(Font, FontStyle.Bold) : Font;

                            Font f;
                            float sz = (cd.Style != null && cd.Style.FontSize > 0f) ? cd.Style.FontSize : this.Font.Size;
                            FontStyle fs = (cd.Style != null && cd.Style.Bold) ? FontStyle.Bold : FontStyle.Regular;
                            if (Math.Abs(sz - this.Font.Size) > 0.01f || fs != this.Font.Style)
                            {
                                // サイズ/スタイルが既定と違う場合だけ新規フォントを作成
                                f = new Font(this.Font.FontFamily, sz, fs);
                            }
                            else
                            {
                                // 既定と同じなら既存を使う(Dispose不要)
                                f = this.Font;
                            }

                            Rectangle textRect = Rectangle.Inflate(mergedRect, -4, -2);
                            g.DrawString(cd.Text ?? "", f, fore, textRect, sf);
                        }

                        g.DrawRectangle(pen, Rectangle.Inflate(mergedRect, -1, -1));
                    }
                }
            }
        }



        public void DumpCell(object _, int r, int c) {
            Cell cd;
            if (_cells.TryGetValue((r, c), out cd)) {
                Debug.WriteLine("[" + r + "," + c + "] text='" + (cd.Text ?? "") + "'");
            } else {
                Debug.WriteLine("[" + r + "," + c + "] <empty>");
            }
        }
        #endregion

        #region  ===== 補助 =====
        private void EnsureInside(int r, int c) {
            if (r < 0 || c < 0 || r >= RowCount || c >= ColumnCount)
                throw new ArgumentOutOfRangeException("セル[" + r + "," + c + "] が範囲外です。");
        }

        private Rectangle GetCellRect(int r, int c) {
            if (_colRects.Length == 0 || _rowTops.Length == 0) return Rectangle.Empty;
            if (c < 0 || c >= ColumnCount || r < 0 || r >= RowCount) return Rectangle.Empty;
            Rectangle col = _colRects[c];
            return Rectangle.FromLTRB(col.Left, _rowTops[r], col.Right, _rowTops[r + 1]);
        }

        private Rectangle GetRowBounds(int row) {
            if (_rowTops.Length == 0 || row < 0 || row >= RowCount) return Rectangle.Empty;
            return Rectangle.FromLTRB(0, _rowTops[row], ClientSize.Width, _rowTops[row + 1]);
        }

        private ValueTuple<int, int> GetMergeAnchor(int r, int c) {
            foreach (MergedCell m in MergedCells) {
                if (r >= m.Row && r < m.Row + m.RowSpan &&
                    c >= m.Column && c < m.Column + m.ColSpan) {
                    return new ValueTuple<int, int>(m.Row, m.Column);
                }
            }
            return new ValueTuple<int, int>(r, c);
        }

        private Rectangle GetMergedRect(int r, int c) {
            ValueTuple<int, int> anc = GetMergeAnchor(r, c);
            int ar = anc.Item1; int ac = anc.Item2;
            if (ar != r || ac != c) return Rectangle.Empty;

            MergedCell m = MergedCells.FirstOrDefault(x => x.Row == ar && x.Column == ac);
            int rs = Math.Max(1, m != null ? m.RowSpan : 1);
            int cs = Math.Max(1, m != null ? m.ColSpan : 1);

            Rectangle rect = GetCellRect(ar, ac);
            for (int i = 1; i < cs; i++) rect = Rectangle.Union(rect, GetCellRect(ar, ac + i));
            for (int j = 1; j < rs; j++) rect = Rectangle.Union(rect, GetCellRect(ar + j, ac));
            return rect;
        }

        private void InvalidateCell(int r, int c) {
            Rectangle rc = GetMergedRect(r, c);
            if (rc.IsEmpty) rc = GetCellRect(r, c);
            if (!rc.IsEmpty) Invalidate(rc);
        }

        private static StringFormat MakeStringFormat(ContentAlignment align) {
            StringFormat sf = new StringFormat(StringFormatFlags.NoWrap);
            // 水平
            if (align == ContentAlignment.TopLeft || align == ContentAlignment.MiddleLeft || align == ContentAlignment.BottomLeft)
                sf.Alignment = StringAlignment.Near;
            else if (align == ContentAlignment.TopCenter || align == ContentAlignment.MiddleCenter || align == ContentAlignment.BottomCenter)
                sf.Alignment = StringAlignment.Center;
            else
                sf.Alignment = StringAlignment.Far;
            // 垂直
            if (align == ContentAlignment.TopLeft || align == ContentAlignment.TopCenter || align == ContentAlignment.TopRight)
                sf.LineAlignment = StringAlignment.Near;
            else if (align == ContentAlignment.MiddleLeft || align == ContentAlignment.MiddleCenter || align == ContentAlignment.MiddleRight)
                sf.LineAlignment = StringAlignment.Center;
            else
                sf.LineAlignment = StringAlignment.Far;
            return sf;
        }
        #endregion

        #region ===== 直接編集(インプレースエディタ) =====
        private TextBox _editor;
        private int _editRow = -1, _editCol = -1;
        private string _editOriginalText = null;

        public class CellEditEventArgs : EventArgs {
            public int Row { get; }
            public int Column { get; }
            public string OldText { get; }
            public string NewText { get; }
            public CellEditEventArgs(int r, int c, string oldT, string newT) { Row = r; Column = c; OldText = oldT; NewText = newT; }
        }
        public event EventHandler<CellEditEventArgs> CellEdited;

        public bool HitTest(Point p, out int row, out int col) {
            row = col = -1;
            if (_rowTops.Length < 2 || _colRects.Length == 0) return false;
            if (p.X < 0 || p.Y < 0 || p.X >= ClientSize.Width || p.Y >= ClientSize.Height) return false;

            // 列
            for (int c = 0; c < ColumnCount; c++) {
                var rc = _colRects[c];
                if (p.X >= rc.Left && p.X < rc.Right) { col = c; break; }
            }
            if (col < 0) return false;

            // 行(非表示はスキップ)
            for (int r = 0; r < RowCount; r++) {
                if (_hiddenRows.Contains(r)) continue;
                int top = _rowTops[r], bottom = _rowTops[r + 1];
                if (p.Y >= top && p.Y < bottom) { row = r; break; }
            }
            if (row < 0) return false;

            // 結合セル内ならアンカーに寄せる
            var anc = GetMergeAnchor(row, col);
            row = anc.Item1; col = anc.Item2;
            return true;
        }

        public void BeginEditAt(int row, int col, bool selectAll = true)
        {
            EnsureInside(row, col);

            // 結合セルはアンカーへ
            int ar, ac;
            if (TryGetMergeAnchor(row, col, out ar, out ac)) { row = ar; col = ac; }

            Cell cell = GetCell(row, col);

            // ★ 編集禁止なら戻る
            if (cell != null && !cell.IsEditable) return;

            // 既存Overlayがあるセルはここでは編集スキップ
            if (GetControlAt(row, col) != null) return;

            // 既存エディタを閉じる
            CancelEdit();

            _editRow = row; _editCol = col;
            _editOriginalText = GetCellText(row, col);

            _editor = new TextBox
            {
                BorderStyle = BorderStyle.FixedSingle,
                Font = this.Font,
                Text = _editOriginalText ?? "",
                Multiline = true,
            };

            // _editor = new TextBox { ... } の直後
            float sz = (cell.Style != null && cell.Style.FontSize > 0f) ? cell.Style.FontSize : this.Font.Size;
            FontStyle fs = (cell.Style != null && cell.Style.Bold) ? FontStyle.Bold : FontStyle.Regular;
            _editor.Font = (Math.Abs(sz - this.Font.Size) > 0.01f || fs != this.Font.Style)
                ? new Font(this.Font.FontFamily, sz, fs)
                : this.Font;


            Rectangle rc = GetMergedRect(row, col);
            if (rc.IsEmpty) rc = GetCellRect(row, col);
            _editor.Bounds = Rectangle.Inflate(rc, -1, -1);
            _editor.Margin = Padding.Empty;

            _editor.KeyDown += Editor_KeyDown;
            _editor.Leave += Editor_Leave;

            Controls.Add(_editor);
            _editor.BringToFront();
            _editor.Focus();
            if (selectAll) _editor.SelectAll();
        }

        public void CommitEdit() {
            if (_editor == null) return;
            var newText = _editor.Text;
            var oldText = _editOriginalText;
            int r = _editRow, c = _editCol;

            // 後始末より先にテキスト反映
            SetCellText(r, c, newText);

            // イベント通知
            CellEdited?.Invoke(this, new CellEditEventArgs(r, c, oldText, newText));

            TeardownEditor();
        }

        public void CancelEdit() {
            if (_editor == null) return;
            TeardownEditor(); // テキストは反映しない
        }

        private void TeardownEditor() {
            if (_editor != null) {
                _editor.KeyDown -= Editor_KeyDown;
                _editor.Leave -= Editor_Leave;
                Controls.Remove(_editor);
                _editor.Dispose();
                _editor = null;
            }
            _editRow = _editCol = -1;
            _editOriginalText = null;
        }

        private void Editor_KeyDown(object sender, KeyEventArgs e) {
            if (e.KeyCode == Keys.Enter) { e.Handled = true; CommitEdit(); } else if (e.KeyCode == Keys.Escape) { e.Handled = true; CancelEdit(); }
        }

        private void Editor_Leave(object sender, EventArgs e) {
            // フォーカスが外れたら確定(Excel風)
            CommitEdit();
        }

        protected override void OnMouseDoubleClick(MouseEventArgs e) {
            base.OnMouseDoubleClick(e);
            if (HitTest(e.Location, out int r, out int c)) BeginEditAt(r, c, selectAll: true);
        }

        protected override void OnKeyDown(KeyEventArgs e) {
            base.OnKeyDown(e);
            // F2で直前にクリックしたセルを編集、という操作を入れたい場合は
            // クリック済みセルを覚える必要があります。簡易版として編集中のみキー処理。
            if (_editor != null) return;

            if (e.KeyCode == Keys.F2) {
                // 直近クリックセルの保持をしない簡易実装:マウス位置で判定(フォーカス中のみ)
                var p = PointToClient(MousePosition);
                if (HitTest(p, out int r, out int c)) BeginEditAt(r, c, selectAll: true);
            }
        }

        protected override void OnResize(EventArgs e) {
            base.OnResize(e);
            RecalcLayoutAndInvalidate();

            if (_editor != null && _editRow >= 0 && _editCol >= 0) {
                var rc = GetMergedRect(_editRow, _editCol);
                if (rc.IsEmpty) rc = GetCellRect(_editRow, _editCol);
                _editor.Bounds = Rectangle.Inflate(rc, -1, -1);
            }
        }

        #endregion

        #region  ---------------------- セルのタグ操作 -----------------------
        public IReadOnlyDictionary<string, string> GetCellDictionaryReadonly(int row, int col) {
            var d = GetCellDictionary(row, col);
            return d == null ? null : new System.Collections.ObjectModel.ReadOnlyDictionary<string, string>(d);
        }

        public string GetCellTag(int row, int col, string key, string defaultValue = null) {
            var d = GetCellDictionary(row, col);
            if (d != null && key != null && d.TryGetValue(key, out var v)) return v;
            return defaultValue;
        }

        public void SetCellTag(int row, int col, string key, string value) {
            EnsureInside(row, col);
            var cd = GetCell(row, col);
            if (cd.Dict == null) cd.Dict = new Dictionary<string, string>();
            if (value == null) cd.Dict.Remove(key);
            else cd.Dict[key] = value;
        }

        public bool RemoveCellTag(int row, int col, string key) {
            var d = GetCellDictionary(row, col);
            return (d != null) && d.Remove(key);
        }

        // ====== 辞書ツールチップ ======
        private ToolTip _dictTip;
        private bool _dictTipInitialized;
        private int _tipAutoPop = 8000, _tipInitial = 400, _tipReshow = 200;
        private bool _showDictionaryToolTip = true;

        private int _hoverRow = -1, _hoverCol = -1;   // 直近ホバーセル(アンカー座標)
        private readonly string[] _tipPreferredKeys = new[] { "Text", "Id", "ColumnName", "AnalysisRatio", "ToolTip" };

        [Browsable(true), Category("Behavior"), Description("辞書の内容をツールチップで表示するかどうか")]
        public bool ShowDictionaryToolTip { get => _showDictionaryToolTip; set { _showDictionaryToolTip = value; if (!value) HideDictionaryToolTip(); } }

        [Browsable(true), Category("Behavior")]
        public int ToolTipAutoPopDelay { get => _tipAutoPop; set { _tipAutoPop = value; ApplyDictTipDelays(); } }

        [Browsable(true), Category("Behavior")]
        public int ToolTipInitialDelay { get => _tipInitial; set { _tipInitial = value; ApplyDictTipDelays(); } }

        [Browsable(true), Category("Behavior")]
        public int ToolTipReshowDelay { get => _tipReshow; set { _tipReshow = value; ApplyDictTipDelays(); } }

        private void EnsureDictTip() {
            if (_dictTipInitialized || !ShowDictionaryToolTip) return;
            _dictTip = new ToolTip { IsBalloon = true, UseAnimation = true, UseFading = true, ShowAlways = true };
            _dictTipInitialized = true;
            ApplyDictTipDelays();
        }

        private void ApplyDictTipDelays() {
            if (_dictTip == null) return;
            _dictTip.AutoPopDelay = _tipAutoPop;
            _dictTip.InitialDelay = _tipInitial;
            _dictTip.ReshowDelay = _tipReshow;
        }

        private void HideDictionaryToolTip() {
            _dictTip?.Hide(this);
            _hoverRow = _hoverCol = -1;
        }

        private static string BuildDictionaryTipText(Dictionary<string, string> dict, IEnumerable<string> preferredKeys) {
            if (dict == null || dict.Count == 0) return string.Empty;
            var sb = new System.Text.StringBuilder();

            // 優先キー(順序固定)
            if (preferredKeys != null) {
                foreach (var k in preferredKeys) {
                    if (dict.TryGetValue(k, out var v) && !string.IsNullOrWhiteSpace(v))
                        sb.AppendLine($"{k} : {v}");
                }
            }

            // 残りのキー(空でないもの、重複除外)
            foreach (var kv in dict) {
                if (preferredKeys != null && preferredKeys.Contains(kv.Key)) continue;
                if (!string.IsNullOrWhiteSpace(kv.Value))
                    sb.AppendLine($"{kv.Key} : {kv.Value}");
            }

            return sb.ToString().TrimEnd();
        }



        protected override void OnMouseLeave(EventArgs e) {
            base.OnMouseLeave(e);
            HideDictionaryToolTip();
        }

        // ヒットテスト(表示ON/OFF用):既存 HitTest と同等だが内部用に簡易版
        private bool HitTestForTip(Point p, out int row, out int col) {
            row = col = -1;
            if (p.X < 0 || p.Y < 0 || p.X >= ClientSize.Width || p.Y >= ClientSize.Height) return false;

            // 列
            for (int c = 0; c < ColumnCount; c++) {
                var rc = _colRects[c];
                if (p.X >= rc.Left && p.X < rc.Right) { col = c; break; }
            }
            if (col < 0) return false;

            // 行(非表示スキップ)
            for (int r = 0; r < RowCount; r++) {
                if (_hiddenRows.Contains(r)) continue;
                int top = _rowTops[r], bottom = _rowTops[r + 1];
                if (p.Y >= top && p.Y < bottom) { row = r; break; }
            }
            if (row < 0) return false;

            // 結合はアンカーへ
            var anc = GetMergeAnchor(row, col);
            row = anc.Item1; col = anc.Item2;
            return true;
        }
        #endregion

        #region ---------------------- 列リサイズ マウスイベント -----------------------
        private const int _grip = 4;          // つかみ判定のピクセル
        private int _resizeCol = -1;          // つかみ中の列
        private int _resizeStartX;            // マウス開始X
        private int _resizeStartW;            // 開始時の列幅
        private int _minColW = 24;            // 最小幅

        private int HitTestColEdge(int x)
        {
            if (_colRects == null) return -1;
            for (int c = 0; c < ColumnCount - 1; c++)
            {
                if (_colRects[c].Width <= 0 || _colRects[c + 1].Width <= 0) continue; // ★ 非表示の境界は無視
                int edge = _colRects[c].Right;
                if (Math.Abs(x - edge) <= _grip) return c;
            }
            return -1;
        }


        protected override void OnMouseMove(MouseEventArgs e)
        {
            base.OnMouseMove(e);

            // ===== 1) 列リサイズ中(ドラッグ中)は幅更新のみ =====
            if (_resizeCol >= 0)
            {
                int dx = e.X - _resizeStartX;

                // auto列(-1)はドラッグ開始時に固定化
                if (ColumnWidths == null || ColumnWidths.Length < ColumnCount)
                {
                    Array.Resize(ref _columnWidths, ColumnCount);
                    for (int i = 0; i < ColumnCount; i++)
                        if (_columnWidths[i] == 0) _columnWidths[i] = -1;
                }
                if (_columnWidths[_resizeCol] < 0)
                    _columnWidths[_resizeCol] = _colRects[_resizeCol].Width;

                _columnWidths[_resizeCol] = Math.Max(_minColW, _resizeStartW + dx);
                RecalcLayoutAndInvalidate();

                // ドラッグ中はツールチップを抑止
                HideDictionaryToolTip();
                return;
            }

            // ===== 2) 列境界の上にいるか(リサイズ準備) =====
            var edgeCol = HitTestColEdge(e.X);
            if (edgeCol >= 0)
            {
                Cursor = Cursors.SizeWE;
                // リサイズ意図がある時はツールチップ非表示で邪魔しない
                HideDictionaryToolTip();
                return;
            }
            Cursor = Cursors.Default;

            // ===== 3) 辞書ツールチップ(通常ホバー時のみ) =====
            if (!ShowDictionaryToolTip) return;

            // レイアウト未計算なら何もしない
            if (_rowTops == null || _rowTops.Length < 2 || _colRects == null || _colRects.Length == 0) return;

            EnsureDictTip();

            // ヒットテスト(結合はアンカーへ寄せる)
            int r, c;
            if (!HitTestForTip(e.Location, out r, out c))
            {
                if (_hoverRow != -1 || _hoverCol != -1) HideDictionaryToolTip();
                return;
            }

            // セルが変わった時だけ更新
            if (r != _hoverRow || c != _hoverCol)
            {
                _hoverRow = r; _hoverCol = c;

                var cd = _cells.TryGetValue((r, c), out var d) ? d : null;
                var tip = BuildDictionaryTipText(cd?.Dict, _tipPreferredKeys);

                if (string.IsNullOrWhiteSpace(tip))
                {
                    HideDictionaryToolTip();
                    return;
                }

                // 位置はカーソル近く(相対座標)
                _dictTip.Show(tip, this, e.Location + new Size(16, 16), _tipAutoPop);
            }
        }

        protected override void OnMouseDown(MouseEventArgs e)
        {
            base.OnMouseDown(e);
            if (e.Button == MouseButtons.Left)
            {
                int edgeCol = HitTestColEdge(e.X);
                if (edgeCol >= 0)
                {
                    _resizeCol = edgeCol;
                    _resizeStartX = e.X;
                    _resizeStartW = _colRects[_resizeCol].Width;
                    Capture = true;
                }
            }
        }

        protected override void OnMouseUp(MouseEventArgs e)
        {
            base.OnMouseUp(e);
            if (_resizeCol >= 0)
            {
                _resizeCol = -1;
                Capture = false;
                Cursor = Cursors.Default;
            }
        }
        #endregion ---------------------- 列リサイズ -----------------------

        #region ---------------------- 行と列の取得と非表示関数 -----------------------
        public void SetRowVisible(int rowIndex, bool visible)
        {
            if (rowIndex < 0 || rowIndex >= RowCount) return;
            if (!visible) _hiddenRows.Add(rowIndex); else _hiddenRows.Remove(rowIndex);
            RecalcLayoutAndInvalidate(); // ★ ここを Invalidate → 再計算に
        }

        public bool IsRowHidden(int rowIndex) { return _hiddenRows.Contains(rowIndex); }

        /// <summary>
        /// 非表示にしていない行の数を返す
        /// </summary>
        public int GetTotalVisibleRowCount()
        {
            return RowCount - _hiddenRows.Count;
        }

        /// <summary>列の表示/非表示を切り替える</summary>
        public void SetColumnVisible(int colIndex, bool visible)
        {
            if (colIndex < 0 || colIndex >= ColumnCount) return;
            if (!visible) _hiddenCols.Add(colIndex); else _hiddenCols.Remove(colIndex);
            RecalcLayoutAndInvalidate();
        }

        /// <summary>列が非表示かどうか</summary>
        public bool IsColumnHidden(int colIndex) => _hiddenCols.Contains(colIndex);

        /// <summary>非表示でない列の数</summary>
        public int GetTotalVisibleColumnCount() => ColumnCount - _hiddenCols.Count;

        #endregion

    }
}

2
3
0

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
2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?