フォーム位置の調整 : RSSリーダを.NET Framework 2.0で作る (第12回)
画面外に出てしまった場合の自動復元機能をフォームに追加します。
今回のテクニカル トピック
- 画面設定の変更の検出
- 画面外に出ていないか判定
- 画面内に入れる
画面外にでる?
通常の操作ではウィンドウが完全に画面外に出ることは無いですが、画面外に出てしまう操作もあります。
例えば最近増えてきたデュアル ディスプレイ環境にリモート デスクトップでログオンするとスクリーン数が減るためセカンダリスクリーンにあったウィンドウが画面外に出ます。
しかし、Microsoft Office 2003(WordやExcel)などは自動的に画面内に復帰してきます。
また、前回起動時のフォームの位置で表示するような気の効いたアプリケーションの場合、スクリーン外に表示してしまわないようにこの処理が必要になります。
このような動作をフォームに組み込みます。
最適なディスプレイに引き込もうとすると幾何計算を必要とするため、ここでは単純な処理を選択しています。
画面外に出ないようにする
この復帰処理は複数のフォームクラスから再利用します。
新規にFormBoundsControllerクラスを作成して「画面設定の検出」「画面外の出ていないかの判定」「画面内に入れる」処理を担当させます。
各フォームクラスはFormBoundsControllerのインスタンスを生成して、画面設定の変更時の動作を任せます。
画面の設定変更の検出には、SystemEventsクラスを使用
画面関連の設定の変更を知るには、Microsoft.Win32名前空間のSystemEvents.DisplaySettingsChanged イベントを使用します。
MSDNドキュメントには、SystemEventsの各イベントの説明に注意点として
「これは静的イベントなので、アプリケーションが破棄されるときにイベント ハンドラをデタッチしないと、メモリ リークが発生します。」
という一文が.NET 2.0より追加されています。
しかし、サンプルコードにはそれらしいコードがない。
SystemEventsLowMemoryイベントはObsoleteになっているので、.NET 2.0に移行する場合に注意が必要である。
SystemEventsには、これ以外にも興味深いイベントがあるので一度見ておくと良いかもしれない。
SystemEvents.DisplaySettingsChanged イベントは、FormBoundsController内のスタティックメソッドでアタッチします。 Application.ApplicationExitイベントにもアタッチして、アプリケーションの終了時にSystemEvents.DisplaySettingsChanged イベントのデタッチ処理を行います。
これで、画面設定変更のタイミングが取れるようになります。
コードサンプル
#region システム状態の変更を検出して変更を検出する内部的なイベントに変換する
/// <summary>
/// FormBoundsControllerクラス内部で使用するイベント
/// </summary>
/// <remarks>
/// <para>FormBoundsControllerのインスタンスは、このイベントに
/// 接続することで間接的にSystemEvents.DisplaySettingsChanged
/// イベントを受け取ります。<br/>
/// これは、SystemEvents.DisplaySettingsChangedイベントへの
/// 接続数を制限することによりリークの発生リスクを下げるため
/// の措置です。</para>
/// </remarks>
private static event EventHandler DisplayChanged;
/// <summary>
/// スタティック コンストラクタ
/// </summary>
static FormBoundsController()
{
Application.ApplicationExit
+= new EventHandler(ApplicationExitCallback);
SystemEvents.DisplaySettingsChanged
+= new EventHandler(DisplaySettingsChangedCallback);
}
/// <summary>
/// アプリケーション終了時の後処理
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
static void ApplicationExitCallback(object sender, EventArgs e)
{
SystemEvents.DisplaySettingsChanged
-= new EventHandler(DisplaySettingsChangedCallback);
DisplayChanged = null;
}
/// <summary>
/// 画面設定が変更された際に呼び出されます。
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
static void DisplaySettingsChangedCallback(object sender, EventArgs e)
{
if (DisplayChanged == null)
return;
DisplayChanged(null, EventArgs.Empty);
}
#endregion
フォームが画面外の出ていないかの判定
.NET Framework では、Screen.AllScreens プロパティにより全スクリーンの情報を取得することが出来ます。
このスクリーンに対してフォームの外形が含まれているかチェックします。
判定コードの一部: bounds=フォームの外形
// 接続されているスクリーンのいずれかに入っているか判定する
foreach (Screen sc in Screen.AllScreens)
{
// スクリーンの中にあるか
Rectangle cross = sc.Bounds;
cross.Intersect(bounds);
if (cross.IsEmpty)
continue;
// スクリーンの作業エリアに入っているか
Rectangle workCross = sc.WorkingArea;
workCross.Inflate(-bounds.Width, -bounds.Height);
workCross.Intersect(bounds);
if (!workCross.IsEmpty)
return bounds;
// 入れる
return NormalizeFormBounds(bounds, sc);
}
// どのスクリーンにも含まれていない。
// たぶんマルチディスプレイじゃ無くなったか、
// 画面の解像度が小さくなったかで、画面外に出てしまった。
return NormalizeFormBounds(bounds, Screen.PrimaryScreen);
なお、連続したスクリーンの境目の動作は簡易になっています。 また、完全にスクリーン外に出てしまっている場合、プライマリ スクリーンに表示するようにしています。
画面内に入れる
画面外に出てしまったフォームを実際に画面内に引き入れる処理では、どのスクリーンに入れるか指定するようにします。
上記判定時に引き入れる先のスクリーンを指定します。
/// <summary>
/// 指定したスクリーンの作業エリアに全体が見えるように移動する
/// </summary>
/// <param name="bounds">現在の外形矩形</param>
/// <param name="screen">調整先のスクリーン</param>
/// <returns>調整結果の外形領域</returns>
private static Rectangle NormalizeFormBounds(
Rectangle bounds, Screen screen)
{
int width = bounds.Width;
int height = bounds.Height;
// 画面に入るように調整する
if (bounds.Right < screen.WorkingArea.Left + width)
{
bounds.X = screen.WorkingArea.Left;
}
if (bounds.Left > screen.WorkingArea.Right - width)
{
bounds.X = screen.WorkingArea.Right - bounds.Width;
}
if (bounds.Y > screen.WorkingArea.Bottom - height)
{
bounds.Y = screen.WorkingArea.Bottom - bounds.Height;
}
if (bounds.Bottom < screen.WorkingArea.Top + height)
{
bounds.Y = screen.WorkingArea.Top;
}
return bounds;
}
まとめ
残念ながら私の環境はシングル ディスプレイなので動作確認できていないため、FormBoundsController の完全なコードはテストしてから出すことにします。

Comments