「見かけ」と「中身」を切り離せ!DataGridViewのCellFormattingとCellParsingを極める
こんにちは。業務自動化の世界へようこそ。
マクロの記録や、単純なコピー&ペーストのコードから一歩踏み出し、「もっと使いやすい、プロ仕様のツールを作りたい」と願い始めたあなた。その向上心は、素晴らしいエンジニアへの第一歩です。
今日は、Windows Forms開発において誰もが一度はぶつかる壁、「DataGridViewでの入力と表示のギャップ」を華麗に解決するテクニックをお伝えします。
「ユーザーには『¥1,234』や『令和5年』と入力させたいけれど、プログラム内部(データベース)では正確な『数値』や『日付型』として扱いたい……」
これを実現するために、`CellFormatting` と `CellParsing` という2つのイベントをマスターしましょう。ここをクリアすれば、あなたの作るアプリケーションの品質は劇的に向上しますよ。
1. なぜ「直接書き換える」のはいけないのか?
多くの初学者が陥る罠があります。それは、「ユーザーが入力した直後に、値を無理やり書き換えようとする」ことです。
例えば、ユーザーが「1,000」と入力したとき、`CellValueChanged` イベントなどで強引に文字列を操作して値を書き換えようとすると、以下のような悲劇が起こります。
1. 無限ループの発生: 値を書き換えたことで再び「値が変わったイベント」が発生し、プログラムが止まらなくなる。
2. 型エラーの連鎖: 文字列として扱いたいのか、数値として扱いたいのかが混濁し、計算ができなくなる。
3. データ型の破壊: 本来 `Decimal`(数値型)であるべき場所に、カンマ入りの `String`(文字列型)を流し込んでしまい、データベース保存時にエラーが出る。
プロの設計思想は違います。
「表示(見かけ)」と「データ(中身)」を完全に分離するのです。そのための架け橋となるのが、今回紹介する2つのイベントです。
2. 二つの架け橋:CellFormatting と CellParsing
この2つのイベントを、「翻訳機」だと考えてください。
① CellFormatting(中身 $\rightarrow$ 見かけ)
DataGridViewがセルを描画する直前に呼ばれます。「データの中身(例:1000)」を、「ユーザーが見る形式(例:¥1,000)」へと、一時的に翻訳して表示する役割です。元のデータ自体は書き換えません。
② CellParsing(見かけ $\rightarrow$ 中身)
ユーザーがセルに値を入力し、確定した直後に呼ばれます。「ユーザーが打った文字(例:¥1,000)」を、「プログラムが理解できる型(例:1000)」へと翻訳して、データソースに書き戻す役割です。
| イベント | 方向 | 役割 |
|---|---|---|
| CellFormatting | データ $\rightarrow$ UI | 「綺麗に見せる」 (Rendering) |
| CellParsing | UI $\rightarrow$ データ | 「正しい型に変える」 (Parsing) |
3. 実践:金額と和暦のカスタム入力を実装する
では、具体的なコードを見ていきましょう。
「金額列(Decimal型)」には「¥」マークを付け、「日付列(DateTime型)」には「和暦」で表示・入力できるような設計を目指します。
“`vb.net
Imports System.Globalization
Public Class Form1
‘ データの器となるDataTableを用意
Private dt As DataTable
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
‘ 1. データ構造の定義
dt = New DataTable()
dt.Columns.Add(“商品名”, GetType(String))
dt.Columns.Add(“金額”, GetType(Decimal)) ‘ 数値型
dt.Columns.Add(“納品日”, GetType(DateTime)) ‘ 日付型
‘ サンプルデータの追加
dt.Rows.Add(“リンゴ”, 1200, New DateTime(2023, 10, 1))
dt.Rows.Add(“バナナ”, 500, New DateTime(2024, 1, 15))
‘ 2. DataGridViewへの紐付け
DataGridView1.DataSource = dt
DataGridView1.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill
End Sub
”’
”’
Private Sub DataGridView1_CellFormatting(sender As Object, e As DataGridViewCellFormattingEventArgs) Handles DataGridView1.CellFormatting
‘ 列のインデックスを確認(エラー防止のため重要!)
If e.RowIndex < 0 OrElse e.ColumnIndex < 0 Then Return
' 金額列(インデックス1)の表示を「¥1,234」形式にする
If DataGridView1.Columns(e.ColumnIndex).Name = "金額" AndAlso e.Value IsNot Nothing Then
If IsNumeric(e.Value) Then
' 数値を通貨形式の文字列に変換して表示
e.Value = CType(e.Value, Decimal).ToString("C", New CultureInfo("ja-JP"))
e.FormattingApplied = True ' 「整形済み」であることを通知
End If
End If
' 日付列(インデックス2)の表示を「和暦」にする
If DataGridView1.Columns(e.ColumnIndex).Name = "納品日" AndAlso e.Value IsNot Nothing Then
If TypeOf e.Value Is DateTime Then
' 日付を和暦形式の文字列に変換
Dim dtValue As DateTime = CType(e.Value, DateTime)
e.Value = dtValue.ToString("ggge年M月d日", New CultureInfo("ja-JP"))
e.FormattingApplied = True
End If
End If
End Sub
'''
”’
Private Sub DataGridView1_CellParsing(sender As Object, e As DataGridViewCellParsingEventArgs) Handles DataGridView1.CellParsing
‘ 列のインデックスを確認
If e.RowIndex < 0 OrElse e.ColumnIndex < 0 Then Return
' 金額列のパース処理
If DataGridView1.Columns(e.ColumnIndex).Name = "金額" Then
Dim input As String = e.Value?.ToString().Replace("¥", "").Replace(",", "")
Dim result As Decimal
' 数字として正しく解釈できるか検証
If Decimal.TryParse(input, result) Then
e.Value = result ' 変換後の値をセット
e.ParsingApplied = True ' 「パース成功」を通知
Else
' 失敗した場合は何もしない(ユーザーにはエラー表示として残る)
' ここでバリデーションエラーを出す処理を書いても良い
End If
End If
' 日付列のパース処理(和暦入力への対応)
If DataGridView1.Columns(e.ColumnIndex).Name = "納品日" Then
Dim input As String = e.Value?.ToString()
Dim result As DateTime
' 日本語の文化圏(ja-JP)を指定してパースを試みる
Dim culture As New CultureInfo("ja-JP")
If DateTime.TryParse(input, culture, DateTimeStyles.None, result) Then
e.Value = result ' 変換後の値をセット
e.ParsingApplied = True ' 「パース成功」を通知
End If
End If
End Sub
End Class
```
4. 陥りやすいエラーと、プロの回避術
このコードを実装する際、以下の3点に注意してください。これを知っているだけで、デバッグの時間は半分になります。
① `e.FormattingApplied = True` を忘れない
`CellFormatting` イベント内で `e.Value` を書き換えたら、必ずこのプロパティを `True` にしてください。これを忘れると、DataGridViewが「まだ整形が必要だ」と判断して、再びイベントを呼び出し、予期せぬ挙動(表示が崩れる、重くなる等)を引き起こすことがあります。
② `e.ParsingApplied = True` は「成功したときだけ」
`CellParsing` で最も重要なのは、「変換に成功したときだけ True にする」ことです。
もし、ユーザーが「あいうえお」と入力したのに、無理やり `True` を返してしまうと、プログラムは「あいうえお」を数値や日付として無理やり保存しようとして、クラッシュ(例外発生)してしまいます。
③ パフォーマンスへの配慮
これらのイベントは、画面をスクロールしたり、セルを選択したりするたびに、膨大な回数呼び出されます。
- NG: `CellFormatting` の中でデータベースに問い合わせる(SELECT文を投げる)。
- OK: 事前にメモリ上の `DataTable` にデータを読み込んでおき、その値を加工するだけにする。
5. まとめ:ここをクリアすれば、基本はバッチリ!
いかがでしたでしょうか?
- `CellFormatting` は、データを見やすく「化粧」するイベント。
- `CellParsing` は、ユーザーの入力を正しく「解釈」するイベント。
この二つを使い分けることで、「ユーザーには優しく、プログラム(データ)には厳格な」プロフェッショナルなアプリケーションが作れるようになります。
最初は「なんだか難しそうだな」と感じるかもしれません。でも、大丈夫。一度この「見かけと中身を分ける」という感覚を掴んでしまえば、もう元の不自由な書き方には戻れなくなるはずです。
一歩ずつ、確実に。あなたのコードが、より洗練されたものになることを応援していますよ!
