ちおさん雑記帳

何の役にも立たない雑記から、誰かの役に立つ(かも知れない)メモなど・・

WPF ウィンドウ描画が画面に入り切っているかを判定する

アプリケーションのウィンドウ表示位置を記憶しておき、次回起動時に表示位置を復帰させたい。
ということがあるかも知れませ。しかし「但し、ウィンドウが画面からはみ出す場合には中央に表示させる」みたいな条件を考慮するにはどうしたら良いか、サンプルを作成してみました。

モニターが1台だけの場合はそれほど難しい話ではありませんが、複数モニターで

f:id:caffe1208:20200406094301p:plain

上記のような環境で、


f:id:caffe1208:20200406094348p:plain

このように、四隅が全てどこかのモニターに表示されている場合や


f:id:caffe1208:20200406094449p:plain

このように、右下が入りきっていない場合、


f:id:caffe1208:20200406094520p:plain

このように、左上が入りきっていない場合などに「はみ出している」ことを認識するには
「四隅何れかの座標が、どのモニターのデスクトップ領域にも含まれない」という判定が出来ればよいことになります。

この例における各座標を見てみましょう。


f:id:caffe1208:20200406094602p:plain

上記のような感じになります。

さて、この場合に「任意の座標がモニターの表示領域に入っている/入っていない」判定することになりますが、方法は幾つかあって
1つは、接続されている各モニターの座標情報を取得し、領域内にアプリケーションの座標が含まれているか判定する方法が考えられます。
接続されている複数(単一でも可)モニターの情報は、System.Windows.Forms名前空間のScreenクラス、AllScreensプロパティで取得可能で、例えば各モニターの情報を表示するには以下のようなコードになります。

            foreach (Screen scrn in Screen.AllScreens)
            {
                Console.WriteLine(scrn.DeviceName + " -> " + scrn.Bounds.ToString());
            }


実行結果
\\.\DISPLAY1 -> {X=0,Y=0,Width=1920,Height=1080}
\\.\DISPLAY2 -> {X=1920,Y=-150,Width=1920,Height=1080}


アプリケーションの座標4点について、AllScreensプロパティで取得した各モニターの領域に入っているかを判定することになりますが、モニターの枚数が増える(と言っても10枚単位とかはほぼあり得ないですが)と、その分処理時間が増える可能性が高いです。(特に一番右下側のモニターにアプリケーションを表示している場合)

何か良い方法はないか、とMSDNでScreenクラスについて見てみると、以下のようなメソッドが用意されていることが分かりました。

f:id:caffe1208:20200406094900p:plain


この中でも、FromPoint(Point)メソッドと、FromRectangle(Rectangle)が使えそうなので詳細を見てみると


f:id:caffe1208:20200406094937p:plain


つまり、引数で指定した座標(Point)位置に該当するモニターのScreenオブジェクトが取得できるようですが、1点
「ポイントを保持するディスプレイがない複数ディスプレイ環境では、指定したポイントに最も近いディスプレイが返されます。」

ということは即ち、


f:id:caffe1208:20200406095015p:plain


上記のような表示になっている際に、アプリケーションの左上の座標を指定して、FromPointメドッドを実行すると、最も近いディスプレイ、つまりモニター①のScreenオブジェクトが返却されます。
アプリケーションの各座標についてFromPointメソッドを実行した場合、
左上 → モニター①が返却される(実際にはモニター①の範囲ではない)
右上 → モニター②が返却される
右下 → モニター②が返却される
左下 → モニター①が返却される
となり、各座標について取得したScreenが実際にその範囲(X座標はX~X+wigthの範囲内か、Y座標はY+heightの範囲内か)を判定すればよいことになります。

というわけで前置きが長くなりましたが、サンプルを作成します。

ボタン押下時に、現在表示されているウィンドウ位置の各四隅がモニターに実際に表示されているかどうかを判定する処理を以下に示します。

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            int top = (int)this.Top;
            int left = (int)this.Left;
            int buttom = (int)(this.Top + this.Height);
            int right = (int)(this.Left + this.Width);

            // 左上
            if(IsLocateOut(left, top))
            {
                Console.WriteLine("左上がどの画面にも表示されていない");
                return;
            }
            // 右上
            if (IsLocateOut(right, top))
            {
                Console.WriteLine("右上がどの画面にも表示されていない");
                return;
            }
            // 右下
            if (IsLocateOut(right, buttom))
            {
                Console.WriteLine("右下がどの画面にも表示されていない");
                return;
            }
            // 左下
            if (IsLocateOut(left, buttom))
            {
                Console.WriteLine("左下がどの画面にも表示されていない");
                return;
            }
            Console.WriteLine("ウィンドウは画面に完全に表示されている");
        }

        // FromPoint()の戻り値は実際ははみ出していても直近のScreenが返却されてしまうので座標を判定する
        private bool IsLocateOut(int locateX, int locateY)
        {
            var pt = new System.Drawing.Point(locateX, locateY);
            var screen = Screen.FromPoint(pt);

            if (screen.Bounds.X > pt.X || (screen.Bounds.X + screen.Bounds.Width) < pt.X)
            {
                return true;
            }
            if (screen.Bounds.Y > pt.Y || (screen.Bounds.Y + screen.Bounds.Height) < pt.Y)
            {
                return true;
            }
            return false;
        }


コードの処理内容については、MSDNでScreenクラスのリファレンス
https://docs.microsoft.com/ja-jp/dotnet/api/system.windows.forms.screen?view=netframework-4.8
を参照してもらえれば、難しいことはしていないので、説明は不要かなと思いますので割愛。

そんなわけで以上になります。