【実務・中級編】VB.NETにおけるTypeConverterの実装:プロパティグリッド(PropertyGrid)上でカスタムオブジェクトを自由自在に編集する – Visual Basic (VB / VB.NET)解析バイブル

スポンサーリンク

VB.NETにおけるTypeConverterの実装:PropertyGrid上でカスタムオブジェクトを自由自在に編集する

Windows Formsでの業務アプリケーション開発において、`PropertyGrid`(プロパティグリッド)コントロールほど過小評価されているコンポーネントはない。多くの開発者は、これを単なる「設定画面用の簡易的なリスト」程度に捉え、せいぜい文字列や数値の編集にしか使っていない。

しかし、考えてみてほしい。
基幹システムの複雑なパラメータ構造、デバイスの制御設定、あるいは独自の計算ロジックを持つオブジェクト群を、わざわざ専用のWindowsフォームを何枚も作って管理していないだろうか? それは工数の無駄であり、保守性を下げる最悪のアンチパターンだ。

`PropertyGrid`の真価は、`TypeConverter`(タイプコンバーター)を完全に掌握した瞬間から発揮される。
今回は、独自のカスタムオブジェクトをPropertyGridに美しくシームレスに統合し、プロパティとしてのネスト展開、文字列へのシリアライズ、さらにはカスタムUI(ドロップダウンやダイアログ)のポップアップを実現する極限のテクニックを伝授する。

—

なぜデフォルトのPropertyGridでは不十分なのか?

カスタムクラスをそのまま`PropertyGrid.SelectedObject`に代入すると、PropertyGridはそのクラスを「単一の読み取り専用文字列(通常は`ToString()`の結果)」として扱うか、あるいはパブリックプロパティを機械的に列挙するだけになる。

これの何が問題か?
1. 階層構造の欠落: クラスの中に別のクラス(例: `Point`や独自の設定構造体)がある場合、グリッド上でインデントされたツリー構造として展開されない。
2. 型変換の欠落: 画面上の「人間が読み書きする文字列」と、メモリ上の「厳密な型(Enumや独自構造体)」の双方向変換ができない。
3. UIの硬直性: ドロップダウンリストや、専用のモーダルエディタ(ファイル選択ダイアログなど)を自由に紐付けられない。

これを解決するのが、`System.ComponentModel.TypeConverter`の継承と、適切な属性(Attribute)の付与である。

—

アーキテクチャの全体像

今回実装するサンプルでは、以下の3要件を満たす堅牢な設計コードを示す。

1. カスタムデータ構造の定義: ネットワーク設定を想定したオブジェクト(IPアドレスとポート番号を保持)。
2. TypeConverterの実装: 文字列とカスタムオブジェクト間の相互変換(`ConvertTo`, `ConvertFrom`)。
3. ExpandableObjectConverterの活用: プロパティグリッド上で子プロパティをツリー状に展開するテクニック。

余計な抽象化は排し、プロダクション環境で即座にコピー&ペーストして動かせる洗練されたVB.NETコードを提供する。

—

プロダクションコード:完全実装

以下のコードをそのままプロジェクトに貼り付ければ動作する。.NET Framework 4.8 / .NET 6+ / .NET 8 のいずれのWindows Forms環境でも通用する堅牢な実装だ。

Imports System
Imports System.ComponentModel
Imports System.Globalization
Imports System.Windows.Forms

Namespace AdvancedPropertyGridDemo

‘ =================================================================
‘ 1. カスタムデータ型 (サーバー接続設定を想定)
‘ =================================================================
‘ TypeConverter属性で、このクラスの変換ロジックを指定する

Public Class ServerConnection

‘ プロパティ



Public Property Host As String = “127.0.0.1”




Public Property Port As Integer = 8080

‘ グリッド上で「Host:Port」のような美しい文字列表現を返すためのオーバーライド
Public Overrides Function ToString() As String
Return $”{Host}:{Port}”
End Function
End Class

‘ =================================================================
‘ 2. カスタム TypeConverter の実装
‘ =================================================================
‘ ExpandableObjectConverterを継承することで、PropertyGrid上でプロパティがツリー展開される
Public Class ServerConnectionConverter
Inherits ExpandableObjectConverter

‘ 文字列からカスタムオブジェクトへの変換を許可するか
Public Overloads Overrides Function CanConvertFrom(context As ITypeDescriptorContext, sourceType As Type) As Boolean
If sourceType = GetType(String) Then
Return True
End If
Return MyBase.CanConvertFrom(context, sourceType)
End Function

