如何在 WindowStyle="None" 的 WPF 窗口中强制执行 MinWidth 和 MinHeight?

Posted

技术标签:

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

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

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

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

【问题讨论】:

【参考方案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 消息“弄脏”您的手,上述解决方案就更好了。 :)

以上是关于如何在 WindowStyle="None" 的 WPF 窗口中强制执行 MinWidth 和 MinHeight?的主要内容,如果未能解决你的问题,请参考以下文章

如何克服“A non-serializable value detection”

wpf 做一个好看的标题栏?

WPF设置窗口模式(Windowstyle=“None”)

无法拖动和移动 WPF 表单

["non_field_errors": ["Expected a list of items."]] 使用 MongoDB 在 Django Rest 中保存

WPF WindowStyle为None