VB.NETにおけるTypeConverterの実装:PropertyGridを極限まで支配する技術
Windows FormsアプリケーションのUI設計において、`PropertyGrid` コントロールほど「使いこなせば最強のインスペクターになり、一歩間違えば開発者を絶望の淵に突き落とす」代物もない。特に、標準のプリミティブ型(Integer, String, Booleanなど)ではなく、業務ドメインに特化したカスタムオブジェクトをPropertyGridに直接バインドしようとした瞬間、開発者は必ず壁にぶつかる。
「文字列としては表示されるが、展開してプロパティを編集できない」
「ドロップダウンで選択させたいのに、テキストボックスになってしまう」
この壁を突破する鍵が `TypeConverter` である。
今回は、VBAによるレガシーシステム連携の泥臭いメモリ管理から、最新の.NET環境におけるパフォーマンス最適化までを知り尽くしたアーキテクトの視点から、`TypeConverter` を用いてPropertyGridを完全に掌握する極限の知見を授ける。
—
1. TypeConverterのメカニズムとオブジェクトのライフサイクル
`TypeConverter` は、あるデータ型から別のデータ型への変換(主に `String` との相互変換)、およびPropertyGrid上でのサブプロパティの展開を仲介する抽象クラスだ。
PropertyGridがオブジェクトを描画する際、ランタイムは対象クラスに付与された `
特に、非托管リソース(Windows APIのハンドル等)を内包するカスタムオブジェクトを扱う場合、コンバーター内での型変換処理(`ConvertTo` / `ConvertFrom`)における例外ハンドリングとGC(ガベージコレクション)のプレッシャーを最小限に抑える設計が求められる。
—
2. 実装:複雑なカスタムオブジェクトをPropertyGridで操る
百聞は一見に如かず。ここでは、通信ポートの設定を表すカスタムオブジェクト `ComPortConfig` を作成し、それをPropertyGrid上で「文字列からの変換」「サブプロパティの展開」「ドロップダウン選択」に対応させる完全なコードを示す。
Imports System
Imports System.ComponentModel
Imports System.Globalization
Namespace Enterprise.UI.Components
‘ 1. カスタムデータ型(ここにTypeConverter属性を紐付ける)
Public Class ComPortConfig
Public Property PortName As String = “COM1”
Public Property BaudRate As Integer = 9600
‘ PropertyGrid上で親オブジェクトのリストに文字列表現として正しく表示させるため
Public Overrides Function ToString() As String
Return $”{PortName} ({BaudRate}bps)”
End Function
End Class
‘ 2. カスタムTypeConverterの実装
Public Class ComPortConfigConverter
Nherits TypeConverter
‘ 文字列からカスタムオブジェクトへの変換が可能か
Public 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 Overrides Function CanConvertTo(context As ITypeDescriptorContext, destinationType As Type) As Boolean
If destinationType = GetType(String) Then
Return True
End If
Return MyBase.CanConvertTo(context, destinationType)
End Function
‘ 文字列(例: “COM3 (19200bps)”)からオブジェクトを復元する
Public Overrides Function ConvertFrom(context As ITypeDescriptorContext, culture As CultureInfo, value As Object) As Object
If TypeOf value Is String Then
Dim parts As String() = CStr(value).Split(” “c)
If parts.Length >= 1 Then
Dim config As New ComPortConfig()
config.PortName = parts(0).Trim()
If parts.Length >= 2 AndAlso parts(1).StartsWith(“(“) Then
Dim rateStr As String = parts(1).Replace(“(“, “”).Replace(“bps)”, “”)
Dim rate As Integer
If Integer.TryParse(rateStr, rate) Then
config.BaudRate = rate
End If
End If
Return config
End If
End If
Return MyBase.ConvertFrom(context, culture, value)
End Function
‘ オブジェクトをPropertyGrid上の単一テキスト表現に変換する
Public 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 ComPortConfig Then
Dim config As ComPortConfig = DirectCast(value, ComPortConfig)
Return config.ToString()
End If
Return MyBase.ConvertTo(context, culture, value)
End Function
‘ ★最重要:PropertyGrid上で子プロパティ(PortName, BaudRate)を展開・編集可能にする
Public Overrides Function GetPropertiesSupported(context As ITypeDescriptorContext) As Boolean
Return True
End Function
Public Overrides Function GetProperties(context As ITypeDescriptorContext, value As Object, attributes As Attribute()) As PropertyDescriptorCollection
Return TypeDescriptor.GetProperties(GetType(ComPortConfig), attributes)
End Function
End Class
End Namespace
—
3. さらに先へ:ドロップダウンリスト(UITypeEditor)との融合
TypeConverter単体でも強力だが、「ボーレートの値をテキスト入力ではなく、既定値のリスト(9600, 19200, 38400…)からドロップダウンで選択させたい」という現場の要求には、`GetStandardValues` メソッドをオーバーライドすることで対応できる。
`ComPortConfigConverter` に以下のメソッドを追加するだけで、PropertyGrid上にコンボボックスが出現する。
‘ 標準値のサポートを有効化
Public Overrides Function GetStandardValuesSupported(context As ITypeDescriptorContext) As Boolean
Return True
End Function
‘ ドロップダウンに表示する値のコレクションを返す
Public Overrides Function GetStandardValues(context As ITypeDescriptorContext) As TypeConverter.StandardValuesCollection
‘ 実際のシステムでは、ここでWin32 API (EnumPorts等) を叩いて動的に列挙することも多い
Dim standardRates As New List(Of Integer) From {4800, 9600, 19200, 38400, 57600, 115200}
Return New StandardValuesCollection(standardRates)
End Function
‘ ユーザーがリスト以外の自由入力禁則とするか(Trueの場合はドロップダウンリスト固定)
Public Overrides Function GetStandardValuesExclusive(context As ITypeDescriptorContext) As Boolean
Return False ‘ 自由入力も許可する場合はFalse
End Function
—
4. 現場のプロが知るべき「罠」とメモリ最適化の極意
長年、レガシーシステムや大規模なWindows Formsアプリケーションを保守してきたアーキテクトとして、現場で陥りがちな罠と対策を共有しておこう。
① メモリリークとCOM相互運用性の考慮
PropertyGridは内部で大量のリフレクションキャッシュを保持する。カスタムオブジェクトやTypeConverterが不必要に重い参照(特にWin32のSafeHandleやアンマネージドポインタを直接保持するオブジェクト)を内包している場合、フォームが破棄されてもガベージコレクションされない「メモリリーク地獄」に陥る。
- 対策: コンバーター内では静的(Shared)な状態を持たせず、インスタンス変数の保持は最小限に留めること。
② パフォーマンスのボトルネック
`GetProperties` や `ConvertFrom` は、ユーザーがPropertyGrid上でマウスを動かしたり、値に変更を加えるたびに高頻度で呼び出される。この中で重いDBクエリを発行したり、動的なリフレクションを毎回行うのは言語道断である。
- 対策: 頻繁に使用するメタデータは `Dictionary` 等でキャッシュし、O(1) で引けるように設計せよ。
—
総括
Visual Basic / VB.NETという言語は、その歴史の長さゆえに「古い」とやゆされることがある。しかし、.NETの深部を理解し、今回紹介した `TypeConverter` のような機構を駆使すれば、モダンなC#製アプリケーションにも劣らない、極めて洗練されたエンタープライズ向けのUIコンポーネントを作り上げることが可能だ。
規律と知見を持てば、VB.NETは最強の武器となる。現場のコードベースを、次の次元へと引き上げてほしい。
