WinUI 3: How to Set Minimum Window Size (Desktop)

Pavel OsadchukPavel Osadchuk
4 min read

Setting a minimum window size in WinUI 3 desktop applications isn't as straightforward as you might expect. Unlike WPF or Windows Forms, there's no simple MinWidth/MinHeight property on the Window class. However, we can achieve this by intercepting Windows Messages using window subclassing.

💡
Windows applications use a messaging system for events like resizing. "Window subclassing" lets you intercept these messages to modify behavior. In WinUI 3, we use this to override the default minimum size logic handled by the OS.

Setup

First, you'll need to add the CsWin32 NuGet package to your project:

<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.49-beta" />

Then create a file called NativeMethods.txt in your project root and add these required Win32 APIs:

SetWindowSubclass
DefSubclassProc
GetDpiForWindow
MINMAXINFO

After creating NativeMethods.txt, ensure its build action is set to "C# analyzer additional file":

  1. Right-click the file > Properties > Build Action > Select "C# analyzer additional file".

This file tells the CsWin32 source generator which Win32 APIs to generate code for.

The Solution

Here is a helper class that will handle the window size constraints:

public class WindowsSubclass
{
    private const int WM_GETMINMAXINFO = 0x0024;
    private readonly Windows.Win32.UI.Shell.SUBCLASSPROC _subclassProc;
    private readonly Windows.Win32.Foundation.HWND _hwnd;
    private readonly uint _id;

    /// <summary>
    /// Initializes a new instance of the <see cref="WindowMinSizeSubclass"/> class.
    /// </summary>
    /// <param name="hwnd">The handle to the window.</param>
    /// <param name="id">The subclass ID, could be any number.</param>
    /// <param name="minSize">The minimum size of the window.</param>
    /// <exception cref="InvalidOperationException">Thrown when setting the window subclass fails.</exception>
    public WindowsSubclass(IntPtr hwnd, uint id, Size minSize)
    {
        _hwnd = (Windows.Win32.Foundation.HWND)hwnd;
        _id = id;
        MinSize = minSize;
        _subclassProc = new Windows.Win32.UI.Shell.SUBCLASSPROC(WindowSubclassProc);

        var result = Windows.Win32.PInvoke.SetWindowSubclass(_hwnd, _subclassProc, _id, 0);
        if (result.Value == 0)
        {
            throw new InvalidOperationException("Failed to set window subclass");
        }
    }

    public Size MinSize { get; }

    private Windows.Win32.Foundation.LRESULT WindowSubclassProc(Windows.Win32.Foundation.HWND hWnd, uint uMsg,
        Windows.Win32.Foundation.WPARAM wParam, Windows.Win32.Foundation.LPARAM lParam,
        nuint uIdSubclass, nuint dwRefData)
    {
        switch (uMsg)
        {
            case WM_GETMINMAXINFO:
                // Windows sends this message to query size constraints. 
                // ptMinTrackSize defines the smallest draggable window size.
                // We adjust it based on DPI scaling.
                var dpi = Windows.Win32.PInvoke.GetDpiForWindow(hWnd);
                float scalingFactor = (float)dpi / 96;
                var minMaxInfo = Marshal.PtrToStructure<Windows.Win32.UI.WindowsAndMessaging.MINMAXINFO>(lParam);
                minMaxInfo.ptMinTrackSize.X = (int)(MinSize.Width * scalingFactor);
                minMaxInfo.ptMinTrackSize.Y = (int)(MinSize.Height * scalingFactor);
                Marshal.StructureToPtr(minMaxInfo, lParam, true);
                return new Windows.Win32.Foundation.LRESULT(0);            
        }
        return Windows.Win32.PInvoke.DefSubclassProc(hWnd, uMsg, wParam, lParam);
    }
}

How to Use It

To use this in your WinUI 3 window, add this code to your window's constructor:

public MainWindow()
{
    this.InitializeComponent();

    var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(this);
    _ = new WindowsSubclass(hwnd, 69, new Size(500, 300)); // Sets minimum size to 500x300
}

How It Works

The solution works by intercepting the WM_GETMINMAXINFO Windows Message, which Windows sends to a window when it needs to know its size constraints. We use window subclassing to add our own procedure that handles this message.

Key points about the implementation:

  1. CsWin32 generates strongly-typed wrappers for the Win32 APIs we specified in NativeMethods.txt

  2. The class keeps a reference to the subclass procedure delegate to prevent it from being garbage collected

  3. It properly handles DPI scaling to ensure consistent sizing across different display scales

  4. All other window messages are passed through to the default handler

  5. The subclass is automatically cleaned up when the window is destroyed

Important Notes

  • The minimum size is specified in logical pixels and will be properly scaled based on the display's DPI

  • The window subclassing is automatically cleaned up when the window is destroyed

  • This solution works only for desktop applications, not for UWP or other platforms

  • Make sure NativeMethods.txt is included in your project and the file properties are set to "C# analyzer additional file"

Troubleshooting

Some problems you could encounter and how to fix them:

  • Subclass ID Conflicts: Ensure unique IDs if subclassing multiple windows.

  • DPI Scaling Issues: Verify GetDpiForWindow returns valid values (e.g., not zero).

  • Garbage Collection: The SUBCLASSPROC delegate must remain referenced (already handled in the helper class).

Now you can set minimum window sizes in your WinUI 3 desktop applications while maintaining proper Windows integration and DPI awareness.

1
Subscribe to my newsletter

Read articles from Pavel Osadchuk directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Pavel Osadchuk
Pavel Osadchuk

I'm a .NET Developer working on MS Stack for more than 12 years already. I worked with most dotnet-based technologies, from WinForms to Azure Functions. In my time, I won a dozen hackathons, launched a couple of startups, failed them, and am now working as a lead .NET developer in an enterprise company.