WinUI3アプリを高DPI(>100%)でappWindow.MoveAndResize()する

もくじ
https://tera1707.com/entry/2022/02/06/144447#WinUI3

やりたいこと

WinUI3アプリのウインドウサイズを変更するには、AppWindowクラスを使えばよいことが分かった。
こちら

また、WinUI3アプリで、ディスプレイの倍率(DPI)が100%でないときに、とりあえずWindowの中身がぐちゃっとつぶれたりはみ出したりしないようにするには、dpiAwareとdpiAwarenessの設定を「System Aware」 にすればよいことが分かった。
こちら

で、高倍率(DPI)のディスプレイで、AppWindowMoveAndResize()を使ってウインドウのサイズをいろいろ変えたいときに、MoveAndResize()にどんな値を入れてやればよいのか?というのがわからなかった。

具体的には、

  • 倍率=100%のときに、1920*1080のディスプレイを使っている場合、WinUI3アプリのウインドウでappWindow.MoveAndResize(new Windows.Graphics.RectInt32(0,0,1920,1080));とすると、ディスプレイいっぱいにウインドウが表示されるが、
  • 倍率=150%のときに、同じディスプレイを使ってappWindow.MoveAndResize(new Windows.Graphics.RectInt32(0,0,1920,1080));とすると、ウインドウは同じように画面いっぱいギリギリで表示されるが、中身は1.5倍大きくなっているので中身の部品がはみ出して表示されてしまう

ということになってしまった。

どうしたらいい?の具体的対処方法のドキュメントが見つけられなかったので、いろいろ試してみる。

前提

今回、DPI(倍率)の設定というのは、下図のことを言っています。

前提(環境)

  • Windows Home 21H2 22000.739
  • VisualStudio2022 Community 17.2.3
  • WinUI3.0
  • Windows App SDK 1.0.0
  • 2022年7月の時点で調査実施

ゴールの考え方

DPI(倍率)は、解像度の高いディスプレイの時に倍率を上げる(100%より大きくする)設定をすると、倍率の分だけアプリを大きく表示する(≒ディスプレイの解像度が高すぎてすごく小さくなってしまうのをデカくする)ためのものだと思う。

なので、倍率を高くしたときは、

  • ウインドウ自体も、倍率をかけた分だけ大きく表示する(150%だったら1.5倍にする)。
  • ウインドウの中のUI部品も、同じように倍率をかけた分だけ大きく表示する。

が正しい動きだと思う。(多分)

dpiAwareをSystem AwareにしているときのMoveAndResize()の動き

上記ゴールを正だとして、 位置と大きさを指定するために今回使うMoveAndResize()を試したところ、

  • 位置と大きさの引数に、倍率=100%のときの値を渡してあげると、現在の倍率がいくらであっても、倍率=100%のときと、見た目同じ位置と大きさのウインドウになってくれる

っぽい。

それだけ聞くと、わかりやすくていいじゃないかと思ってしまうのだが、問題は、

  • ウインドウの中身の部品(ボタンや文字など)は、倍率が変わると、その倍率に合わせて大きさが変わる

という動きをしている点。

なので、倍率を大きくして、MoveAndResize()に、100%のときの値をそのまま渡していると、ウインドウの大きさは今まで通りなのに、中身の大きさは倍率をかけた大きさになるので、文字やボタンがはみ出したりしてしまう。

今回の対処方法

そこで今回は、MoveAndResize()に渡す位置と大きさの引数について、

  • 位置の値は、倍率がいくつであっても、100%のときの値を渡す
  • 大きさの値は、100%の時の値に、倍率をかけた値を渡す

という風に対処した。

例えば先ほど例に出した、

  • 1920x1080のディスプレイ使用時に、
  • 倍率=150%にして、
  • 画面のど真ん中を起点にして、
  • 画面右下1/4にウインドウを出したい

ような場合は、

appWindow.MoveAndResize(1920/2,1080/2, (int)((1920 / 2.0) * 1.5), (int)((1080 / 2.0) * 1.5))

にしてやればうまくいった。つまり、

appWindow.MoveAndResize(100%時の位置xy, 100%時の大きさ*倍率))

で、思ったような動きになってくれた。

※この場合、100%時にこうなので、

150%時はこうなった。

で、上で考えたゴールからして、この動きでOKと思う。

備考

私が思った動きになってくれてはいるが、これが「正しい方法」なのかは自信なし。

高DPI時にウインドウの大きさがついてこない(見た目、100%の時のままの大きさになる)動作は、WinUI3のバグのようにも思える。
(UWPやWPFでは、ウインドウの大きさが倍率についてくる)

もしそれが本当にバグで、将来それがWinUI3側で治ったとしたら、上記で試したことが逆にアダになるかもしれない。

サンプルコード

using Microsoft.UI.Xaml;

namespace dpi_WinUI3
{
    public sealed partial class MainWindow : Window
    {
        public MainWindow()
        {
            this.InitializeComponent();

            // WinUI3のウインドウのハンドルを取得
            var hWnd = WinRT.Interop.WindowNative.GetWindowHandle(this);
            // そのウインドウのIDを取得
            Microsoft.UI.WindowId windowId = Microsoft.UI.Win32Interop.GetWindowIdFromWindow(hWnd);
            // そこからAppWindowを取得する
            Microsoft.UI.Windowing.AppWindow appWindow = Microsoft.UI.Windowing.AppWindow.GetFromWindowId(windowId);
            // 標準タイプのウインドウ(OverlappedPresenter)に対して、好きな設定を行う
            //var op = OverlappedPresenter.Create();
            //op.IsMaximizable = false;
            //op.IsMinimizable = true;
            //op.IsResizable = false;
            //op.IsAlwaysOnTop = false;
            //appWindow.SetPresenter(op);

            appWindow.MoveAndResize(new Windows.Graphics.RectInt32(960,540, (int)((1920 / 2.0) * 1.5), (int)((1080 / 2.0) * 1.5)));
            //appWindow.Resize(new Windows.Graphics.SizeInt32(1920, 1080));
            //appWindow.Move(new Windows.Graphics.PointInt32(0, 0));
        }
    }
}