‘ カスタムオブジェクトから文字列への変換を許可するか
Public Overloads Overrides Function CanConvertTo(context As ITypeDescriptorContext, destinationType As Type) As Boolean
If destinationType = GetType(ServerConnection) Then
Return True
End If
Return MyBase.CanConvertTo(context, destinationType)
End Function

‘ 文字列 (例: “192.168.1.10:9000”) から ServerConnection オブジェクトを生成する
Public Overloads Overrides Function ConvertFrom(context As ITypeDescriptorContext, culture As CultureInfo, value As Object) As Object
Dim strVal As String = TryCast(value, String)
If strVal IsNot Nothing Then
Dim parts = strVal.Split(“:”c)
If parts.Length = 2 Then
Dim conn As New ServerConnection()
conn.Host = parts(0).Trim()

Dim portNum As Integer
If Integer.TryParse(parts(1).Trim(), portNum) Then
conn.Port = portNum
End If
Return conn
End If
End If

Return MyBase.ConvertFrom(context, culture, value)
End Function

‘ ServerConnection オブジェクトから文字列へ変換する
Public Overloads Overrides Function ConvertTo(context As ITypeDescriptorContext, culture As CultureInfo, value As Object, destinationType As Type) As Object
If destinationType = GetType(String) AndAlso TypeOf value Is ServerConnection Then
Dim conn = DirectCast(value, ServerConnection)
Return $”{conn.Host}:{conn.Port}”
End If

Return MyBase.ConvertTo(context, culture, value)
End Function
End Class

‘ =================================================================
‘ 3. ホストとなるメインフォーム
‘ =================================================================
Public Class MainForm
Inherits Form

Private propertyGrid1 As PropertyGrid

Public Sub New()
‘ フォームの初期設定
Me.Text = “TypeConverter 実装サンプル”
Me.Width = 500
Me.Height = 400

‘ PropertyGridの配置
propertyGrid1 = New PropertyGrid()
propertyGrid1.Dock = DockStyle.Fill
Me.Controls.Add(propertyGrid1)

‘ アプリケーション設定用ルートオブジェクトのバインド
propertyGrid1.SelectedObject = New AppSettings()
End Sub
End Class

‘ =================================================================
‘ 4. PropertyGridにバインドするルート設定クラス
‘ =================================================================
Public Class AppSettings


Public Property AppName As String = “Enterprise Automation Tool”




Public Property PrimaryServer As ServerConnection = New ServerConnection() With {.Host = “10.0.0.5”, .Port = 5000}
End Class

End Namespace

—

現場のエンジニアへ:実装上の重要な注意点

このコードを実務のプロダクション環境に導入する際、以下の「罠」に注意してほしい。

1. 例外処理の厳密性

`ConvertFrom` メソッド内での文字列パース(`Split` や `Integer.TryParse`)において、ユーザーが不正なフォーマット(例: `:` が抜けている、ポートが数値ではないなど)を入力した場合、容赦なく例外が発生してPropertyGridがクラッシュするか、入力値がロールバックされる。
実務では、パースに失敗した際に `ArgumentException` をスローし、それをキャッチしたUI側がユーザーに適切なエラーメッセージ(例: `”形式が不正です。 Host:Port の形式で入力してください。”`)をポップアップする防御的コードを入れるべきだ。

2. データベースやファイル連携(シリアライズ)時の挙動

このようなカスタムオブジェクトをJSON(Newtonsoft.JsonやSystem.Text.Json)やXMLにシリアライズする際、`TypeConverter` が正しく機能していれば、文字列として一発でシリアライズされるか、あるいは各プロパティ(Host, Port)が個別のJSONプロパティとして綺麗に分解される。
しかし、DBのORM(Entity FrameworkやDapperなど)に直接このオブジェクトを流し込む場合は注意が必要だ。DBの型マッピング(カスタムTypeHandler等)側でも、この`ServerConnection`をどのように解釈するか定義してやる必要がある点を忘れないでほしい。

—

まとめ

`TypeConverter`と`ExpandableObjectConverter`を使いこなせば、VB.NETのWindows FormsアプリケーションのUI品質は劇的に向上する。
コード量を無駄に増やして専用の編集ダイアグラムを作り込む必要などない。PropertyGridという既存の強力なフレームワークに適切なメタデータと変換ロジックを流し込むだけで、洗練されたエンタープライズ向けの管理画面が数行のコードで組み上がる。

保守性が高く、拡張性に優れたコードを書くこと――それこそが、プロフェッショナルな業務自動化エンジニアの仕事である。次回の設計から、ぜひこのアプローチを取り入れてみてほしい。

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