【実務・中級編】アプリケーション設定(My.Settings)の活用:ユーザーごとのウィンドウサイズ・位置や入力履歴を自動保存・復元する実装手順 – Visual Basic (VB / VB.NET)解析バイブル

スポンサーリンク

【Windows Forms】`My.Settings`で実現する!ウィンドウ位置と入力履歴の堅牢な永続化設計

業務アプリケーション開発において、ユーザーの利便性を左右する細かな配慮ご存知だろうか?
「前回閉じた位置にウィンドウが復元されない」「入力したはずの履歴が消えている」。これらはユーザーにとって無駄なストレスであり、業務効率化ツールの価値を大きく下げる要因となる。

多くの開発者が、この永続化のためにわざわざJSONファイルを書いたり、レジストリを直接叩いたり、果てはローカルDBにウィンドウ座標を保存するような過剰設計に走りがちだ。しかし、Visual Basic (VB.NET) には、最初から用意された極めて強力で安全な仕組みがある。それが `My.Settings` だ。

今回は、この `My.Settings` を極限まで使い倒し、マルチモニター環境や異常終了にも耐える、プロダクション品質のウィンドウ位置・入力履歴の自動保存・復元システム の実装手順を、チーフアーキテクトの視点からロジカルかつシャープに伝授する。

—

なぜ「適当なイベント」での保存はバグを生むのか?

まず、アマチュアプログラマーブルとプロのアーキテクトの決定的な違いについて話そう。
多くの解説記事では、ウィンドウの `Form_Closing` イベントや `Form_Closed` イベントで、平然と以下のようなコードを書いている。

.net
‘ 【アンチパターン】これではマルチモニターで事故が起きる
Private Sub MainForm_FormClosed(sender As Object, e As FormClosedEventArgs) Handles My.FormClosed
My.Settings.WindowLocation = Me.Location
My.Settings.WindowSize = Me.Size
My.Settings.Save()
End Sub

このコードの何が問題か?
1. 最小化・最大化状態の考慮漏れ: 最小化された状態でアプリを閉じると、次回起動時に `Location` が負の値(画面外)になり、二度と画面に表示されなくなるという致命的なバグ(ロストウィンドウ問題)が発生する。
2. マルチモニターの罠: 前回配置していたサブモニターが、次回の起動時に物理的に接続されていない場合、ウィンドウが画面外の座標に生成され、操作不能に陥る。
3. 無駄なI/O書き込み: `My.Settings.Save()` はユーザー設定ファイル(user.config)へ直接XMLを書き込む。これを高頻度で行うとパフォーマンスの低下やファイル破損のリスクを招く。

これらの懸念をすべてクリアし、実務でそのまま使える堅牢な設計コードを見ていこう。

—

実装手順:堅牢な `My.Settings` 構築の全貌

ステップ1:プロジェクトの「設定」定義

Visual Studioのソリューションエクスプローラーから、`My Project` > `設定` (Settings.settings)を開き、以下のプロパティを定義する。

| 名前 | 型 | スコープ | 初期値 | 用途 |
| :— | :— | :— | :— | :— |
| `WindowLocation` | Point | ユーザー | 100, 100 | ウィンドウの座標 |
| `WindowSize` | Size | ユーザー | 800, 600 | ウィンドウのサイズ |
| `WindowState` | System.Windows.Forms.FormWindowState | ユーザー | Normal | 最大化・通常状態の保持 |
| `SearchKeywordHistory` | System.Collections.Specialized.StringCollection | ユーザー | (空) | 入力履歴の保持 |

—

ステップ2:プロダクションコード(MainForm.vb)

以下に、例外を完全にガードし、マルチモニターや画面外配置にも対応した完全版のフォームコードを提示する。

.net
Public Class MainForm

Private Sub MainForm_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Try
‘ 1. ウィンドウ状態の復元(最大化・通常など)
Me.WindowState = My.Settings.WindowState

‘ 2. 最小化または最大化状態で保存されていた場合の例外処理
If Me.WindowState = FormWindowState.Normal Then
Me.Size = My.Settings.WindowSize
Dim savedLocation As Point = My.Settings.WindowLocation

‘ 【重要】マルチモニター環境や画面解像度変更による「画面外ロスト」の防止
‘ 画面のどこかにウィンドウの矩形が含まれているか厳格にチェックする
If IsVisibleOnAnyScreen(New Rectangle(savedLocation, Me.Size)) Then
Me.Location = savedLocation
Else
‘ 画面外にいる場合は安全な中央配置へフォールバック
Me.StartPosition = FormStartPosition.CenterScreen
End If
End If

‘ 3. 入力履歴(ComboBoxなど)の復元
LoadInputHistory()

Catch ex As Exception
‘ 致命的な設定破損への備え:デフォルト値でフォールバック起動させる
System.Diagnostics.Debug.WriteLine($”設定の復元に失敗しました: {ex.Message}”)
Me.StartPosition = FormStartPosition.CenterScreen
End Try
End Sub

