DataGridView:進捗率をプログレスバー表示・数値編集(サンプルあり)

📋 目次(クリックで展開)

DataGridView で、進捗状況などの数値を普段は視覚的なプログレスバー(横バー)で表示し、ダブルクリックして編集モードに入ったときだけ通常の数値入力に切り替えたいという要件は、業務システムでよくあります。

この「非編集時はグラフィカルな表示、編集時は標準の数値入力」という挙動は、標準の DataGridView が持つ CellPainting イベントCellValidating イベント を組み合わせることで綺麗に実現できます。さらに、実務で必須となる「0〜100 以外の不正な値や文字列の入力をガッチリ制限するバリデーション機能」もスマートに組み込むことができます。

今回は、標準機能のみで実装するプログレスバー切り替え機能と、堅牢な入力検証の実装手順について解説します。

表示・編集の切り替えと入力制限の仕組み

この実装の核となるのは、DataGridView が持つ標準のインライン編集構造と、検証イベントのコントロールです。

  • 非編集時の描画制御(CellPainting:通常時は e.Graphics.FillRectangle を用いて、セル内に進捗率に応じた横バーと「〇〇%」というテキストをカスタム描画します。ただし、現在カレントセルが編集モード中(IsCurrentCellInEditMode)であり、かつ描画対象のセルである場合は、自前の描画をスキップして処理を抜けます(return)。これにより、編集開始と同時に標準のテキストボックスが目の前に立ち上がります。
  • 確定直前の入力検証(CellValidating:ユーザーが数値を書き換えて別のセルへ移動しようとした瞬間、入力された値が「0〜100の間の整数か」を厳密にチェックします。条件を満たさない場合は e.Cancel = true を宣言することで、正しい値が入力されるまで他のセルへの移動や画面のクローズを強力にブロックします。

実装コード

以下は、進捗率を管理する「Progress(数値型)」の列を用意し、非編集時のみ横バーを描画しつつ、不正入力を防ぐバリデーションを実装したコード例です。

Form1.cs

Form1.cs
using System.Data;
namespace DataGridView_ProgressCell;
public partial class Form1 : Form
{
private DataTable _taskTable = new DataTable();
public Form1()
{
InitializeComponent();
InitializeData();
InitializeDataGridView();
}
private void InitializeData()
{
// 1. データソースの準備(値は 0 〜 100 の整数を想定)
_taskTable.Columns.Add("TaskName", typeof(string));
_taskTable.Columns.Add("Progress", typeof(int)); // 進捗率
_taskTable.Rows.Add("画面設計の作成", 100);
_taskTable.Rows.Add("データバインディング実装", 65);
_taskTable.Rows.Add("デバッグ・テスト", 10);
}
private void InitializeDataGridView()
{
dataGridView1.DataSource = _taskTable;
// 列名の調整
dataGridView1.Columns["TaskName"].HeaderText = "タスク名";
dataGridView1.Columns["Progress"].HeaderText = "進捗状況";
dataGridView1.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
dataGridView1.AllowUserToAddRows = false;
// カスタム描画イベントを購読
dataGridView1.CellPainting += DataGridView1_CellPainting;
// 入力制限(バリデーション)イベントを購読
dataGridView1.CellValidating += DataGridView1_CellValidating;
dataGridView1.CellEndEdit += DataGridView1_CellEndEdit;
}
/// <summary>
/// セルの値が確定する直前に呼び出される検証イベント
/// </summary>
private void DataGridView1_CellValidating(object? sender, DataGridViewCellValidatingEventArgs e)
{
// Progress列以外はチェック対象外にする
if (dataGridView1.Columns[e.ColumnIndex].Name != "Progress") return;
// 新しく入力された「画面上の文字列」は e.FormattedValue から取得します
string newValueStr = e.FormattedValue?.ToString() ?? "";
// 未入力(空っぽ)はエラーにする場合
if (string.IsNullOrWhiteSpace(newValueStr))
{
dataGridView1.Rows[e.RowIndex].ErrorText = "進捗状況を入力してください。";
e.Cancel = true;
return;
}
// 整数に変換できるか、および 0 〜 100 の範囲内かをチェック
if (!int.TryParse(newValueStr, out int parsedValue) || parsedValue < 0 || parsedValue > 100)
{
// 行にエラーテキストを設定(行ヘッダーにエラーアイコンが表示されます)
dataGridView1.Rows[e.RowIndex].ErrorText = "進捗率は 0 〜 100 の間の整数で入力してください。";
// これに true をセットすることで、不正な値のままフォーカスが外れるのを阻止します
e.Cancel = true;
}
}
/// <summary>
/// セルの編集が正常に終了したときに呼び出されるイベント
/// </summary>
private void DataGridView1_CellEndEdit(object? sender, DataGridViewCellEventArgs e)
{
// 正しい値が入力されて編集が無事終わったら、行のエラーテキストを綺麗に消去します
dataGridView1.Rows[e.RowIndex].ErrorText = string.Empty;
}
/// <summary>
/// セルの描画イベント
/// </summary>
private void DataGridView1_CellPainting(object? sender, DataGridViewCellPaintingEventArgs e)
{
// ヘッダーや無効なセル、および「Progress」列以外は通常通りシステムに描画させる
if (e.RowIndex < 0 || e.ColumnIndex < 0) return;
if (dataGridView1.Columns[e.ColumnIndex].Name != "Progress") return;
// 3. 現在「編集モード中」であるかチェック
// PaintPartsに「All」が指定されている、かつ、セルの状態に「Editing(編集中)」が含まれている場合
if (dataGridView1.IsCurrentCellInEditMode &&
dataGridView1.CurrentCell.RowIndex == e.RowIndex &&
dataGridView1.CurrentCell.ColumnIndex == e.ColumnIndex)
{
// 編集中の場合は自前での描画(プログレスバー)は一切せず、システム(標準のTextBox)に丸投げする
return;
}
// --- ここから非編集時の「プログレスバー」描画ロジック ---
// セルの値を数値(0〜100)として取得
int progressValue = 0;
if (e.Value != null && int.TryParse(e.Value.ToString(), out int parsedValue))
{
// 0〜100の範囲に収めるガード処理
progressValue = Math.Max(0, Math.Min(100, parsedValue));
}
// A. まずはセルの背景と枠線を描画(選択状態の色なども自動考慮されます)
e.PaintBackground(e.CellBounds, true);
// B. プログレスバー(横バー)の描画領域を計算(セルの内側に少しマージンを取る)
int marginX = 4;
int marginY = 4;
Rectangle barBounds = new Rectangle(
e.CellBounds.X + marginX,
e.CellBounds.Y + marginY,
e.CellBounds.Width - (marginX * 2),
e.CellBounds.Height - (marginY * 2)
);
// バーの背景(薄いグレー)を塗る
using (SolidBrush bgBrush = new SolidBrush(Color.FromArgb(240, 240, 240)))
{
e.Graphics.FillRectangle(bgBrush, barBounds);
}
// 進捗率に応じた「緑色のバー」の幅を計算し、配色する
if (progressValue > 0)
{
int fillWidth = (int)(barBounds.Width * (progressValue / 100.0));
Rectangle fillBounds = new Rectangle(barBounds.X, barBounds.Y, fillWidth, barBounds.Height);
// 100%なら緑、それ以外は黄緑で配色する
Color barColor = (progressValue == 100) ? Color.MediumSeaGreen : Color.LightGreen;
using (SolidBrush barBrush = new SolidBrush(barColor))
{
e.Graphics.FillRectangle(barBrush, fillBounds);
}
}
// C. バーの枠線(細いグレー)を描画
using (Pen borderPen = new Pen(Color.LightGray))
{
e.Graphics.DrawRectangle(borderPen, barBounds);
}
// D. 中央に「〇〇%」というテキストを重ねて描画
string text = $"{progressValue}%";
// 文字の配置(中央揃え)を指定
TextFormatFlags flags = TextFormatFlags.HorizontalCenter | TextFormatFlags.VerticalCenter | TextFormatFlags.PreserveGraphicsClipping;
// 選択されている行かどうかで文字色を変える
Color textColor = (e.State & DataGridViewElementStates.Selected) == DataGridViewElementStates.Selected
? e.CellStyle.SelectionForeColor
: e.CellStyle.ForeColor;
TextRenderer.DrawText(e.Graphics, text, e.CellStyle.Font, e.CellBounds, textColor, flags);
// 4. 「描画は完了した」とシステムに通知(標準の数値テキストが上書きされるのを防ぐ)
e.Handled = true;
}
}

サンプルアプリケーション

DataGridView_ProgressCell.zip
※ サンプルアプリケーションは、.NET 10 をターゲットに作成しています。

実装のポイント

1. IsCurrentCellInEditMode による編集セルの正確な判定

CellPainting イベントは、ユーザーがセルをダブルクリックしてテキストボックスが起動している「編集モード中」であっても、そのテキストボックスの背景側(真下)を描画するために裏で動き続けています。

そのため、単に「編集中であるか」だけでなく、IsCurrentCellInEditMode と現在のアクティブセル(CurrentCell)の行・列インデックスを正確に比較し、「いま目の前で編集中のセル」だけを対象にカスタム描画をパス(return)させる処理を行います。

2. 検証値の取得には e.FormattedValue を使う

CellValidating イベント内で入力値をチェックする際、セルの本来の値である Value プロパティを参照してはいけません。このイベントが走っている時点では、まだ入力内容の妥当性をチェックしている最中であるため、裏側のデータソースには古い値が残ったままになっています。

「ユーザーがいまキーボードで打ち込んだ、画面上の未確定の最新文字列」 は、引数である e.FormattedValue に格納されています。こちらを取り出して int.TryParse による型判定や範囲チェックを行うのが、データバインディングにおけるルールです。

3. ErrorText を用いた標準のエラー通知

不正な入力があった場合に MessageBox.Show などのポップアップ警告を出すと、連続入力を行いたいユーザーの操作を阻害するため、業務システムのUI設計としては嫌われるケースがあります。

今回のコードのように、行の ErrorText プロパティにエラー理由のメッセージを代入すると、DataGridView の行ヘッダー(左端の余白)に自動的にエラーアイコンが出現するようになります(マウスカーソルを合わせるとメッセージがツールチップ表示されます)。

そして、正しい値が入力され編集が正常終了したタイミング(CellEndEdit イベント)で ErrorText を空文字(string.Empty)にしてクリアすることで、Windows 標準のエラー通知を実現できます。

まとめ

今回は、DataGridView の標準機能だけを駆使して、プログレスバーによる視覚的な表示と、0〜100に制限された安全な数値入力をインラインで切り替える手法について解説しました。

複雑なカスタムセルクラスを一から自作しなくても、CellPaintingCellValidating の協調動作だけで、実務で喜ばれるグラフィカルで扱いやすいインライン編集画面を構築することができます。

  • IsCurrentCellInEditMode を駆使し、編集中のセルのみカスタム描画をスルーさせてテキストボックスを露出させる
  • 入力検証には e.FormattedValue を使用し、不整合データは e.Cancel = true で確定を阻止する
  • 不正入力の通知には ErrorText を利用し、行ヘッダーに警告アイコンを出すスマートな UI を設計する

表現力豊かなグリッド UI を設計する際の参考になりましたら幸いです。


💬 コメント