【実務・中級編】DataGridViewのセル単位での高度な入力制御:EditingControlShowingイベントをフックして数値専用セルでカンマ自動編集を実現する – Visual Basic (VB / VB.NET)解析バイブル

スポンサーリンク

DataGridViewの限界を超える:`EditingControlShowing`を支配し、数値セルに「生きた」カンマ自動編集を実装する

Windows Formsアプリケーションの設計において、`DataGridView`は避けて通れない主役だ。しかし、業務システムの現場で避けて通れない要件の一つに「数値入力時のリアルタイムなカンマ区切りフォーマット」がある。

標準の`DataGridViewTextBoxColumn`に数値をバインドすると、セルからフォーカスが外れた(LostFocus)瞬間にはフォーマットされるが、編集中(Editing時)にはただの殺風景な生数値に戻る。ユーザーは「いま何桁入力しているのか」を一目で把握できず、入力ミスが頻発する。かといって、専用のサードパーティ製コントロールを導入する予算や権限が現場にあるとは限らない。

今回は、標準のDataGridViewのポテンシャルを極限まで引き出し、`EditingControlShowing`イベントをフックして「編集中もリアルタイムにカンマが踊る」洗練されたカスタム入力制御のアーキテクチャを伝授する。

—

なぜ「セルテンプレートの拡張」だけでは破綻するのか?

多くの初級~中級プログラマが最初に陥る罠が、`DataGridViewTextBoxCell`を継承したカスタムセルクラスの作成だ。
「`EditControl`をオーバーライドして、内部のTextBoxにイベントを仕込めばいい」――一見、正しく見えるこのアプローチは、オブジェクトのライフサイクルを無視しているため、メモリリークやイベントの多重登録という致命的なバグを引き起こす。

DataGridViewは、メモリ上のパフォーマンスを維持するために、表示されている領域にのみ「編集用コントロール(実体は単なる一つのTextBox)」を動的に生成し、セルを切り替えるたびにそのコントロールを「使い回す(リサイクル)」。

このライフサイクルを理解していないと、前回のセルのイベントハンドラが残ったままになり、入力値がバグる、あるいはアプリケーションがクラッシュする。

解決の鍵:`EditingControlShowing`イベント

使い回される編集用コントロール(`TextBox`)に対し、「今まさに編集モードに入った瞬間」にピンポイントで介入し、イベントを安全にアタッチ・デタッチする。それが、`EditingControlShowing`イベントを真に支配するということだ。

—

実装:カンマ自動編集を実現するプロダクションコード

以下のコードは、特定の列(ここでは例として2番目の列)にフォーカスが当たった際、自動的にTextBoxをジャックし、入力中の数値にリアルタイムでカンマを付与、さらに数値以外の入力を完全にガードする完全な実装例である。

コピペでそのままWindows Forms(VB.NET)に組み込み、即座に検証できる。

Public Class Form1

‘ フォームロード時にDataGridViewの初期設定を行う
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
‘ テスト用のダミー列を追加
DataGridView1.Columns.Add(“ItemName”, “商品名”)

Dim priceCol As New DataGridViewTextBoxColumn()
priceCol.Name = “Price”
priceCol.HeaderText = “単価(自動カンマ)”
DataGridView1.Columns.Add(priceCol)

‘ テストデータ行の追加
DataGridView1.Rows.Add(“ノートPC”, 128000)
DataGridView1.Rows.Add(“マウス”, 3500)
End Sub

”’

”’ セルが編集状態に入った瞬間に発生するイベント。
F”’ ここでエディタ(TextBox)をキャプチャし、振る舞いを動的に書き換える。
”’

Private Sub DataGridView1_EditingControlShowing(sender As Object, e As DataGridViewEditingControlShowingEventArgs) Handles DataGridView1.EditingControlShowing

‘ 現在編集中のカラムが対象のカラム(例: “Price”)であるか判定
If DataGridView1.CurrentCell.OwningColumn.Name = “Price” Then

‘ 渡されたコントロールをTextBox型にキャスト
Dim tb As TextBox = TryCast(e.Control, TextBox)

If tb IsNot Nothing Then
‘ 【重要】多重登録を防ぐため、一度既存のイベントを確実に解除する
RemoveHandler tb.KeyPress, AddressOf PriceTextBox_KeyPress
RemoveHandler tb.TextChanged, AddressOf PriceTextBox_TextChanged

‘ 新たにイベントハンドラをアタッチ
AddHandler tb.KeyPress, AddressOf PriceTextBox_KeyPress
AddHandler tb.TextChanged, AddressOf PriceTextBox_TextChanged
End If
End If
End Sub

”’

”’ キー入力制限:数値と制御キー(バックスペース等)以外を弾く
”’

