using KssSmaPlaLib.Commons; using KssSmaPlaLib.Forms.ContextMenuFunction; using KssSmaPlaLib.Ini; using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace KssSmaPlaLib.Forms { public class DgvHelper { /// /// 汎用的な初期化 /// /// public static void Initialize(DataGridView dgv) { // 行選択 dgv.SelectionMode = DataGridViewSelectionMode.FullRowSelect; // 行ヘッダ消去 dgv.RowHeadersVisible = false; // 編集不可 dgv.ReadOnly = true; // ユーザ操作制限 dgv.AllowUserToAddRows = false; dgv.AllowUserToDeleteRows = false; dgv.AllowUserToOrderColumns = false; dgv.AllowUserToResizeColumns = true; dgv.AllowUserToResizeRows = false; // 列ヘッダはセンタリング dgv.ColumnHeadersDefaultCellStyle.Alignment=DataGridViewContentAlignment.MiddleCenter; //// ソート用余白を消す //dgv.ColumnHeadersDefaultCellStyle.Padding = new Padding(0); // セル内余白(上下左右)をゼロに dgv.DefaultCellStyle.Padding = new Padding(0); // 行高さの自動調整 dgv.AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.AllCells; // ダブルバッファ(プロパティが隠れている) var type = typeof(DataGridView); var prop = type.GetProperty("DoubleBuffered", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic); prop.SetValue(dgv, true, null); } /// /// 列ヘッダのソート用余白を消す /// ※列が確定してからCallすること /// /// //public static void ClearColumnHeaderSortYohaku(DataGridView dgv) //{ // // 列ヘッダの幅にソート用の幅は不要 // //foreach (DataGridViewColumn col in dgv.Columns) col.SortMode = DataGridViewColumnSortMode.NotSortable; // // 何をやってもダメなので、テキストの先頭に余白入れます!👉ダメ!orz // //const string YOHAKU = "       "; // //foreach (DataGridViewColumn col in dgv.Columns) col.HeaderText = YOHAKU + col.HeaderText; //} // どちらのDTOでも使える共通バインド関数 public static void BindListWithAttributeHeaders( DataGridView grid, IList source, bool isForce = false) { // 強制でないとき、データソースありなら何もしない if (!(grid.DataSource is null) && !isForce) return; // データバインド完了後にヘッダー等を属性で上書き grid.DataBindingComplete += (_, __) => ApplyAttributeHeaders(grid); // 「とりあえず ToString() で文字列表示」したい場合の統一フォーマット grid.CellFormatting += (_, e) => { try { // AutoGenerateColumnsだと各セルの Value はプロパティ値になります // ここで ToString() 統一(nullは空文字に) if (e.Value == null) { e.Value = string.Empty; e.FormattingApplied = true; return; } // 文化依存や日付/数値の既定書式を崩したくなければコメントアウト推奨 if (!(e.Value is bool) && string.IsNullOrEmpty(grid.Columns[e.ColumnIndex].DefaultCellStyle.Format)) { e.Value = e.Value.ToString(); e.FormattingApplied = true; } } catch (Exception ex) { MessageBox.Show(ex.Message); } }; grid.AutoGenerateColumns = true; // 変更通知を効かせたい場合は BindingList に包む var bindingList = source is BindingList bl ? bl : new BindingList(source); // BindingSource 経由が安定 var bs = new BindingSource { DataSource = bindingList }; grid.DataSource = bs; } private const string KEY_NAME_DISPLAY_NAME_CSV = "DisplayNameCsv"; /// /// プライベートな子関数 /// /// /// public static void ApplyAttributeHeaders(DataGridView grid) { // 編集した表示名 var csv = IniHelper.ReadValue(null, grid.Parent.Name + "." + grid.Name, KEY_NAME_DISPLAY_NAME_CSV); var props = TypeDescriptor.GetProperties(typeof(T)) .Cast() .ToDictionary(p => p.Name); foreach (DataGridViewColumn col in grid.Columns) { var key = col.DataPropertyName ?? col.Name; if (key is null) continue; if (props.TryGetValue(key, out var pd)) { // タグに入れておく(PropertyDescriptor型) col.Tag = pd; // 列ヘッダーを [DisplayName] に col.HeaderText = pd.DisplayName; // 表示名を登録されてたら、置き換える var editName = StringUxer.GetStringByIndexFormCsv(csv,col.Index,string.Empty); if (!string.IsNullOrEmpty(editName)) col.HeaderText = editName; // 文字寄せ var alignment = (col.HeaderText.IndexOf("[R]") >= 0 ? DataGridViewContentAlignment.MiddleRight : (col.HeaderText.IndexOf("[C]") >= 0 ? DataGridViewContentAlignment.MiddleCenter : DataGridViewContentAlignment.MiddleLeft)); col.DefaultCellStyle.Alignment = alignment; col.HeaderText = col.HeaderText.Replace("[L]", "").Replace("[C]", "").Replace("[R]", ""); // [Browsable(false)] の列は非表示(AutoGenerateでも列が立つ場合の保険) var browsableAttr = pd.Attributes.OfType().FirstOrDefault(); if (browsableAttr != null && !browsableAttr.Browsable) { col.Visible = false; continue; } // 読み取り専用属性の尊重 col.ReadOnly = pd.IsReadOnly; // 便利系:ソートを自動に // col.SortMode = DataGridViewColumnSortMode.Automatic; // 必要なら列幅の初期値 // col.AutoSizeMode = DataGridViewAutoSizeColumnMode.AllCells; } } } private const string INI_KEY_COL_WIDTH_CSV = "ColWidthCsv"; /// /// 列幅保存(FormのFormClosingにてCall) /// /// public static void DgvSaveColWidth(DataGridView dgv) { var section = dgv.Parent.Name + "." + dgv.Name; var list = new List(); foreach(DataGridViewColumn col in dgv.Columns) list.Add(col.Width); IniHelper.WriteValue(null, section, INI_KEY_COL_WIDTH_CSV, string.Join(",", list)); } /// /// 列幅復元(FormのコンストラクタにてCall) /// /// public static void DgvLoadColWidth(DataGridView dgv) { var section = dgv.Parent.Name + "." + dgv.Name; var list = IniHelper.ReadValue(null, section,INI_KEY_COL_WIDTH_CSV).Split(',').ToList(); foreach (DataGridViewColumn col in dgv.Columns) { if (list.Count - 1 < col.Index) break; if (!int.TryParse(list[col.Index], out int val)) break; col.Width = val; } } private const string INI_KEY_FONT_CSV = "Font"; public static void DgvSelectFont(Form form, DataGridView dgv) { if (!FontUxer.SelectFont(form, form.Name, dgv.Name + ".Font", dgv.DefaultCellStyle.Font, out var font)) return; DgvSetFontFromFont(dgv, font); DgvSaveFontToIni(dgv); } public static void DgvSetFontFromFont(DataGridView dgv, Font font) { var font2 = new Font(font.Name, font.Size, font.Bold ? FontStyle.Bold : FontStyle.Regular); dgv.DefaultCellStyle.Font = font2; dgv.ColumnHeadersDefaultCellStyle.Font = font2; dgv.AutoResizeRows(); } public static void DgvSaveFontToIni(DataGridView dgv) { var fontCsv = dgv.ColumnHeadersDefaultCellStyle.Font.Name + "," + dgv.ColumnHeadersDefaultCellStyle.Font.Size.ToString() + "," + dgv.ColumnHeadersDefaultCellStyle.Font.Bold.ToString(); IniHelper.WriteValue(null, dgv.Parent.Name + "." + dgv.Name, INI_KEY_FONT_CSV, fontCsv); } public static void DgvLoadFontFromIni(DataGridView dgv) { var defaultFontCsv = dgv.ColumnHeadersDefaultCellStyle.Font.Name + "," + dgv.ColumnHeadersDefaultCellStyle.Font.Size.ToString() + "," + dgv.ColumnHeadersDefaultCellStyle.Font.Bold.ToString(); var iniFontCsv = IniHelper.ReadValue(null, dgv.Parent.Name + "." + dgv.Name, INI_KEY_FONT_CSV, defaultFontCsv); var fontItems = iniFontCsv.Split(','); if (fontItems.Length != 3) fontItems = defaultFontCsv.Split(','); if (float.TryParse(fontItems[1], out var size) == false) size = dgv.ColumnHeadersDefaultCellStyle.Font.Size; if (bool.TryParse(fontItems[2], out var bold) == false) bold = dgv.ColumnHeadersDefaultCellStyle.Font.Bold; var font = new Font(fontItems[0], size, bold ? FontStyle.Bold : FontStyle.Regular); dgv.DefaultCellStyle.Font = font; dgv.ColumnHeadersDefaultCellStyle.Font = font; dgv.AutoResizeRows(); } private const string KEY_NAME_DISPLAY_INDEX_CSV = "DisplayIndexCsv"; private const string KEY_NAME_COLUMN_VISIBLE_CSV = "ColumnVisibleCsv"; /// /// INIの列表示順をDGVに反映 /// /// public static void SetColumnDisplayIndexFromIni(DataGridView dgv) { // 表示順(DisplayIndex)の設定 var csv = IniHelper.ReadValue(null, dgv.Parent.Name + "." + dgv.Name, KEY_NAME_DISPLAY_INDEX_CSV); if (string.IsNullOrEmpty(csv)) return; var items = csv.Split(','); if (items.Length != dgv.Columns.Count) return; List displayIndexList = new List(); foreach ( var item in items) displayIndexList.Add(int.Parse(item)); for (var cnt = 0; cnt < dgv.Columns.Count; cnt++) { var index = displayIndexList.IndexOf(cnt); dgv.Columns[index].DisplayIndex = cnt; } // 列の表示/非表示の設定 csv = IniHelper.ReadValue(null, dgv.Parent.Name + "." + dgv.Name, KEY_NAME_COLUMN_VISIBLE_CSV); if (string.IsNullOrEmpty(csv)) return; items = csv.Split(','); if (items.Length != dgv.Columns.Count) return; List visibleList = new List(); foreach (var item in items) visibleList.Add(bool.TryParse(item, out var boolValue) ? boolValue : true); for (var cnt = 0; cnt < dgv.Columns.Count; cnt++) { dgv.Columns[cnt].Visible = visibleList[cnt]; } } public static void MoveRow(DataGridView dgv, int rowIndex, bool moveUp) { // 移動先のインデックスを計算 int targetIndex = moveUp ? rowIndex - 1 : rowIndex + 1; // 範囲チェック(一番上より上、または一番下より下には行かない) if (targetIndex < 0 || targetIndex >= dgv.Rows.Count - (dgv.AllowUserToAddRows ? 1 : 0)) { return; } // 選択状態などを保持したい場合の準備 DataGridViewRow row = dgv.Rows[rowIndex]; // 一旦削除して挿入し直す dgv.Rows.RemoveAt(rowIndex); dgv.Rows.Insert(targetIndex, row); // 移動後の行を選択状態にする(ユーザー体験向上) dgv.ClearSelection(); dgv.Rows[targetIndex].Selected = true; dgv.CurrentCell = dgv.Rows[targetIndex].Cells[0]; } private const string INI_KEY_DGV_ROW_HEADER_HEIGHT = "DgvRowHeaderHeight"; /// /// ヘッダ行の高さ保存 /// /// public static void DgvSaveHeaderHeight(DataGridView dgv) { var section = dgv.Parent.Name + "." + dgv.Name; IniHelper.WriteValue(null, section, INI_KEY_DGV_ROW_HEADER_HEIGHT, dgv.ColumnHeadersHeight.ToString()); } /// /// ヘッダ行の高さロード /// /// public static void DgvLoadHeaderHeight(DataGridView dgv) { var section = dgv.Parent.Name + "." + dgv.Name; var str = IniHelper.ReadValue(null, section, INI_KEY_DGV_ROW_HEADER_HEIGHT, dgv.ColumnHeadersHeight.ToString()); dgv.ColumnHeadersHeight = int.TryParse(str, out var intValue) ? intValue : dgv.ColumnHeadersHeight; } /// /// 非表示列の再表示 /// /// public static void DgvRedispAllColumn(DataGridView dgv) { foreach (DataGridViewColumn col in dgv.Columns) col.Visible = true; } private const string INI_KEY_COL_VISIBLE_CSV = "ColVisibleCsv"; /// /// 列の表示非表示を保存 /// /// public static void DgvSaveColVisible(DataGridView dgv) { var section = dgv.Parent.Name + "." + dgv.Name; var list = new List(); foreach (DataGridViewColumn col in dgv.Columns) list.Add(col.Visible); IniHelper.WriteValue(null, section, INI_KEY_COL_VISIBLE_CSV, string.Join(",", list)); } /// /// 列の表示非表示をロード /// /// public static void DgvLoadColVisible(DataGridView dgv) { var section = dgv.Parent.Name + "." + dgv.Name; var list = IniHelper.ReadValue(null, section, INI_KEY_COL_VISIBLE_CSV).Split(',').ToList(); foreach (DataGridViewColumn col in dgv.Columns) { if (list.Count - 1 < col.Index) break; if (!bool.TryParse(list[col.Index], out bool val)) break; col.Visible = val; } } private const string INI_KEY_COL_DISPLAY_INDEX_CSV = "ColDisplayIndexCsv"; /// /// 列の表示順インデックスを保存 /// /// public static void DgvSaveColDisplayIndex(DataGridView dgv) { var section = dgv.Parent.Name + "." + dgv.Name; var list = new List(); foreach (DataGridViewColumn col in dgv.Columns) list.Add($"{col.Name}={col.DisplayIndex}"); IniHelper.WriteValue(null, section, INI_KEY_COL_DISPLAY_INDEX_CSV, string.Join(",", list)); } /// /// 列の表示順インデックスをロード /// /// public static void DgvLoadColDisplayIndex(DataGridView dgv) { var section = dgv.Parent.Name + "." + dgv.Name; var list = IniHelper.ReadValue(null, section, INI_KEY_COL_DISPLAY_INDEX_CSV).Split(',').ToList(); if (list.Count == 1 && string.IsNullOrEmpty(list[0])) return; Dictionary dic = new Dictionary(); foreach (var str in list) { dic.Add(int.Parse(str.Split('=')[1]), str.Split('=')[0]); } // DisplayIndexは小さい方から設定しないと崩れる foreach (var di in dic.Keys.OrderBy(m => m)) { dgv.Columns[dic[di]].DisplayIndex = di; } } /// /// 列幅の自動調整 /// /// public static void DgvColumnWidthAutoAdjust(DataGridView dgv) { dgv.AutoResizeColumns(DataGridViewAutoSizeColumnsMode.AllCells); } /// /// 行高さの自動調整 /// /// public static void DgvRowHeightAutoAdjust(DataGridView dgv) { dgv.AutoResizeRows(DataGridViewAutoSizeRowsMode.AllCells); } /// /// 列固定 /// /// public static void DgvColumnFix(DataGridView dgv) { if (dgv.CurrentCell == null) return; // 1. 今クリックした列の「見た目の位置」を取得 int targetDisplayIndex = dgv.CurrentCell.OwningColumn.DisplayIndex; // 2. いったん全列の固定を解除(これをしないとエラーになる場合がある) DgvColumnFixKaijo(dgv); // 3. 見た目の位置(DisplayIndex)が、ターゲット以下の列をすべて固定! // ※DisplayIndexの小さい順(左側から順)にFrozenをかけていくのがコツ var targetColumns = dgv.Columns.Cast() .OrderBy(c => c.DisplayIndex); foreach (var col in targetColumns) { if (col.DisplayIndex <= targetDisplayIndex) { col.Frozen = true; } } } /// /// 列固定解除 /// /// public static void DgvColumnFixKaijo(DataGridView dgv) { // 2. いったん全列の固定を解除(これをしないとエラーになる場合がある) foreach (DataGridViewColumn col in dgv.Columns) { col.Frozen = false; } } private const string INI_KEY_COL_FROZEN_CSV = "ColFrozenCsv"; /// /// 列の固定を保存 /// /// public static void DgvSaveColFrozen(DataGridView dgv) { var section = dgv.Parent.Name + "." + dgv.Name; var list = new List(); foreach (DataGridViewColumn col in dgv.Columns) list.Add(col.Frozen); IniHelper.WriteValue(null, section, INI_KEY_COL_FROZEN_CSV, string.Join(",", list)); } /// /// 列の固定をロード /// /// public static void DgvLoadColFrozen(DataGridView dgv) { var section = dgv.Parent.Name + "." + dgv.Name; var list = IniHelper.ReadValue(null, section, INI_KEY_COL_FROZEN_CSV).Split(',').ToList(); foreach (DataGridViewColumn col in dgv.Columns) { if (list.Count - 1 < col.Index) break; if (!bool.TryParse(list[col.Index], out bool val)) break; col.Frozen = val; } } private const string INI_KEY_DGV_HEADER_BACK_COLOR = "DgvHeaderBackColor"; /// /// ヘッダ背景色選択 /// /// /// public static void DgvSelectHeaderBackColor(Form form, DataGridView dgv) { using (ColorDialog cd = new ColorDialog()) { // 今の色を初期選択にしておく cd.Color = dgv.ColumnHeadersDefaultCellStyle.BackColor; if (cd.ShowDialog() == DialogResult.OK) { // 1. フォームの背景色を即時変更 dgv.ColumnHeadersDefaultCellStyle.BackColor = cd.Color; // 文字色を計算して算出し設定 dgv.ColumnHeadersDefaultCellStyle.ForeColor = ColorHelper.GetContrastColor(dgv.ColumnHeadersDefaultCellStyle.BackColor); // 2. 🔥 Colorを16進数(#RRGGBB)に変換してINIに書き込み string colorHex = ColorTranslator.ToHtml(cd.Color); IniHelper.WriteValue(null, form.Name, INI_KEY_DGV_HEADER_BACK_COLOR, colorHex); } } } /// /// ヘッダ背景色(名前でマッチした列のみ)選択 /// /// /// /// /// public static void DgvSelectHeaderBackColorWithNameMatch(Form form, DataGridView dgv, string colNamePattern, string iniKey) { // マッチする列を探し、現在の色を採取 Color? color = null; foreach (DataGridViewColumn col in dgv.Columns) { if (StringUxer.WildcardMatch(col.Name, colNamePattern)) { color = col.HeaderCell.Style.BackColor; } } if (color == null) { MessageBox.Show(form, $"パターンにマッチする列なし[パターン:{colNamePattern}]"); return; } using (ColorDialog cd = new ColorDialog()) { // 今の色を初期選択にしておく cd.Color = (Color)color; if (cd.ShowDialog() == DialogResult.OK) { var backColor = cd.Color; // 文字色を計算して算出し設定 var foreColor = ColorHelper.GetContrastColor(backColor); // DGVに反映 foreach (DataGridViewColumn col in dgv.Columns) { if (StringUxer.WildcardMatch(col.Name, colNamePattern)) { col.HeaderCell.Style.BackColor = backColor; col.HeaderCell.Style.ForeColor = foreColor; } } // 2. 🔥 Colorを16進数(#RRGGBB)に変換してINIに書き込み string colorHex = ColorTranslator.ToHtml(cd.Color); IniHelper.WriteValue(null, form.Name, iniKey, colorHex); } } } /// /// ヘッダ背景色をINIから取得して設定 /// /// public static void DgvSetHeaderBackColorFromIni(Form form, DataGridView dgv) { var colorHex = IniHelper.ReadValue(null, form.Name, INI_KEY_DGV_HEADER_BACK_COLOR, ColorTranslator.ToHtml(dgv.ColumnHeadersDefaultCellStyle.BackColor)); dgv.ColumnHeadersDefaultCellStyle.BackColor = ColorTranslator.FromHtml(colorHex); // 文字色を計算して算出し設定 dgv.ColumnHeadersDefaultCellStyle.ForeColor = ColorHelper.GetContrastColor(dgv.ColumnHeadersDefaultCellStyle.BackColor); } /// /// ヘッダ背景色をINIから取得して設定(名前マッチング) /// /// /// /// /// public static void DgvSetHeaderBackColorFromIniWithNameMatch(Form form, DataGridView dgv, string colNamePattern, string iniKey) { // マッチする列を探し、現在の色を採取 Color? color = null; foreach (DataGridViewColumn col in dgv.Columns) { if (StringUxer.WildcardMatch(col.Name, colNamePattern)) { color = col.HeaderCell.Style.BackColor; } } if (color == null) { MessageBox.Show(form, $"パターンにマッチする列なし[パターン:{colNamePattern}]"); return; } // INIから取得 var colorHex = IniHelper.ReadValue(null, form.Name, iniKey, ColorTranslator.ToHtml((Color)color)); var backColor = ColorTranslator.FromHtml(colorHex); var foreColor = ColorHelper.GetContrastColor(backColor); // マッチする列に設定 foreach (DataGridViewColumn col in dgv.Columns) { if (StringUxer.WildcardMatch(col.Name, colNamePattern)) { col.HeaderCell.Style.BackColor = backColor; col.HeaderCell.Style.ForeColor = foreColor; } } } public const string TSV_PATH = @".\@@@DGV@@@.tsv"; /// /// DGVデータTSV保存 /// /// /// public static void SaveGridDataToTsv(Form form, DataGridView dgv) { var path = TSV_PATH.Replace("@@@DGV@@@", $"{form.Name}.{dgv.Name}"); try { using (StreamWriter sw = new StreamWriter(path, false, Encoding.UTF8)) { foreach (DataGridViewRow row in dgv.Rows) { // 新規行(入力用の空行)はスキップ if (row.IsNewRow) continue; // 各セルの値をタブで結合 var cells = row.Cells.Cast().Select(c => c.Value?.ToString() ?? ""); sw.WriteLine(string.Join("\t", cells)); } } } catch (Exception ex) { MessageBox.Show("DGVのTSV保存に失敗しました: " + ex.Message); } } public static void LoadGridDataToTsv(Form form, DataGridView dgv) { var path = TSV_PATH.Replace("@@@DGV@@@", $"{form.Name}.{dgv.Name}"); if (!File.Exists(path)) return; try { dgv.Rows.Clear(); string[] lines = File.ReadAllLines(path, Encoding.UTF8); foreach (string line in lines) { if (string.IsNullOrWhiteSpace(line)) continue; // タブで分割して行を追加 string[] values = line.Split('\t'); dgv.Rows.Add(values); } } catch (Exception ex) { MessageBox.Show("DGVのTSVによる復元に失敗しました: " + ex.Message); } } public static void SearchGrid( DataGridView dgv, string searchText, bool forward) { if (string.IsNullOrEmpty(searchText)) return; int rowCount = dgv.RowCount; int colCount = dgv.ColumnCount; // 現在のセルの位置を取得(選択されていなければ 0,0 から) int startRow = dgv.CurrentCell?.RowIndex ?? 0; int startCol = dgv.CurrentCell?.ColumnIndex ?? 0; // 全セルをフラットなインデックスとして考えて計算 int totalCells = rowCount * colCount; int startIndex = (startRow * colCount) + startCol; for (int i = 1; i < totalCells; i++) { // forwardがtrueならプラス、falseならマイナス方向にずらす int offset = forward ? i : -i; int nextIndex = (startIndex + offset + totalCells) % totalCells; int r = nextIndex / colCount; int c = nextIndex % colCount; DataGridViewCell cell = dgv[c, r]; if (cell.Value != null && cell.Value.ToString().Contains(searchText)) { dgv.CurrentCell = cell; return; // 見つかったら終了 } } MessageBox.Show("他には見つかりませんでしたにゃ!🐾"); } } }