Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Window Maximization Behavior Consistency Issues #9427

Open
tingjiangcao opened this issue Mar 12, 2024 · 4 comments
Open

Window Maximization Behavior Consistency Issues #9427

tingjiangcao opened this issue Mar 12, 2024 · 4 comments
Labels
area-AppWindow area-Windowing bug Something isn't working team-CompInput Issue for IXP (Composition, Input) team

Comments

@tingjiangcao
Copy link

tingjiangcao commented Mar 12, 2024

Describe the bug

Objective:
Lock the window size and prohibit the user from modifying the window size and maximizing it.

This can be achieved with the following code.
var presenter = (OverlappedPresenter)AppWindow.Presenter;
presenter.IsResizable = false;
presenter.IsMaximizable = false;

But double clicking on the title bar still maximizes it. While I suspect that customizing the title bar would solve this issue, I think that when the developer sets (presenter.IsMaximizable = false;), the ability to maximize the title bar on double click should be disabled.

I don't know if there is a way to disable "double-click titlebar maximization" other than "custom titlebar".

Steps to reproduce the bug

        var presenter = (OverlappedPresenter)AppWindow.Presenter;
        presenter.IsResizable = false;
        presenter.IsMaximizable = false;

Expected behavior

No response

Screenshots

No response

NuGet package version

WinUI 3 - Windows App SDK 1.5.0: 1.5.240227000

Windows version

Windows 11 (22H2): Build 22621

Additional context

No response

@tingjiangcao tingjiangcao added the bug Something isn't working label Mar 12, 2024
@microsoft-github-policy-service microsoft-github-policy-service bot added the needs-triage Issue needs to be triaged by the area owners label Mar 12, 2024
@codendone codendone changed the title Window Maximization Behavior Consistency IssuesBug title Window Maximization Behavior Consistency Issues Mar 13, 2024
@codendone codendone added area-AppWindow team-CompInput Issue for IXP (Composition, Input) team area-Windowing and removed needs-triage Issue needs to be triaged by the area owners labels Mar 13, 2024
@ianier
Copy link

ianier commented Nov 16, 2024

Is anyone working on fixing this issue or at least provide a workaround?
The simplest workaround I could come up with is to add the code below to my Window constructor. It's not perfect (the window still blinks), but at least it doesn't involve a bunch of native code like other workarounds I've seen elsewhere:

            AppWindow.Changed += (s, e) => 
                {
                    // this.SizeChanged won't work because it's not called on every maximize
                    var presenter = (OverlappedPresenter)AppWindow.Presenter;
                    if (presenter.State == OverlappedPresenterState.Maximized)
                        presenter.Restore();
                };

@castorix
Copy link

An usual workaround in Win32 is to intercept WM_SYSCOMMAND and SC_MAXIMIZE
(I tested in Windows App SDK 1.6.240829007, with SetWindowSubclass to intercept SC_MAXIMIZE (return 0))

@ianier
Copy link

ianier commented Nov 16, 2024

An usual workaround in Win32 is to intercept WM_SYSCOMMAND and SC_MAXIMIZE (I tested in Windows App SDK 1.6.240829007, with SetWindowSubclass to intercept SC_MAXIMIZE (return 0))

Thanks, I had seen that elsewhere but preferred to stay away from hooking into WndProc and so on. This bug affects such a basic functionality that I simply don't understand why it hasn't been given the highest priority. This is the kind of issue that fuels the "WinUI is dead" kind of comment.

@YuiSayou
Copy link

YuiSayou commented Nov 27, 2024

This is the workaround that work for me.

Edit 1

Already cleared out the System.ExecutionEngineException
at this line
return CallWindowProc(originalWndProc, wndHwnd, msg, wParam, lParam);
Happened when I aggressively drag the titlebar across the screen lol.

Took me almost 2 days to figured this out. Now double clicking the title bar no longer maximize the window. Idk why the basic things like this haven't implemented yet in WinUI3.

Edit 2
Implemented these:

  1. Disable double click maximize
  2. Disable window maximize shortcut Win + Up.
  3. Disable Alt + Space > X or right click > maximize

For disabling Aero Snap (Drag the window to the top) you just need to set
IsResizable = false;

Just Follow the step

  1. Create a class name NativeWindowHelper.cs then copy paste this.
using System;
using System.Runtime.InteropServices;
using Microsoft.UI.Xaml;
using WinRT.Interop;

namespace YourNamespace  // Dont forget to rename this to your namespace
{
    public static class NativeWindowHelper
    {
        private const int WM_NCLBUTTONDBLCLK = 0x00A3; // Non-client left button double-click
        private const int WM_SYSCOMMAND = 0x0112; // System command message
        private const int SC_MAXIMIZE = 0xF030; // Maximize command
        private const int WM_SIZE = 0x0005; // Resize message
        private const int SIZE_MAXIMIZED = 2; // Maximized size
        private const int GWLP_WNDPROC = -4;

        // Static field to hold the delegate, preventing it from being garbage-collected
        private static WndProcDelegate _currentWndProcDelegate;

        // Delegate for the new window procedure
        private delegate IntPtr WndProcDelegate(IntPtr hwnd, uint msg, IntPtr wParam, IntPtr lParam);