Private Sub MainForm_FormClosing(sender As Object, e As FormClosingEventArgs) Handles MyBase.FormClosing
Try
‘ 1. 現在のウィンドウ状態に応じた座標・サイズの退避
If Me.WindowState = FormWindowState.Normal Then
My.Settings.WindowLocation = Me.Location
My.Settings.WindowSize = Me.Size
End If

‘ 実際にタスクバーに隠れるような状態でない限り、WindowStateを保存
My.Settings.WindowState = If(Me.WindowState = FormWindowState.Minimized, FormWindowState.Normal, Me.WindowState)

‘ 2. 入力履歴の保存処理
SaveInputHistory()

‘ 3. 永続化ファイル(user.config)への書き込み確定
My.Settings.Save()

Catch ex As Exception
‘ シャットダウン時の例外はアプリをクラッシュさせないようログ出力に留める
System.Diagnostics.Debug.WriteLine($”設定の保存に失敗しました: {ex.Message}”)
End Try
End Sub

”’

”’ 指定された矩形が、現在接続されているいずれかのディスプレイ画面内に存在するか判定する
”’

Private Function IsVisibleOnAnyScreen(winRect As Rectangle) As Boolean
For Each screen As Screen In Screen.AllScreens
‘ WorkingArea(タスクバーを除く)または Bounds(画面全体)との交差判定
If screen.WorkingArea.IntersectsWith(winRect) Then
Return True
End If
Next
Return False
End Function

”’

”’ ComboBoxの入力履歴をStringCollectionから復元する
”’

Private Sub LoadInputHistory()
cmbSearchKeyword.Items.Clear()
If My.Settings.SearchKeywordHistory IsNot Nothing Then
For Each item As String In My.Settings.SearchKeywordHistory
cmbSearchKeyword.Items.Add(item)
Next
End If
End Sub

”’

”’ ComboBoxの入力履歴を重複排除&最大件数制限をかけてStringCollectionへ保存する
”’

Private Sub SaveInputHistory()
Dim currentText As String = cmbSearchKeyword.Text.Trim()

‘ 履歴用コレクションの初期化(Null防護)
If My.Settings.SearchKeywordHistory Is Nothing Then
My.Settings.SearchKeywordHistory = New Specialized.StringCollection()
End If

‘ 未入力、または既に存在する場合は一旦削除して最上位へ持っていく(LIFO構造)
If Not String.IsNullOrEmpty(currentText) Then
If My.Settings.SearchKeywordHistory.Contains(currentText) Then
My.Settings.SearchKeywordHistory.Remove(currentText)
End If

My.Settings.SearchKeywordHistory.Insert(0, currentText)

‘ 履歴が20件を超えたら古いものを切り捨てる(メモリ肥大化防止)
While My.Settings.SearchKeywordHistory.Count > 20
My.Settings.SearchKeywordHistory.RemoveAt(My.Settings.SearchKeywordHistory.Count – 1)
End While
End If
End Sub

End Class

—

アーキテクトからの実践的アドバイス:ファイル実態とトラブルシューティング

`My.Settings` を使う上で、エンジニアが知っておくべき「黒魔術的な仕様」がある。

1. 保存先のパスの罠:
`My.Settings`(実体は `user.config`)は、ユーザープロファイル下の以下のパスに動的に生成される。
`C:\Users\<ユーザー名>\AppData\Local\<会社名またはプロジェクト名>\_<証跡ハッシュ>\v1.0.0.0\user.config`
ここで注意すべきなのは、アプリケーションのバージョン番号(v1.0.0.0など)が上がると、設定ファイルが別フォルダ扱いになり、前回の設定が引き継がれなくなるという仕様だ。
これを解決するには、新バージョン初回起動時に `My.Settings.Upgrade()` を呼ぶコードを `Main` メソッドや `Form_Load` の冒頭に仕込んでおく必要がある。

.net
‘ バージョンアップ時にも旧設定を引き継がせるための定石
If My.Settings.NeedsUpgrade Then
My.Settings.Upgrade()
My.Settings.NeedsUpgrade = False
My.Settings.Save()
End If

(※あらかじめ設定デザイナーで `NeedsUpgrade` ブール値(初期値:True)を作っておくこと)

2. ファイル破損への耐性:
PCの強制シャットダウンなどで `user.config` が破損すると、次回起動時にアプリ全体がクラッシュする。これを防ぐために、上記のコードの通り `Try-Catch` で完全に囲み、万が一破損している場合は `My.Settings.Reset()` を呼び出して初期状態に自動リカバリする仕組みを組み込んでおくのが「プロの仕事」である。

—

まとめ

今回の実装により、以下のメリットが完全に担保された。

  • マルチモニター環境でのウィンドウロスト事故のゼロ化
  • ユーザーにとって最も心地よい状態(最大化・通常位置)の完全な永続化
  • 肥大化しない安全な入力履歴の管理

手軽な機能である `My.Settings` だからこそ、設計のディテールにこだわることで、業務アプリケーション全体の品格と信頼性が劇的に向上する。ぜひ、あなたの開発するツールにもこの堅牢なアーキテクチャを導入し、ワンランク上のエンジニアリングを体感してほしい。

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