MDIの亡霊を葬り、タブインターフェースへ。VB.NETで実現するモダンUIの極致
かつて、Windows Formsにおける「MDI(Multiple Document Interface)」は、マルチタスク業務の王道であった。しかし、2024年の今、親ウィンドウの中で散らばる子ウィンドウを制御する手法は、もはや「技術的負債」の象徴だ。
メモリリークの温床となり、フォーカス遷移のバグに悩まされ、高DPI環境では無残に崩れるMDI。今すぐこれを捨て、`TabControl`を用いたモダンな構成へ移行すべきだ。本稿では、レガシーを叩き壊し、堅牢でメモリ効率の良いタブ型アーキテクチャを構築する極意を伝授する。
—
1. MDIからタブへ:設計思想の転換
MDIの問題点は、子フォームが親フォームのメモリ空間に「ぶら下がる」不安定さにある。対して、タブインターフェースは「`TabPage`をコンテナとし、そこに`Form`(または`UserControl`)を埋め込む」という明快な構造だ。
アーキテクチャの要点
- Formの埋め込み: `TopLevel = False` を指定し、親のコンテナにねじ込む。
- ライフサイクル管理: タブを閉じる際、`Dispose()`を明示的に呼び出し、マネージドヒープの断片化を最小限に抑える。
- 疎結合な通信: 親と子の依存を排除し、インターフェースやイベントによる疎結合な通信を行う。
—
2. 実装の心臓部:タブへのフォーム埋め込み処理
単にフォームを表示するのではない。ライフサイクルを管理し、メモリを解放する。ここがエンジニアの腕の見せ所だ。
”’
”’
Public Sub OpenFormInTab(Of T As {Form, New})(tabContainer As TabControl)
Dim newForm As New T()
‘ TopLevelをFalseにすることで、コントロールとして埋め込み可能にする
newForm.TopLevel = False
newForm.FormBorderStyle = FormBorderStyle.None
newForm.Dock = DockStyle.Fill
‘ タブページの生成とフォームの追加
Dim newPage As New TabPage(newForm.Text)
newPage.Controls.Add(newForm)
tabContainer.TabPages.Add(newPage)
‘ フォームの表示
newForm.Show()
tabContainer.SelectedTab = newPage
End Sub
—
3. メモリの最適化:タブを閉じる時の「作法」
MDIで最も悲惨なのは、ウィンドウを閉じてもインスタンスが残存するケースだ。`Dispose`を呼ぶだけでは不十分な場合もある。以下のクリーンアップロジックをテンプレート化せよ。
”’
”’
Private Sub CloseCurrentTab(tabContainer As TabControl)
If tabContainer.SelectedTab Is Nothing Then Return
Dim currentTab As TabPage = tabContainer.SelectedTab
‘ フォームを検索して明示的にDispose
For Each ctrl As Control In currentTab.Controls
If TypeOf ctrl Is Form Then
Dim f As Form = DirectCast(ctrl, Form)
f.Close()
f.Dispose() ‘ マネージド/アンマネージドリソースの解放を確実に
End If
Next
‘ タブページの削除
tabContainer.TabPages.Remove(currentTab)
currentTab.Dispose()
‘ ガベージコレクションの明示的呼び出しは基本推奨しないが、
‘ 大規模な業務アプリでは、画面遷移の区切りで以下を検討する価値がある
‘ GC.Collect()
‘ GC.WaitForPendingFinalizers()
End Sub
—
4. 伝説のエンジニアからの忠告:Windows APIの活用
Windows Formsは古いフレームワークだが、`User32.dll`を直接叩けば、ネイティブレベルの挙動を制御できる。例えば、タブの描画が重い、あるいは表示がちらつく場合、`SendMessage`で描画を一時停止させるのが鉄則だ。
‘ 描画のチラつきを抑えるための定数定義
Private Const WM_SETREDRAW As Integer = &HB
Private Shared Function SendMessage(hWnd As IntPtr, wMsg As Integer, wParam As Boolean, lParam As Integer) As Integer
End Function
‘ 処理開始前
SendMessage(tabContainer.Handle, WM_SETREDRAW, False, 0)
‘ … 処理 …
‘ 処理終了後
SendMessage(tabContainer.Handle, WM_SETREDRAW, True, 0)
tabContainer.Refresh()
—
最後に:なぜ「VB.NET」なのか
「今さらVB.NETか」という声が聞こえるかもしれない。だが、レガシーな業務システムにおいて、VB.NETの持つ生産性と、.NET Frameworkによる堅牢な資産価値を凌駕する技術スタックは、そう簡単には現れない。
MDIという過去の遺物に固執するのではなく、タブという現代的なインターフェースでそれを包み込む。この設計思想こそが、現場を止めることなく、システムを次世代へと繋ぐ架け橋となる。
諸君、コードを汚すな。オブジェクトの寿命を支配し、メモリの最後の一滴まで制御し尽くせ。それが、業務自動化エンジニアの誇りだ。