Private Sub PriceTextBox_KeyPress(sender As Object, e As KeyPressEventArgs)
‘ 制御キー(BackSpace等)は許可
If Char.IsControl(e.KeyChar) Then
Return
End If

‘ 数字以外が入力された場合はイベントをキャンセル
If Not Char.IsDigit(e.KeyChar) Then
e.Handled = True
End If
End Sub

”’

”’ テキスト変更時:入力値にリアルタイムでカンマを付与する
”’

Private Sub PriceTextBox_TextChanged(sender As Object, e As EventArgs)
Dim tb As TextBox = CType(sender, TextBox)

‘ カーソル位置を復元するためのオフセット計算用
Dim selectionStart As Integer = tb.SelectionStart
Dim originalLength As Integer = tb.Text.Length

‘ 1. 一度すべてのカンマを除去して「純粋な数値文字列」に戻す
Dim rawText As String = tb.Text.Replace(“,”, “”)

If Long.TryParse(rawText, Nothing) Then
‘ 2. 数値としてパースできる場合、3桁区切りにフォーマット
Dim formattedText As String = Convert.ToInt64(rawText).ToString(“#,0”)

If tb.Text <> formattedText Then
tb.Text = formattedText

‘ 3. カンマ挿入によってずれたカーソル位置をスマートに再調整
Dim diff As Integer = tb.Text.Length – originalLength
tb.SelectionStart = Math.Max(0, selectionStart + diff)
End If
ElseIf rawText = “” Then
‘ 空文字の場合はそのまま許容
tb.Text = “”
End If
End Sub

”’

”’ 【極めて重要】編集モードが終了した際、イベントのデタッチとクリーンアップを行う
”’

Private Sub DataGridView1_CellFormatting(sender As Object, e As DataGridViewCellFormattingEventArgs) Handles DataGridView1.CellFormatting
‘ 必要に応じてセル非編集中(グリッド表示時)のフォーマットも保証
If DataGridView1.Columns(e.ColumnIndex).Name = “Price” AndAlso e.Value IsNot Nothing Then
Dim val As Long
If Long.TryParse(e.Value.ToString(), val) Then
e.Value = val.ToString(“#,0”)
e.FormattingApplied = True
End If
End If
End Sub

Private Sub DataGridView1_CellEndEdit(sender As Object, e As DataGridViewCellEventArgs) Handles DataGridView1.CellEndEdit
‘ 編集が完了したセルの値からカンマを除去してデータモデル側の整合性を保つアプローチが必要な場合はここで処理する
Dim cell = DataGridView1.Rows(e.RowIndex).Cells(e.ColumnIndex)
If cell.OwningColumn.Name = “Price” AndAlso cell.Value IsNot Nothing Then
cell.Value = cell.Value.ToString().Replace(“,”, “”)
End If
End Sub

End Class

—

現場のプロが教える「データベース連携とバグ回避」の鉄則

この実装を業務システムに投入する際、データベース(SQL ServerやOracle等)やファイル(CSV/Excel)へ書き出すフェーズで、必ず次の落とし穴に直面する。

1. データ型(Type Mismatch)の罠

画面上では「`128,000`」という文字列(String)として美しくフォーマットされていても、データベースの型が `INT` や `DECIMAL` である場合、そのままADO.NETのパラメータや型付きDataSetへ流し込むと `FormatException` が発生する。

  • 対策: `CellEndEdit` やデータ保存のコミット直前で、必ず `.Replace(“,”, “”)` を通して純粋な数値にパースしてからエンティティにバインドすること。上記のサンプルコードでは `CellEndEdit` で担保している。

2. カーソル位置の暴走を防ぐ

TextBoxの `.Text` を書き換えると、通常はカーソルが強制的に末尾にジャンプしてしまう。ユーザーが数値を修正しようと「真ん中」をクリックして入力した際、文字が勝手に後ろに飛んだら、それは「使えないシステム」の烙印を押される。

  • 対策: `TextBox.SelectionStart` の差分を計算し、テキスト変更前の相対位置を維持するロジック(上記コードの `TextChanged` 内の処理)を必ず入れること。この細部へのこだわりが、プロとアマを分ける。

—

総括

DataGridViewは「素のまま」使うものではない。イベントのライフサイクルを正しく理解し、動的なコントロール制御(フッキング)をマスターすれば、サードパーティ製の高価なコンポーネントに頼らずとも、極めて滑らかでモダンなUI体験をVB.NETで構築できる。

「動けばいい」という妥協を捨て、細部まで磨き上げられたコードこそが、保守性に優れ、ユーザーを魅了する業務システムの土台となる。今すぐあなたのプロジェクトの入力制御をアップデートしてほしい。

タイトルとURLをコピーしました