Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to enforce MinWidth & MinHeight in a WPF window where WindowStyle="None"?

I have a WPF application in which the main window's decoration is custom, via WindowStyle="None". I draw my own titlebar and min/max/close buttons. Unfortunately, Windows doesn't enforce my MinWidth and MinHeight properties when the window is resized, thus allowing the window to be resized all the way down to 3x3 (appx - just enough to show the handles to grow the window).

I'm already having to intercept window events (sp. 0x0024) to fix the maximization bug (where it will maximize over the windows taskbar) caused by WindowStyle=none. I'm not afraid to intercept more events to achieve what I need.

Does anyone know how to get my window to not resize below my MinWidth and MinHeight properties, if it is even possible? Thanks!!

like image 604
Ben McMillan Avatar asked Nov 11 '09 22:11

Ben McMillan


3 Answers

Below code will work for any DPI settings.

case 0x0046: //Window position message to be handled to restrict the min and max height of the window on 120% screen
                    {
                        WINDOWPOS pos = (WINDOWPOS)Marshal.PtrToStructure(lParam, typeof(WINDOWPOS));
                        if ((pos.flags & (int)SWP.NOMOVE) != 0)
                        {
                            return IntPtr.Zero;
                        }

                        System.Windows.Window wnd = (System.Windows.Window)HwndSource.FromHwnd(hwnd).RootVisual;
                        if (wnd == null)
                        {
                            return IntPtr.Zero;
                        }

                        bool changedPos = false;

                        //Convert the original to original size based on DPI setting. Need for 120% screen
                        PresentationSource MainWindowPresentationSource = PresentationSource.FromVisual(wnd);
                        Matrix m = MainWindowPresentationSource.CompositionTarget.TransformToDevice;
                        if (pos.cx < (wnd.MinWidth * m.M11)) { pos.cx = (int)(wnd.MinWidth * m.M11); changedPos = true; }
                        if (pos.cy < (wnd.MinHeight * m.M22)) { pos.cy = (int)(wnd.MinHeight * m.M22); changedPos = true; }

                        if (!changedPos)
                        {
                            return IntPtr.Zero;
                        }

                        Marshal.StructureToPtr(pos, lParam, true);
                        handled = true;
                    }
                    break;
like image 113
Ayyappan Subramanian Avatar answered Sep 23 '22 10:09

Ayyappan Subramanian


You do need to handle a windows message to do it, but it's not complicated.

You have to handle the WM_WINDOWPOSCHANGING message, doing that in WPF requires a bit of boilerplate code, you can see below the actual logic is just two lines of code.

internal enum WM
{
   WINDOWPOSCHANGING = 0x0046,
}

[StructLayout(LayoutKind.Sequential)]
internal struct WINDOWPOS
{
   public IntPtr hwnd;
   public IntPtr hwndInsertAfter;
   public int x;
   public int y;
   public int cx;
   public int cy;
   public int flags;
}

private void Window_SourceInitialized(object sender, EventArgs ea)
{
   HwndSource hwndSource = (HwndSource)HwndSource.FromVisual((Window)sender);
   hwndSource.AddHook(DragHook);
}

private static IntPtr DragHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handeled)
{
   switch ((WM)msg)
   {
      case WM.WINDOWPOSCHANGING:
      {
          WINDOWPOS pos = (WINDOWPOS)Marshal.PtrToStructure(lParam, typeof(WINDOWPOS));
          if ((pos.flags & (int)SWP.NOMOVE) != 0)
          {
              return IntPtr.Zero;
          }

          Window wnd = (Window)HwndSource.FromHwnd(hwnd).RootVisual;
          if (wnd == null)
          {
             return IntPtr.Zero;
          }

          bool changedPos = false;

          // ***********************
          // Here you check the values inside the pos structure
          // if you want to override them just change the pos
          // structure and set changedPos to true
          // ***********************

          // this is a simplified version that doesn't work in high-dpi settings
          // pos.cx and pos.cy are in "device pixels" and MinWidth and MinHeight 
          // are in "WPF pixels" (WPF pixels are always 1/96 of an inch - if your
          // system is configured correctly).
          if(pos.cx < MinWidth) { pos.cx = MinWidth; changedPos = true; }
          if(pos.cy < MinHeight) { pos.cy = MinHeight; changedPos = true; }


          // ***********************
          // end of "logic"
          // ***********************

          if (!changedPos)
          {
             return IntPtr.Zero;
          }

          Marshal.StructureToPtr(pos, lParam, true);
          handeled = true;
       }
       break;
   }

   return IntPtr.Zero;
}
like image 21
Nir Avatar answered Sep 25 '22 10:09

Nir


Solution is working but have a bug. Window is moving when i try to resize window by dragging top or left border. It happened when changePos is true. Here is code without this bug:

private static WindowPos _prevPos = new WindowPos();
/// <summary>
/// You do need to handle a windows message to do it, but it's not complicated. 
/// You have to handle the WM_WINDOWPOSCHANGING message, doing that in WPF requires
///  a bit of boilerplate code, you can see below the actual logic is just two lines of code.
/// </summary>
/// <param name="hwnd"></param>
/// <param name="lParam"></param>
private static bool OnWmWindowPosChanging(IntPtr hwnd, IntPtr lParam)
{
    // ReSharper disable once InconsistentNaming
    const int SwpNoMove = 0x0002;
    WindowPos pos = (WindowPos) Marshal.PtrToStructure(lParam, typeof (WindowPos));
    if ((pos.flags & SwpNoMove) != 0) return false;

    Window wnd = (Window) HwndSource.FromHwnd(hwnd)?.RootVisual;
    if (wnd == null) return false;

    bool changePos = false;

    if (pos.cx < wnd.MinWidth)
    {
        pos.cx = (int)wnd.MinWidth;
        // here is we keep pos x
        if (_prevPos.hwnd != IntPtr.Zero)
            pos.x = _prevPos.x;
        changePos = true;
    }

    if (pos.cy < wnd.MinHeight)
    {
        pos.cy = (int)wnd.MinHeight;
        // here is we keep pos y
        if (_prevPos.hwnd != IntPtr.Zero)
            pos.y = _prevPos.y;
        changePos = true;
    }

    // Debug.WriteLine($"x = {pos.x}, y = {pos.y}; w = {pos.cx}, h = {pos.cy}");

    if (!changePos) return false;
    // Keep prev pos for bounded window
    _prevPos = pos;
    Marshal.StructureToPtr(pos, lParam, true);

    return true;
}
like image 40
Andrey A .Eroshenko Avatar answered Sep 23 '22 10:09

Andrey A .Eroshenko