こんにちは!Windows Formsアプリケーションの開発で、こんな恐怖を味わったことはありませんか?
「自宅のデュアルモニター環境でアプリを右側の画面に配置して終了したのに、会社に戻ってノートPC単体(シングルモニター)で起動したら……あれ? アプリのウィンドウが画面のどこにも見当たらない! タスクバーにはいるのに、クリックしても表示されないし、操作不能になった!」
……開発現場で本当によくある、そしてユーザーを絶望させる「ウィンドウ行方不明事件」です。
マルチディスプレイが当たり前になった現代、アプリの座標記憶と復元は、プログラマなら絶対に避けて通れない重要課題です。今回は、VB.NET(Windows Forms)を使って、「どんなディスプレイ環境の変化にも負けず、絶対に画面外へ消失しない鉄壁のウィンドウ座標管理ロジック」を一緒に作っていきましょう。
ここをクリアすれば、あなたのアプリの信頼性はプロの品質にグッと近づきますよ。さあ、始めましょう!
—
なぜウィンドウは「画面外」へ消えてしまうのか?
まずは敵を知ることから。VB.NETでウィンドウの場所を保存する時、私たちは通常、`Settings`やファイルに `Left`(X座標)と `Top`(Y座標)の値を保存しますよね。
そして、アプリ起動時にこう復元します。
.net
‘ 【危険なコードの例】保存された座標をそのままブチ込む
Me.Left = My.Settings.WindowLeft
Me.Top = My.Settings.WindowTop
一見、何の問題もなさそうですが……ここにマルチモニターの罠があります。
- 昨日: 4Kのメイン画面の右側に、サブモニター(X座標 = 1920)を置いていた。
- 今日: モニターを外し、ノートPC単体(画面解像度 1920×1080)で起動した。
- 結果: アプリは記憶に従い「X座標 = 2000」に描画しようとする。しかし、現在のPCの画面は1920までしかない! 結果、画面の遥か右外(宇宙空間)に描画され、ユーザーからは見えなくなるのです。
これを防ぐためには、「復元しようとしている座標が、現在接続されているいずれかのディスプレイの有効領域内に収まっているか?」をチェックし、はみ出そうなら強制的に安全な位置へ引き戻す「境界チェック(クランピング処理)」が絶対に必要なのです。
—
解決の鍵:`Screen` クラスを完全に使いこなす
VB.NET(Windows.Forms)には、接続されているモニター情報を取得するための最強のクラス、`System.Windows.Forms.Screen` が用意されています。
これを使うと、「今、PCには何台のモニターがあって、それぞれの表示領域(Bounds)はどこか?」が手に取るように分かります。
安全装置付き・ウィンドウ復元&保存の全コード
それでは、実際のWindows Forms(フォーム)に組み込んでそのまま使える、洗練された実装コードをお見せします。フォームの `Load` イベントと `FormClosing` イベントに、以下のコードを記述してください。
.net
Imports System.Windows.Forms
Public Class MainForm
‘ =================================================================
‘ 1. アプリ起動時:安全にウィンドウ位置を復元する
‘ =================================================================
Private Sub MainForm_Load(sender As Object, e As EventArgs) Handles MyBase.Load
‘ 前回の終了位置を取得(設定ファイル等から読み込む想定)
Dim savedLeft As Integer = My.Settings.WindowLeft
Dim savedTop As Integer = My.Settings.WindowTop
Dim savedWidth As Integer = My.Settings.WindowWidth
Dim savedHeight As Integer = My.Settings.WindowHeight
‘ サイズが極端に小さくないか初期値チェック
If savedWidth > 100 Then Me.Width = savedWidth
If savedHeight > 100 Then Me.Height = savedHeight
‘ 【最重要】画面外へのはみ出し防止&マルチディスプレイ対応検証
‘ 保存された座標のウィンドウが、現在のいずれかの画面に「見える状態」で存在するか判定する
Dim restoreRect As New Rectangle(savedLeft, savedTop, Me.Width, Me.Height)
If IsVisibleOnAnyScreen(restoreRect) Then
‘ 安全確認OK:前回の位置に配置
Me.StartPosition = FormStartPosition.Manual
Me.Left = savedLeft
Me.Top = savedTop
Else
‘ 安全確認NG(画面外に消える):プライマリモニターの中央等にフォールバック
Me.StartPosition = FormStartPosition.CenterScreen
System.Diagnostics.Debug.WriteLine(“警告: 前回のウィンドウ位置が画面外だったため、中央に初期化しました。”)
End If
End Sub
‘ =================================================================
‘ 2. アプリ終了時:現在の位置とサイズを保存する
‘ =================================================================
Private Sub MainForm_FormClosing(sender As Object, e As FormClosingEventArgs) Handles MyBase.FormClosing
‘ 最小化または最大化されている時は、通常の座標を保存してしまうのを防ぐため、
‘ WindowStateがNormalの時だけ座標を記憶する(プロの技!)
If Me.WindowState = FormWindowState.Normal Then
My.Settings.WindowLeft = Me.Left
My.Settings.WindowTop = Me.Top
My.Settings.WindowWidth = Me.Width
My.Settings.WindowHeight = Me.Height
My.Settings.Save()
End If
End Sub
‘ =================================================================
‘ 3. 補助関数:指定した矩形が「いずれかの画面にしっかり含まれているか」を判定
‘ =================================================================
Private Function IsVisibleOnAnyScreen(targetRect As Rectangle) As Boolean
‘ 接続されているすべてのモニターをループしてチェック
For Each scr As Screen In Screen.AllScreens
‘ WorkingArea(タスクバーを除く領域)または Bounds(画面全体)を使用
‘ ここではユーザーがタスクバーの下に埋もれないよう WorkingArea を基準にします
Dim screenArea As Rectangle = scr.WorkingArea
‘ ウィンドウの大部分(または一部)がその画面と重なっているか判定
‘ ※今回は「ウィンドウのタイトルバー付近(左上基準点)」が画面内に収まっているかで判定します
If screenArea.Contains(targetRect.Location) Then
Return True ‘ 画面内に発見!
End5 If
Next
Return End If どの画面にもカスリもしなかった…
Return False
End Function
End Class
—
恵まれたエンジニアならスルーしがちな、このコードの「こだわりポイント」を3つ解説します。
こだわりポイント1:`WindowState = FormWindowState.Normal` のチェック
ユーザーがウィンドウを「最大化」または「最小化」している状態でアプリを閉じることがあります。もし最大化中に `Me.Left` や `Me.Top` を保存してしまうと、画面いっぱいに広がった座標(例: 左上 `-8, -8` など)が記録され、次回起動時に変な位置で中途半端に復元されてしまいます。
必ず「普通のサイズ(Normal)」の時だけ座標を保存するのが、UI設計の美しいマナーです。
こだわりポイント2:`Screen.AllScreens` による全画面走査
PCに繋がっているモニターが1台だろうが、3台だろうが、`Screen.AllScreens` はすべてのモニター情報を配列で返してくれます。ループを回して、保存された座標が「どのモニターの有効領域(WorkingArea)に属しているか」を完璧にキャッチします。
こだわりポイント3:安全装置(フォールバック)の存在
もし判定の結果、どこにもウィンドウが存在しなかった場合は、無理やりそこに表示するのではなく、`FormStartPosition.CenterScreen`(画面中央)にパッと逃がしてあげます。これだけで、「アプリが起動しなくなった!」というユーザーからの絶望的な問い合わせを100%防ぐことができます。
—
さらに完璧を目指すあなたへ(発展編:一部はみ出しの自動クランプ)
上記のコードでは、「画面に全くカスリもしなかったら中央に戻す」という仕様にしました。
しかし、より親切な設計にするなら、「画面からはみ出た分だけ、内側にスライドさせて強制的に画面内に収める(クランプする)」というアプローチもあります。
.net
‘ 例:もし右側にはみ出ていたら、画面の右端にピタッと合わせる補正ロジック
‘ (応用として考えてみてくださいね!)
マルチディスプレイ環境への対応は、ユーザービリティ(使いやすさ)に直結する非常に価値の高い実装です。「おっ、このアプリ、マルチモニターの環境が変わってもちゃんと俺の画面を覚えていてくれるぞ」と、ユーザーに密かな感動を与えることができます。
ここをクリアしたあなたは、もう単なる「コードをコピペする初心者」ではありません。ユーザーの体験価値までデザインできる、立派なWindows Formsエンジニアです。
ぜひ、あなたの開発するアプリケーションにこのロジックを組み込んで、ワンランク上の安定性を手に入れてくださいね!
