【テクニカル・上級編】ToolStripManagerとSettingsによるツールバーカスタマイズの永続化:ユーザーごとのドッキング位置と表示状態の保存 – Visual Basic (VB / VB.NET)解析バイブル

スポンサーリンク

ToolStripManagerとSettingsによるツールバーカスタマイズの永続化:ユーザーごとのドッキング位置と表示状態の保存

Windows Forms(WinForms)におけるUI設計の極みは、エンドユーザーが自身の業務効率に合わせて自由にカスタマイズできる柔軟性と、それをミリ単位で再現する堅牢な永続化メカニズムの両立にある。

長年、レガシーな業務システムやVBAからの移行期にある基幹システムを見てきた中で、最も要望が多く、かつ実装者の力量が問われるのが「ツールバーとメニューのドッキング位置、可視状態、およびオーバーフロー(隠し領域)の状態の完全保存」だ。

今回は、`.NET Framework / .NET (Core)` における `ToolStripManager` のシリアライズ機能と、`My.Settings`(または `ApplicationSettingsBase`)を極限までチューニングし、ユーザーごとのコンテキストを完全に復元するアーキテクチャを解説する。

—

1. アーキテクチャの核心:なぜ標準の保存機能だけでは破綻するのか?

多くの開発者は、`ToolStripManager.SaveSettings` をフォームの `FormClosing` イベントで呼び出し、`LoadSettings` を `Load` イベントで呼べばすべて解決すると錯覚する。しかし、以下の条件が揃った瞬間、アプリケーションはクラッシュするか、レイアウトが崩壊して二度と元に戻らなくなる。

1. 動的に生成されるコントロール: プラグイン構造や権限管理により、起動時にツールバーの構成が動的に変わる場合。
2. マルチモニター環境のDPIスケーリング: 解像度やスケーリング率(125%, 150%など)が異なる環境間でのドッキング座標の矛盾。
3. ユーザープロファイルの分離: 複数ユーザーが同一端末を共有する環境(リモートデスクトップ等)での設定ファイルの競合。

これらを制圧するためには、`ToolStripManager` の内部動作を理解し、設定データのシリアライズストリームを明示的に制御する必要がある。

—

2. 実装コード:完全永続化エンジン

以下のコードは、フォームのライフサイクルと完全に調停し、メモリリークや設定ファイルの破損を防ぐためのプロダクション品質のVB.NET実装である。

Imports System.Windows.Forms
Imports System.IO

Public Class MainForm
Inherits Form

‘ ユーザー設定のキー名(必要に応じて動的に変更可能)
Private Const SETTING_KEY_TOOLSTRIP As String = “ToolStripCustomLayout”

Private Sub MainForm_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Try
‘ 1. フォームのインスタンス名義でToolStripの状態をロード
LoadToolbarState()

Catch ex As Exception
‘ 破損した設定ファイルによる起動不能(Fatal Error)を防ぐためのフォールバック
System.Diagnostics.Trace.WriteLine($”ツールバー設定の復元に失敗しました: {ex.Message}”)
ToolStripManager.LoadSettings(Me) ‘ デフォルト状態へのフォールバック
End Try
End Sub

Private Sub MainForm_FormClosing(sender As Object, e As FormClosingEventArgs) Handles MyBase.FormClosing
Try
‘ 2. フォームが破棄される直前に状態をキャプチャして永続化
SaveToolbarState()

Catch ex As Exception
System.Diagnostics.Trace.WriteLine($”ツールバー設定の保存に失敗しました: {ex.Message}”)
End Try
End Sub

”’

”’ ツールバーの配置情報をSettingsへシリアライズして保存する
”’

Private Sub SaveToolbarState()
‘ ストリームの初期化(メモリ上でのバイナリ/XML構築)
‘ 注: ToolStripManagerは内部で独自のXMLフォーマットまたはシリアライズを使用する

‘ 既存の設定値コンテナ(My.Settings)にカスタムプロパティが存在すると仮定
‘ ※My.Settings.Item に string や byte() として保存する設計とする

Dim settingsKey As String = Me.Name & “_” & SETTING_KEY_TOOLSTRIP

‘ ToolStripManagerにフォーム上の全ToolStripの状態を保存させる
‘ ※注意: 保存対象のToolStripには一意の Name プロパティが必須である。
Dim configString As String = CaptureToolStripSettings()

