DataGridViewの深淵:`EditingControlShowing`を支配し、数値専用セルの「カンマ自動編集」を極限までチューニングする
レガシーな業務システムにおいて、ユーザーからの要求はいつだって容赦がない。
「Excelのように、数値を打ち込んだ瞬間に3桁区切りのカンマを入れてほしい」
「だが、編集中はカンマなしの純粋な数値として扱え。マイナス記号や小数点も制御しろ」
標準の `DataGridViewNumericUpDownColumn`? 拡張性の低いコントロールを無理やりサードパーティ製ライブラリで補う? そんな無駄な依存関係を現場に持ち込む必要はない。
我々には、Windows Formsの骨格を成すメッセージループと、`DataGridView` のセルライフサイクルを直接制御する圧倒的な知見がある。
今回は、`EditingControlShowing` イベントをフックし、`TextBox` コントロールを動的に差し替え・調教することで、極限まで滑らかで堅牢な「数値専用・カンマ自動編集セル」を実装するアーキテクチャを解説する。
—
1. DataGridViewの編集メカニズムと「実体」の把握
`DataGridView` のセルは、通常時は単なる「描画用のプレースホルダー」に過ぎない。ユーザーがセルをダブルクリックするか、キー入力を行って編集モード(Edit Mode)に突入した瞬間、内部で唯一の 編集用コントロール(Editing Control) が生成され、セル上にドロップされる。
標準のテキストボックス型セルであれば、実体は `System.Windows.Forms.TextBox` だ。
この「一時的に生成されるコントロール」のライフサイクルを完全に掌握することが、高度な入力制御の第一歩となる。
ライフサイクルの罠
- 使い回しの原則: `DataGridView` はパフォーマンスを維持するため、Editing Controlを毎回破棄・生成せず、プールして使い回す。
- イベントの多重登録の恐怖: `EditingControlShowing` イベント内で、動的コントロールに対して安易に `AddHandler` を行うと、セルを移動するたびにイベントハンドラーが多重登録され、同じ処理が2回、3回と実行されるメモリリークとバグの温床となる。
このハードルをクリアする唯一の解が、「イベントの厳密なデタッチとアタッチの管理」 である。
—
2. 実装アーキテクチャ:カンマ自動編集TextBoxの構築
以下のコードは、VB.NETによる完全な実装例である。
単に動かすだけでなく、IMEの制御、ペースト時のバリデーション、そしてフォーカス離脱時のパース処理までを網羅した、実戦投入仕様のコードだ。
Imports System.Windows.Forms
Imports System.Globalization
Public Class FormMain
Inherits Form
Private WithEvents dgvData As New DataGridView()
Public Sub New()
‘ フォームの初期化
Me.ClientSize = New Size(800, 600)
dgvData.Dock = DockStyle.Fill
Me.Controls.Add(dgvData)
‘ グリッドのセットアップ
SetupGrid()
End Sub
Private Sub SetupGrid()
dgvData.AllowUserToAddRows = True
dgvData.EditMode = DataGridViewEditMode.EditOnEnter
‘ カラム定義(数値入力用のテキストカラム)
Dim col As New DataGridViewTextBoxColumn()
col.Name = “NumericWithComma”
col.HeaderText = “金額(自動カンマ)”
dgvData.Columns.Add(col)
‘ テストデータ
dgvData.Rows.Add(“1234567”)
dgvData.Rows.Add(“-8901234.56″)
‘ イベントの結線
AddHandler dgvData.EditingControlShowing, AddressOf DgvData_EditingControlShowing
AddHandler dgvData.CellFormatting, AddressOf DgvData_CellFormatting
AddHandler dgvData.CellParsing, AddressOf DgvData_CellParsing
End Sub
”’
”’
Private Sub DgvData_EditingControlShowing(sender As Object, e.KeyEventArgsEventArgs) Handles dgvData.EditingControlShowing
‘ 該当するカラムかチェック
If dgvData.CurrentCell.ColumnIndex = dgvData.Columns(“NumericWithComma”).Index Then
Dim tb As TextBox = TryCast(e.Control, TextBox)
If tb IsNot Nothing Then
‘ 【重要】多重登録を防ぐため、既存のハンドラーを必ず解除
RemoveHandler tb.KeyPress, AddressOf NumericTextBox_KeyPress
RemoveHandler tb.Enter, AddressOf NumericTextBox_Enter
RemoveHandler tb.Leave, AddressOf NumericTextBox_Leave
‘ 新たにイベントをアタッチ
AddHandler tb.KeyPress, AddressOf NumericTextBox_KeyPress
AddHandler tb.Enter, AddressOf NumericTextBox_Enter
AddHandler tb.Leave, AddressOf NumericTextBox_Leave
‘ 編集中はカンマを除去した生の値(プレーンテキスト)にする
If Not String.IsNullOrEmpty(tb.Text) Then
Dim rawVal As Decimal
If Decimal.TryParse(tb.Text.Replace(“,”, “”), NumberStyles.Any, CultureInfo.InvariantCulture, rawVal) Then
tb.Text = rawVal.ToString(CultureInfo.InvariantCulture)
End If
End If
End If
End If
End Sub
”’
”’
Private Sub NumericTextBox_KeyPress(sender As Object, e.KeyPressEventArgs)
Dim tb As TextBox = CType(sender, TextBox)
‘ バックスペースなどの制御キーは許可
If Char.IsControl(e.KeyChar) Then
Return
End If
‘ 数字、マイナス、小数の判定
Dim isNumber As Boolean = Char.IsDigit(e.KeyChar)
Dim isMinus As Boolean = (e.KeyChar = “-“c)
Dim isDecimal As Boolean = (e.KeyChar = “.”c)
If isMinus Then
‘ マイナスは先頭のみ、かつ未入力の場合のみ許可
If tb.SelectionStart <> 0 OrElse tb.Text.Contains(“-“) Then
e.Handled = True
End If
ElseIf isDecimal Then
‘ 小数点は1つだけ許可
If tb.Text.Contains(“.”) Then
e.Handled = True
End If
ElseIf Not isNumber Then
‘ それ以外はすべて拒否
e.Handled = True
End If
End Sub
”’
”’
Private Sub NumericTextBox_Enter(sender As Object, e As EventArgs)
Dim tb As TextBox = CType(sender, TextBox)
tb.SelectAll()
End Sub
”’
”’
Private Sub NumericTextBox_Leave(sender As Object, e As EventArgs)
Dim tb As TextBox = CType(sender, TextBox)
Dim val As Decimal
If Decimal.TryParse(tb.Text.Replace(“,”, “”), NumberStyles.Any, CultureInfo.InvariantCulture, val) Then
‘ 3桁区切りの書式を適用
tb.Text = val.ToString(“N0″, CultureInfo.CurrentCulture)
End If
End Sub
”’
”’
Private Sub DgvData_CellFormatting(sender As Object, e.DataGridViewCellFormattingEventArgs)
If e.ColumnIndex = dgvData.Columns(“NumericWithComma”).Index AndAlso e.Value IsNot Nothing Then
Dim val As Decimal
If Decimal.TryParse(e.Value.ToString(), NumberStyles.Any, CultureInfo.InvariantCulture, val) Then
e.Value = val.ToString(“N0″, CultureInfo.CurrentCulture)
e.FormattingApplied = True
End If
End If
End Sub
”’
”’
Private Sub DgvData_CellParsing(sender As Object, e.DataGridViewCellParsingEventArgs)
If e.ColumnIndex = dgvData.Columns(“NumericWithComma”).Index AndAlso e.Value IsNot Nothing Then
Dim val As Decimal
If Decimal.TryParse(e.Value.ToString().Replace(“,”, “”), NumberStyles.Any, CultureInfo.InvariantCulture, val) Then
e.Value = val
e.ParsingApplied = True
End If
End If
End Sub
End Class
—
3. シニアエンジニアが押さえるべき「3つの極限最適化」
上記のコードは表面的な美しさだけでなく、レガシーシステムや高負荷環境を耐え抜くための設計思想が組み込まれている。
① `RemoveHandler` によるイベントの多重登録完全防止
前述の通り、`EditingControlShowing` はセルがアクティブになるたびに発火する。ここで `RemoveHandler` を行わずに `AddHandler` を重ねると、1回キーを押しただけで `NumericTextBox_KeyPress` が数回呼び出され、最悪の場合はスタックオーバーフローや予期せぬ文字化けを引き起こす。
「アタッチする前に必ずデタッチする」。これはVB.NETのイベント駆動プログラミングにおける鉄則中の鉄則である。
② `CultureInfo` の明示的指定による環境依存の排除
社内システムにおいて、クライアントPCのOS言語設定やロケール(日本語環境か英語環境か)によって、小数点(`.` と `,`)の解釈が狂うバグは悪名高い。
- 内部処理(編集・パース)では `CultureInfo.InvariantCulture` を使い、ドットを確実に小数点として認識させる。
- 画面表示(フォーマット)では `CultureInfo.CurrentCulture` を使い、ユーザーのローカル設定に準拠させる。
この二面性をコードレベルで厳密に制御することで、グローバル展開されるシステムや環境差異のある仮想デスクトップ(VDI)環境でも完全な動作を担保する。
③ メモリリークとGC(ガベージコレクション)の配慮
VB.NETのWindows Formsアプリケーションにおいて、マネージドリソースの解放漏れはメモリフットプリントを増大させ、長期間稼働する業務端末の動作を重くする原因となる。
今回作成したイベントハンドラー群は、`TextBox`(Editing Control)という一時的なオブジェクトにアタッチされる。コントロール破棄時にイベントが残存していると、GCがオブジェクトを回収できなくなる。
動的コントロールのイベントは、インスタンスの寿命に依存するため、フォーム破棄時や再利用時のクリーンアップ設計が極めて重要となる。
—
結び:標準機能の限界を超える技術的矜持
サードパーティ製の高価なグリッドコントロールを導入すれば、このようなコードを書く手間は省けるかもしれない。しかし、ライブランのバージョンアップに伴う依存関係の地雷や、レガシー環境でのライセンス問題を考慮すれば、「.NET Framework / .NET Coreの標準コンポーネントを極限までハックする」というアプローチこそが、真にシステムを支配するアーキテクトの選択肢である。
`EditingControlShowing` の挙動を完全に手なずけた今、あなたのDataGridViewは、もはやただの表計算グリッドではない。ユーザーのあらゆる入力を正確無比にコントロールする、強靭な業務アプリケーションの心臓部となる。