        public static void ForceDisableMaximize(Window window)
        {
            var hwnd = WindowNative.GetWindowHandle(window);

            if (hwnd == IntPtr.Zero)
            {
                System.Diagnostics.Debug.WriteLine("Invalid window handle. Cannot hook window procedure.");
                return;
            }

            // Store the original WndProc and assign the new one
            IntPtr originalWndProc = GetWindowLongPtr(hwnd, GWLP_WNDPROC);
            if (originalWndProc == IntPtr.Zero)
            {
                System.Diagnostics.Debug.WriteLine("Failed to retrieve the original WndProc.");
                return;
            }

            _currentWndProcDelegate = (wndHwnd, msg, wParam, lParam) =>
            {
                // Suppress double-click maximize
                if (msg == WM_NCLBUTTONDBLCLK)
                {
                    System.Diagnostics.Debug.WriteLine("Double-click maximize suppressed.");
                    return IntPtr.Zero;
                }

                // Suppress system maximize command (e.g., via keyboard shortcuts or title bar menu)
                if (msg == WM_SYSCOMMAND && wParam.ToInt32() == SC_MAXIMIZE)
                {
                    System.Diagnostics.Debug.WriteLine("Maximize via system command suppressed.");
                    return IntPtr.Zero;
                }


                try
                {
                    // Ensure parameters are valid before calling originalWndProc
                    if (wndHwnd != IntPtr.Zero && originalWndProc != IntPtr.Zero)
                    {
                        return CallWindowProc(originalWndProc, wndHwnd, msg, wParam, lParam);
                    }
                    else
                    {
                        System.Diagnostics.Debug.WriteLine("Invalid parameters in WndProc call.");
                        return IntPtr.Zero;
                    }
                }
                catch (Exception ex)
                {
                    // Handle exceptions to avoid crashing
                    System.Diagnostics.Debug.WriteLine($"Error in WndProc: {ex.Message}");
                    return IntPtr.Zero;
                }
            };

            try
            {
                // Hook the new WndProc
                IntPtr result = SetWindowLongPtr(hwnd, GWLP_WNDPROC, Marshal.GetFunctionPointerForDelegate(_currentWndProcDelegate));
                if (result == IntPtr.Zero)
                {
                    System.Diagnostics.Debug.WriteLine($"Failed to set new WndProc. Error: {Marshal.GetLastWin32Error()}");
                }
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine($"Error hooking window procedure: {ex.Message}");
                return;
            }

            // Prevent garbage collection of the delegate (redundant but safe)
            GC.KeepAlive(_currentWndProcDelegate);
        }

        // Win32 API declarations
        [DllImport("user32.dll", SetLastError = true)]
        private static extern IntPtr CallWindowProc(IntPtr lpPrevWndFunc, IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);

        [DllImport("user32.dll", EntryPoint = "GetWindowLongPtr", SetLastError = true)]
        private static extern IntPtr GetWindowLongPtr64(IntPtr hWnd, int nIndex);

        [DllImport("user32.dll", EntryPoint = "GetWindowLong", SetLastError = true)]
        private static extern IntPtr GetWindowLong32(IntPtr hWnd, int nIndex);

        [DllImport("user32.dll", EntryPoint = "SetWindowLongPtr", SetLastError = true)]
        private static extern IntPtr SetWindowLongPtr64(IntPtr hWnd, int nIndex, IntPtr dwNewLong);

        [DllImport("user32.dll", EntryPoint = "SetWindowLong", SetLastError = true)]
        private static extern IntPtr SetWindowLong32(IntPtr hWnd, int nIndex, IntPtr dwNewLong);

        private static IntPtr GetWindowLongPtr(IntPtr hWnd, int nIndex)
        {
            return IntPtr.Size == 8 ? GetWindowLongPtr64(hWnd, nIndex) : GetWindowLong32(hWnd, nIndex);
        }

        private static IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr dwNewLong)
        {
            return IntPtr.Size == 8 ? SetWindowLongPtr64(hWnd, nIndex, dwNewLong) : SetWindowLong32(hWnd, nIndex, dwNewLong);
        }
    }
}


  1. In your MainWindow or any other window you don't want it to maximize.

You can call the

NativeWindowHelper.ForceDisableMaximize(this);

Example

using Microsoft.UI.Xaml;
using WinUIEx;


namespace YourNamespace
{
    public sealed partial class MainWindow : WinUIEx.WindowEx
    {
        public MainWindow()
        {
            this.InitializeComponent();
            this.CenterOnScreen();
            this.Height = 800;
            this.Width = 800;
            this.IsResizable = false; //set this to false or Aero Snap will maximize your window
            this.IsTitleBarVisible = false;

            ExtendsContentIntoTitleBar = true;
            SetTitleBar(YourCustomTitleBar); // Use Custom TitleBar

            // Disable maximize behavior
            NativeWindowHelper.ForceDisableMaximize(this);
        }
    }
}

Tested with x86 and x64 and Window App SDK 1.6.240923002

Tips

Notice how I can easily set the window size with just this.Height = 800; ?
I use WinUIEx extension. It is really helpful when it comes to customizing the window.

Pardon for my bad English. it is not my native but I will try my best to spoon-fed everyone with the little effort and knowledge that I have.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-AppWindow area-Windowing bug Something isn't working team-CompInput Issue for IXP (Composition, Input) team
Projects
None yet
Development

No branches or pull requests

5 participants