‘ My.Settingsへ格納(アプリケーションスコープではなくユーザー固有スコープ)
My.Settings(settingsKey) = configString
My.Settings.Save()
End Sub

”’

”’ ツールバーの配置情報を復元する
”’

Private Sub LoadToolbarState()
Dim settingsKey As String = Me.Name & “_” & SETTING_KEY_TOOLSTRIP

If My.Settings(settingsKey) IsNot Nothing AndAlso Not String.IsNullOrEmpty(My.Settings(settingsKey).ToString()) Then
Dim configString As String = My.Settings(settingsKey).ToString()

‘ 状態の適用
RestoreToolStripSettings(configString)
End If
End Sub

”’

”’ ToolStripManagerのリフレクションおよび内部ストリーム操作をカプセル化
”’

Private Function CaptureToolStripSettings() As String
‘ 標準の ToolStripManager.SaveSettings(Form) はファイルや特定のストリームに直接書き出すため、
‘ 独自にStringへバインドするためのラッパー

Using memStream As New MemoryStream()
‘ 注: .NET Frameworkの仕様上、ToolStripManagerはフォーム全体のコントロールを走査する
ToolStripManager.SaveSettings(Me, memStream)
memStream.Position = 0

Using reader As New StreamReader(memStream)
Return reader.ReadToEnd()
End Using
End Using
End Function

Private Sub RestoreToolStripSettings(configString As String)
Using memStream As New MemoryStream()
Using writer As New StreamWriter(memStream)
writer.Write(configString)
writer.Flush()
memStream.Position = 0

ToolStripManager.LoadSettings(Me, memStream)
End Using
End Using
End Sub

End Class

—

3. シニアエンジニアが押さえるべき実装の急所

A. `Name` プロパティの絶対的一意性

`ToolStripManager` は、保存と復元においてコントロールの `Name` プロパティを主キーとしてレイアウトを紐付ける。動的に生成されるツールバー(例:`Dim ts As New ToolStrip()`)に対して `Name` を動的かつ適当に割り振ったり、起動ごとに名前が変わるような実装にしている場合、復元時に完全に無視されるか、致命的な例外(`ArgumentException`)を引き起こす。
対策: デザイナで配置されたもの、動的生成を問わず、すべての `ToolStrip`, `MenuStrip`, `StatusStrip` にはハードコードされた不変のプレフィックスを持つ一意の `Name` を付与せよ。

B. オブジェクトのライフサイクルとメモリ管理

上記のコード例で `MemoryStream` や `StreamWriter` を `Using` ステートメントで囲んでいる点に注目してほしい。
UIスレッド周辺でのストリーム操作やファイルI/Oは、不用意に放置するとガベージコレクション(GC)の世代交代を阻害し、特にMDI(マルチドキュメントインターフェイス)環境においてメモリリークの温床となる。マネージド資源であっても、非管理に近い挙動を示すWinFormsのAPIを扱う際は、「スコープを最小限にし、即座に破棄する」のが鉄則である。

C. 設定ファイルの肥大化とレジストリ・DB連携

`My.Settings`(内部的には `local.settings` ファイル / `%LOCALAPPDATA%`以下のuser.config)は非常に手軽だが、エンタープライズ環境では以下の問題が生じる。

  • 端末のローミングプロファイル環境で設定が同期されない。
  • ユーザーがプロファイルをクリアした際に設定が消失する。

もし厳格な「ユーザーごとのシステム間連携」が求められる場合は、`My.Settings` への依存を断ち切り、上記コードの `CaptureToolStripSettings()` が吐き出す `String`(XMLデータ)を、直接データベースのユーザー別プロファイルテーブルやActive Directory(AD)の拡張属性、あるいはカスタムのJSON/XMLファイルとして明示的にファイルI/Oで保存する設計に昇華させるべきだ。

—

終わりに:レガシーの皮をかぶった高度なUI制御

Visual BasicおよびWindows Formsは、古い技術と揶揄されることがある。しかし、現場の泥臭い要求(「前回閉じた位置に、ピクセル単位で、ユーザーの好みの状態でツールバーを出してくれ」)に対して、これほどダイレクトに応答できるフレームワークは他にない。

`ToolStripManager` の内部仕様を理解し、メモリとストリームのライフサイクルを完全に掌握したコードこそが、エンドユーザーに「吸い付くような操作性」を提供し、保守員としてのエンジニアの価値を証明する盾となる。妥協のないコードを書き続けよ。

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