开发者

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 wind开发者_如何学JAVAows 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!!


I was able to solve this problem by setting handled (the last parameter for WindowProc()) to false in the case for 0x0024 (which the OP mentioned he was already hooking to fix maximization), and then setting MinHeight and MinWidth in your Window XAML. This lets the handling of this window message fall through to default WPF mechanisms.

This way, the Min* attributes on your Window manage the minimum size and the custom GetMinMaxInfo code manages the maximum size.


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;
}


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;


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;
}


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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