single.php

C# WinUI 3アプリでPInvoke.User32を使わずに最小ウィンドウサイズを設定する方法

C# WinUI 3アプリでウィンドウの最小ウィンドウサイズを設定する方法を、調べるとほぼNuGet パッケージの[PInvoke.User32]を利用している場合が多く、今回はNuGetパッケージを使わずに、最小ウィンドウサイズを設定にする方法を備忘録的に投稿しておきます。

PInvoke.User32 は非推奨

Visual Studioで[PInvoke.User32]をインストールしてみると[非推奨]が表示される。

現状は、代替として[Microsoft.Windows.CsWin32]がオススメされている。

DllImport(”user32.dll”)のみでも実現可能

[PInvoke.User]代替してくれている定数や列挙値を自前で置き換えたら、なんとかWin32の呼び出しだけで実現ができました。

ちょっと長いけれど、Win32を呼び出すための具体的なコードは、こんな感じ。

[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
    public int x;
    public int y;
}

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

public delegate IntPtr WinProc(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);

[DllImport("user32.dll", EntryPoint = "SetWindowLongPtr")]
public static extern IntPtr SetWindowLongPtr64(IntPtr hWnd, int nIndex, WinProc newProc);

[DllImport("user32.dll")]
public static extern IntPtr SetWindowLong(IntPtr hWnd, int nIndex, WinProc newProc);

[DllImport("user32.dll")]
public static extern IntPtr CallWindowProc(IntPtr lpPrevWndFunc, IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll")]
private static extern int GetDpiForWindow(nint hwnd);

const int GWL_WNDPROC = -4;
const uint WM_GETMINMAXINFO = 36;

[SetWindowLong]はx86でビルドする際に必要で、x64アーキテクチャーでのみビルドする際には必要ありません。

後は[MainWindow]でサブクラス化した関数を呼び出す形でウィンドウメッセージを補則して[SetWindowLongPtr64]を呼び出してウィンドウの最小サイズを設定しています。

int MinWidth = 640;
int MinHeight = 360;

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

private WinProc? newWinProc = null;
private IntPtr oldWinProc = IntPtr.Zero;

private void SubClassing()
{
    var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(this);
    newWinProc = new WinProc(NewWindowProc);
    oldWinProc = SetWindowLongPtr64(hwnd, GWL_WNDPROC, newWinProc);

    // [x86]でビルドする場合はこっちを使う
    //oldWinProc = SetWindowLong(hwnd, GWL_WNDPROC, newWinProc);

}

private IntPtr NewWindowProc(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam)
{
    switch (Msg)
    {
        case WM_GETMINMAXINFO:
            var dpi = GetDpiForWindow(hWnd);
            float resio = (float)dpi / 96;
            MINMAXINFO minMaxInfo = Marshal.PtrToStructure<MINMAXINFO>(lParam);
            minMaxInfo.ptMinTrackSize.x = (int)(MinWidth * resio);
            minMaxInfo.ptMinTrackSize.y = (int)(MinHeight * resio);
            Marshal.StructureToPtr(minMaxInfo, lParam, true);
            break;
    }
    return CallWindowProc(oldWidProc, hWnd, Msg, wParam, lParam);
}

[PInvoke.User32]で利用していた、定数や列挙値などを自前で定義することでNuGetパッケージを追加せずに最小ウィンドウのサイズ設定が可能になりました。

最後に[MainWindow.xaml.cs]を載せておきます。

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.WindowsRuntime;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Navigation;
using Windows.Foundation;
using Windows.Foundation.Collections;

// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.

namespace MinimumWindowApp
{
    /// <summary>
    /// An empty window that can be used on its own or navigated to within a Frame.
    /// </summary>
    public sealed partial class MainWindow : Window
    {
        [StructLayout(LayoutKind.Sequential)]
        public struct POINT
        {
            public int x;
            public int y;
        }

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

        public delegate IntPtr WinProc(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);

        [DllImport("user32.dll", EntryPoint = "SetWindowLongPtr")]
        public static extern IntPtr SetWindowLongPtr64(IntPtr hWnd, int nIndex, WinProc newProc);

        [DllImport("user32.dll")]
        public static extern IntPtr SetWindowLong(IntPtr hWnd, int nIndex, WinProc newProc);

        [DllImport("user32.dll")]
        public static extern IntPtr CallWindowProc(IntPtr lpPrevWndFunc, IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);

        [DllImport("user32.dll")]
        private static extern int GetDpiForWindow(nint hwnd);

        const int GWL_WNDPROC = -4;
        const uint WM_GETMINMAXINFO = 36;

        int MinWidth = 640;
        int MinHeight = 360;

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

        private void myButton_Click(object sender, RoutedEventArgs e)
        {
            myButton.Content = "Clicked";
        }

        private WinProc? newWidProc = null;
        private IntPtr oldWidProc = IntPtr.Zero;

        private void SubClassing()
        {
            var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(this);
            newWidProc = new WinProc(NewWindowProc);
            oldWidProc = SetWindowLongPtr64(hwnd, GWL_WNDPROC, newWidProc);
//            oldWidProc = SetWindowLong(hwnd, GWL_WNDPROC, newWidProc);

        }

        private IntPtr NewWindowProc(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam)
        {
            switch (Msg)
            {
                case WM_GETMINMAXINFO:
                    var dpi = GetDpiForWindow(hWnd);
                    float scalingFactor = (float)dpi / 96;
                    MINMAXINFO minMaxInfo = Marshal.PtrToStructure<MINMAXINFO>(lParam);
                    minMaxInfo.ptMinTrackSize.x = (int)(MinWidth * scalingFactor);
                    minMaxInfo.ptMinTrackSize.y = (int)(MinHeight * scalingFactor);
                    Marshal.StructureToPtr(minMaxInfo, lParam, true);
                    break;
            }
            return CallWindowProc(oldWidProc, hWnd, Msg, wParam, lParam);
        }
    }
}

まとめ

今回は短い記事ですが、WinUI3プロジェクトでNuGetパッケージの[PInvoke.User]を使わずに、Xamlで作成されるウィンドウの最小サイズを設定する方法を書きました。

x64とx86のアーキテクチャーで[SetWindowLongPtr64]と[SetWindowLong]を使い分ける必要がありますが、NuGetパッケージを利用せずにウィンドウ最小サイズの設定が可能になります。

C#のWinUI 3アプリのウィンドウの最小サイズを設定したい人の参考になれば幸いです。

スポンサーリンク

最後までご覧いただき、ありがとうございます。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です