tags:

views:

238

answers:

2

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!!

A: 

I can't verify this at the moment because I'm on a Mac laptop, but I believe I've done this before by handling the SizeChanged event and then detecting if the MinWidth/Height is being violated and, if so, just setting the Width/Height property back to the min.

Drew Marsh
Thank you for the suggestion. It indeed works, but provides a weird experience - they can still drag all the way down, but the window "fights" them. The other answer, below, produces the proper UX.
Ben McMillan
Agree, the above solution is much nicer as long as you're not afraid to get your hands "dirty" with low level Windows messages. :)
Drew Marsh
+1  A: 

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;
}
Nir
Thank you! This is exactly what I needed. I already had the source hooked up, because I was monitoring 0x0024 (maximize event), so I just had to add a case to my switch. Thanks again!
Ben McMillan