如何在 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】:我能够通过将handled
(WindowProc()
的最后一个参数)设置为false
在 0x0024 的情况下解决这个问题(OP 提到他已经挂钩以修复最大化),然后设置MinHeight
和 MinWidth
在您的 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”
["non_field_errors": ["Expected a list of items."]] 使用 MongoDB 在 Django Rest 中保存