【问题标题】:How to enforce MinWidth & MinHeight in a WPF window where WindowStyle="None"?如何在 WindowStyle="None" 的 WPF 窗口中强制执行 MinWidth 和 MinHeight?
【发布时间】:2010-12-15 16:13:24
【问题描述】:

我有一个 WPF 应用程序,其中主窗口的装饰是自定义的,通过 WindowStyle="None"。我画了自己的标题栏和最小/最大/关闭按钮。不幸的是,当窗口调整大小时,Windows 不会强制执行我的 MinWidth 和 MinHeight 属性,因此允许窗口一直调整到 3x3(appx - 足以显示句柄以扩大窗口)。

我已经不得不拦截窗口事件 (sp. 0x0024) 以修复由 WindowStyle=none 引起的最大化错误(它将在 Windows 任务栏上最大化)。我不怕拦截更多的事件来实现我所需要的。

如果可能的话,有谁知道如何让我的窗口不调整到我的 MinWidth 和 MinHeight 属性以下?谢谢!!

【问题讨论】:

    标签: wpf xaml window height width


    【解决方案1】:

    我能够通过将handledWindowProc() 的最后一个参数)设置为false 在 0x0024 的情况下解决这个问题(OP 提到他已经挂钩以修复最大化),然后设置MinHeightMinWidth 在您的 Window XAML 中。这让这个窗口消息的处理落入了默认的 WPF 机制。

    这样,Window 上的 Min* 属性管理最小大小,自定义 GetMinMaxInfo 代码管理最大大小。

    【讨论】:

    • 我投了赞成票,因为它也适用于 Windows 8 64 位,而上述解决方案在尝试调整到最小尺寸以下时会产生 COM 错误。
    【解决方案2】:

    你确实需要处理一个windows消息来做到这一点,但这并不复杂。

    您必须处理 WM_WINDOWPOSCHANGING 消息,在 WPF 中执行此操作需要一些样板代码,您可以在下面看到实际逻辑只是两行代码。

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

    【讨论】:

    • 谢谢!这正是我所需要的。我已经连接了源,因为我正在监视 0x0024(最大化事件),所以我只需要在我的开关中添加一个案例。再次感谢!
    • 当尝试超过 MinHeight 或 MinWidth 时,有什么方法可以阻止“黑色”屏幕闪烁?闪烁不好。
    • handeled = true 在某些系统上导致我崩溃 (UCEERR_RENDERTHREADFAILURE)。注释掉这一行可以消除崩溃,其余部分仍然正常运行。
    【解决方案3】:

    以下代码适用于任何 DPI 设置。

    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;
    

    【讨论】:

      【解决方案4】:

      解决方案有效,但存在错误。当我尝试通过拖动顶部或左侧边框来调整窗口大小时,窗口正在移动。它发生在 changePos 为真时。 这是没有此错误的代码:

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

      【讨论】:

        【解决方案5】:

        目前我无法验证这一点,因为我使用的是 Mac 笔记本电脑,但我相信我之前已经通过处理 SizeChanged 事件然后检测是否违反了 MinWidth/Height 以及如果违反了这一点,只需将 Width/Height 属性设置回最小值。

        【讨论】:

        • 感谢您的建议。它确实有效,但提供了一种奇怪的体验——它们仍然可以一直向下拖动,但窗口会“对抗”它们。下面的另一个答案会产生正确的用户体验。
        • 同意,只要您不怕用低级别的 Windows 消息“弄脏”您的手,上述解决方案就更好了。 :)
        猜你喜欢
        • 2011-07-10
        • 2019-06-12
        • 2011-10-03
        • 2014-01-23
        • 1970-01-01
        • 2011-02-25
        • 1970-01-01
        • 2013-04-13
        • 2011-04-28
        相关资源
        最近更新 更多