WinUI3で最小ウインドウサイズを指定する

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

やりたいこと

WPFとかでよくやっていた、ウインドウサイズをこれ以上小さくできないようにしたい、ということをWinUI3でもやりたい。

が、ぱっと調べただけでは、WinUI3の標準のやり方でそういったことはできないっぽい。
でも何とかしたい。

前提

  • VisualStudio2022 Community 17.3.0
  • WinUI3 と パッケージプロジェクト
  • Windows App SDK 1.1.3
  • PInvoke.User32 v0.7.124のnugetパッケージを使用

やりかた

MSのgithubに、同じことで困っている人のissueが上がっていた。

github.com

これを、まるっとまねさせてもらう形で、ちょっとだけ改造して実現する。

サンプルコード

ざっくり処理の流れとしては

  • WinUI3ウインドウのウインドウプロシージャで、
  • WM_GETMINMAXINFOを処理し、その中で最小のウインドウサイズを指定する

となる。

using Microsoft.UI.Xaml;
using System;
using System.Runtime.InteropServices;

// nuget PInvoke.User32 0.7.124

namespace MinWidthJikken
{
    public sealed partial class MainWindow : Window
    {
        // 最小の大きさ
        int MinWidth = 400;
        int MinHeight = 300;

        public MainWindow()
        {
            this.InitializeComponent();
            SubClassing();
        }

        private NativeMethods.WinProc newWndProc = null;
        private IntPtr oldWndProc = IntPtr.Zero;

        private void SubClassing()
        {
            // ウインドウのハンドルを取ってくる
            var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(this);
            newWndProc = new NativeMethods.WinProc(NewWindowProc);
            oldWndProc = NativeMethods.SetWindowLong(hwnd, PInvoke.User32.WindowLongIndexFlags.GWL_WNDPROC, newWndProc);
        }

        private IntPtr NewWindowProc(IntPtr hWnd, PInvoke.User32.WindowMessage Msg, IntPtr wParam, IntPtr lParam)
        {
            switch (Msg)
            {
                case PInvoke.User32.WindowMessage.WM_GETMINMAXINFO:
                    // ウインドウのサイズが変わるたびにここを通る
                    var dpi = PInvoke.User32.GetDpiForWindow(hWnd);
                    float scalingFactor = (float)dpi / 96;

                    // ここで、最上の大きさをMINMAXINFOに入れて指定する
                    NativeMethods.MINMAXINFO minMaxInfo = Marshal.PtrToStructure<NativeMethods.MINMAXINFO>(lParam);
                    minMaxInfo.ptMinTrackSize.x = (int)(MinWidth * scalingFactor);
                    minMaxInfo.ptMinTrackSize.y = (int)(MinHeight * scalingFactor);
                    Marshal.StructureToPtr(minMaxInfo, lParam, true);
                    break;

            }
            // WM_GETMINMAXINFO以外の、自分で処理したくないMsgは、もとのWndProcに任せる
            return NativeMethods.CallWindowProc(oldWndProc, hWnd, Msg, wParam, lParam);
        }
    }

    public class NativeMethods
    {
        [StructLayout(LayoutKind.Sequential)]
        public struct MINMAXINFO
        {
            public PInvoke.POINT ptReserved;
            public PInvoke.POINT ptMaxSize;
            public PInvoke.POINT ptMaxPosition;
            public PInvoke.POINT ptMinTrackSize;
            public PInvoke.POINT ptMaxTrackSize;
        }

        public delegate IntPtr WinProc(IntPtr hWnd, PInvoke.User32.WindowMessage Msg, IntPtr wParam, IntPtr lParam);

        [DllImport("user32")]
        public static extern IntPtr SetWindowLong(IntPtr hWnd, PInvoke.User32.WindowLongIndexFlags nIndex, WinProc newProc);
        [DllImport("user32.dll")]
        public static extern IntPtr CallWindowProc(IntPtr lpPrevWndFunc, IntPtr hWnd, PInvoke.User32.WindowMessage Msg, IntPtr wParam, IntPtr lParam);
    }
}

結果、

(見た目ではわからないが)これ以上小さくできないウインドウになった。

注意

64bitでビルドするときは、SetWindowLongPtr64()を使わないといけない。 64bitでSetWindowLong()をそのまま使ってると、ビルド時にエラーなどおきず、ただ動かない。(フックしたはずのメソッドに来ない)

https://qiita.com/tera1707/items/75609595f4d42e0ac160#%E6%B3%A8%E6%84%8F

参考

MSのgithubのissue
こういうのが標準でできないのは、WinUI3がUWPの血も受け継いでいるからなんでしょうか。

https://github.com/microsoft/microsoft-ui-xaml/issues/2945

サブクラス化とは?のわかりやすい日本語解説
これがあれば、なんかいろんなことができそう。。

http://wisdom.sakura.ne.jp/system/winapi/win32/win64.html

参考書

WinUI3

WinUI3でアプリを作ろうと思ったときのとっかかりによかった。 msdocsに書いてある情報を、体系的に、順番に読みたいな、というときによいかも。(ただし英語)
この本で分からなかった、かゆいところに手が届かなかった部分を私は記事にしてる感じ。

C#①

表紙に書いてある通り、教科書として最適。 これからC#を勉強したいけど、ネットだけで勉強するのは効率が悪いから体系的に学べる本が欲しいときや、 ちょっとC#を勉強してコード書けるようになったけど、もう少し広く深く知りたいなというときによいと思う。
私は仕事で触れるコードを軸に、基本ネットで断片的にC#を学んだので、その知識の隙間を埋めて枝葉を広げるためにとても分かりやすかった。

C#②

C#の文法的に色々できるのは分かったが、いざ実装するときに、わかったことを使ってどう実装すればいいのか?と悩んだときに指針になりそうな本。
「プロパティ等の名前の付け方、どうすればいい?」「情報をクラス外部に見せるときに、プロパティにすべき?メソッドにすべき?」「異常だと判定したいとき、どんなときにどんな例外をスローすべき?」などなど、勉強になる部分が山ほどあった。
私のように「コードは書くけどこれであってるのか自信がない、レビューで指摘されるのが嫌だ、実装時の(心の)よりどころが欲しい」という人に最適。