wpfxaml.net-4.0windows-shellwindow-chrome

WindowChrome ResizeBorderThickness issue


I am styling a Window, but I noticed this strange behaviour of WindowChrome (in .NET FW 4.0, from external Microsoft.Windows.Shell dll).

I set the WindowChrome with AllowTransparency = true and WindowStyle = None.

If I set the WindowChrome's ResizeBorderThickness <= 7 everything works perfectly, but if I do

ResizeBorderThickness="8"

or more, when the Window is Maximized I can't drag it from the last top pixel near the top edge of the screen, and for each +1 exceeding 7 I must start dragging 1 pixel more down from the edge.

This is annoying 'cause it disable a common behaviour when closing a window, forcing me to set it to 7 or less.

Can someone explain me this behaviour?

Thank you!


Solution

  • The window doesn't have a strange behavior. Instead of it, the window has two strange behaviors.

    "[...] when the Window is Maximized I can't drag it from the last top pixel near the top edge of the screen [...]"

    This behavior is due to the edge to resize is still active when the window changes to its maximized state. Indeed, this edge is always active. Setting the ResizeBorderThickness property, WindowChrome reserve that amount of pixels to control the behavior of resizing the window. Given that in maximized mode the resize events aren't allowed, then you will notice that these pixels don't allow any kind of behavior. This is precisely because WindowChrome reserve exclusively those pixels that control the behavior of resizing.

    What is the solution? You need to notify WindowChrome must change to establish the ResizeBorderThickness property to 0 when the window is maximized. This can be done simply by setting WindowChrome again by a Trigger in xaml:

    <Trigger Property="WindowState" Value="Maximized">
         <Setter Property="WindowChrome.WindowChrome">
              <Setter.Value>
                   <WindowChrome ResizeBorderThickness="0" [...] />
              </Setter.Value>
         </Setter>
    </Trigger>
    

    Note: This can also do so in run-time code

    "[...] If I set the WindowChrome's ResizeBorderThickness <= 7 everything works perfectly [...] and for each +1 exceeding 7 I must start dragging 1 pixel more down from the edge. [...]"

    Take care. Actually this behavior isn't due to the value set in ResizeBorderThickness but this is due to set the property WindowStyle=None. When this property is set, the window takes on a strange behavior when maximized:

    1. The upper left edge of the window is not positioned at the point (0,0) of the current screen, but rather erratically becomes negative (in your case, on the Y axis the value seems to be -7).

    2. The size of the window takes the size of the current screen, when the normal behavior should be that the size of the window takes the size of current work area (current screen except task bar, etc...) of the current screen.

    This strange behavior that takes the window makes that 7 pixels reserved for 'WindowChrome' aren't visible in the current screen (with ResizeBorderThickness="7", obviously), and therefore gives you the feeling that the property ResizeBorderThickness="7" works properly, when it isn't. In fact, this justifies the behavior when ResizeBorderThickness takes the value 8 or more.

    What is the solution? It is necessary to force the window when maximizing a size and position on the work area of the current screen. Warning: if you only do it for the primary screen, the maximize event doesn't work properly for multiple screens.

    The code that solves this problem I solved by calling an external API:

    [DllImport("user32")]
    internal static extern bool GetMonitorInfo(IntPtr hMonitor, MONITORINFO lpmi);
    [DllImport("user32")]
    internal static extern IntPtr MonitorFromWindow(IntPtr handle, int flags);
    

    Defining the classes and structs:

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto, Pack = 4)]
    public class MONITORINFO
    {
          public int cbSize = Marshal.SizeOf(typeof(MONITORINFO));
          public RECT rcMonitor = new RECT();
          public RECT rcWork = new RECT();
          public int dwFlags = 0;
    }
    
    [StructLayout(LayoutKind.Sequential)]
    public struct RECT
    {
          public int left;
          public int top;
          public int right;
          public int bottom;
    }
    
    [StructLayout(LayoutKind.Sequential)]
    public struct POINT
    {
          public int x;
          public int y;
          public POINT(int x, int y) { this.x = x; this.y = y; }
    }
    
    [StructLayout(LayoutKind.Sequential)]
    public struct MINMAXINFO
    {
          public POINT ptReserved;
          public POINT ptMaxSize;
          public POINT ptMaxPosition;
          public POINT ptMinTrackSize;
          public POINT ptMaxTrackSize;
    }
    

    And finally defining the functions that add the hook WndProc to the window:

    public static void CompatibilityMaximizedNoneWindow(Window window)
    {
          WindowInteropHelper wiHelper = new WindowInteropHelper(window);
          System.IntPtr handle = wiHelper.Handle;
          HwndSource.FromHwnd(handle).AddHook(
                    new HwndSourceHook(CompatibilityMaximizedNoneWindowProc));
    }
    
    private static System.IntPtr CompatibilityMaximizedNoneWindowProc(
        System.IntPtr hwnd,
        int msg,
        System.IntPtr wParam,
        System.IntPtr lParam,
        ref bool handled)
    {
          switch (msg)
          {
          case 0x0024:    // WM_GETMINMAXINFO
                MINMAXINFO mmi =
                    (MINMAXINFO)Marshal.PtrToStructure(lParam, typeof(MINMAXINFO));
    
                    // Adjust the maximized size and position
                    // to fit the work area of the correct monitor
                    // int MONITOR_DEFAULTTONEAREST = 0x00000002;
                    System.IntPtr monitor = MonitorFromWindow(hwnd, 0x00000002);
    
                    if (monitor != System.IntPtr.Zero)
                    {
    
                          MONITORINFO monitorInfo = new MONITORINFO();
                          GetMonitorInfo(monitor, monitorInfo);
                          RECT rcWorkArea = monitorInfo.rcWork;
                          RECT rcMonitorArea = monitorInfo.rcMonitor;
                          mmi.ptMaxPosition.x =
                                Math.Abs(rcWorkArea.left - rcMonitorArea.left);
                          mmi.ptMaxPosition.y =
                                Math.Abs(rcWorkArea.top - rcMonitorArea.top);
                          mmi.ptMaxSize.x =
                                Math.Abs(rcWorkArea.right - rcWorkArea.left);
                          mmi.ptMaxSize.y =
                                Math.Abs(rcWorkArea.bottom - rcWorkArea.top);
                    }
                    Marshal.StructureToPtr(mmi, lParam, true);
                    handled = true;
                    break;
          }
          return (System.IntPtr)0;
    }
    

    With CompatibilityMaximizedNoneWindow API, you simply call the API in the constructor of the window, something like this:

    public MyWindow
    {
          [...]
          MyNamespace.CompatibilityMaximizedNoneWindow(this);
    }
    

    And the second strange behavior must be resolved. You will notice that the code to work, you must add reference PresentationFramework and the namespace System.Windows.Interop.