我正在设计一个 Window 样式,但我注意到 WindowChrome 的这种奇怪行为(在 .NET FW 4.0 中,来自外部 Microsoft.Windows.Shell dll)。
我将 WindowChrome 设置为 AllowTransparency = true 且 WindowStyle = None。
如果我设置WindowChrome的ResizeBorderThickness <= 7 everything works perfectly, but if I do
ResizeBorderThickness="8"
或者更多,当窗口最大化时,我无法将其从靠近屏幕顶部边缘的最后一个顶部像素拖动,并且对于每个超过 7 的 +1,我必须开始从边缘向下拖动 1 个像素。
这很烦人,因为它会禁用关闭窗口时的常见行为,迫使我将其设置为 7 或更少。
有人可以向我解释一下这种行为吗?
谢谢!
窗口没有奇怪的行为。相反,窗口有两个奇怪的行为。
“[...]当窗口最大化时,我无法将其从靠近屏幕顶部边缘的最后一个顶部像素拖动[...]”
此行为是由于当窗口更改为最大化状态时,要调整大小的边缘仍然处于活动状态。事实上,该边缘始终处于活动状态。 设置 ResizeBorderThickness 属性,WindowChrome 保留该数量的像素来控制调整窗口大小的行为。鉴于在最大化模式下不允许调整大小事件,那么您会注意到这些像素不允许任何类型的行为。这正是因为 WindowChrome 专门保留那些控制调整大小行为的像素。
解决方案是什么?您需要通知WindowChrome必须更改以在窗口最大化时将ResizeBorderThickness属性设置为0。只需通过 xaml 中的 Trigger 再次设置 WindowChrome 即可完成此操作:
<Trigger Property="WindowState" Value="Maximized">
<Setter Property="WindowChrome.WindowChrome">
<Setter.Value>
<WindowChrome ResizeBorderThickness="0" [...] />
</Setter.Value>
</Setter>
</Trigger>
注意:这也可以在运行时代码中这样做
“[...] 如果我设置 WindowChrome 的 ResizeBorderThickness <= 7 everything works perfectly [...] and for each +1 exceeding 7 I must start dragging 1 pixel more down from the edge. [...]"
保重。实际上,这种行为不是由于 ResizeBorderThickness 中设置的值引起的,而是由于设置属性 WindowStyle=None 引起的。设置此属性后,窗口在最大化时会呈现奇怪的行为:
窗口的左上边缘不位于当前屏幕的点(0,0),而是不稳定地变为负值(在您的情况下,在Y轴上该值似乎是-7)。
窗口的大小采用当前屏幕的大小,正常行为应该是窗口的大小采用当前工作区域(当前屏幕,任务栏等除外)的大小当前屏幕。
这种占用窗口的奇怪行为使得为“WindowChrome”保留的 7 个像素在当前屏幕中不可见(显然,使用 ResizeBorderThickness="7"),因此给您一种属性 ResizeBorderThickness= “7” 可以正常工作,但不能正常工作时。事实上,这证明了当 ResizeBorderThickness 取值 8 或更大时的行为是合理的。
解决办法是什么?在当前屏幕工作区最大化尺寸和位置时需要强制窗口。 警告:如果您只在主屏幕上执行此操作,则最大化事件在多个屏幕上无法正常工作。
解决这个问题的代码我是通过调用外部API解决的:
[DllImport("user32")]
internal static extern bool GetMonitorInfo(IntPtr hMonitor, MONITORINFO lpmi);
[DllImport("user32")]
internal static extern IntPtr MonitorFromWindow(IntPtr handle, int flags);
定义类和结构:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto, Pack = 4)]
public class MONITORINFO
{
public int cbSize = Marshal.SizeOf(typeof(MONITORINFO));
public RECT rcMonitor = new RECT();
public RECT rcWork = new RECT();
public int dwFlags = 0;
}
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int left;
public int top;
public int right;
public int bottom;
}
[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
public int x;
public int y;
public POINT(int x, int y) { this.x = x; this.y = y; }
}
[StructLayout(LayoutKind.Sequential)]
public struct MINMAXINFO
{
public POINT ptReserved;
public POINT ptMaxSize;
public POINT ptMaxPosition;
public POINT ptMinTrackSize;
public POINT ptMaxTrackSize;
}
最后定义将钩子 WndProc 添加到窗口的函数:
public static void CompatibilityMaximizedNoneWindow(Window window)
{
WindowInteropHelper wiHelper = new WindowInteropHelper(window);
System.IntPtr handle = wiHelper.Handle;
HwndSource.FromHwnd(handle).AddHook(
new HwndSourceHook(CompatibilityMaximizedNoneWindowProc));
}
private static System.IntPtr CompatibilityMaximizedNoneWindowProc(
System.IntPtr hwnd,
int msg,
System.IntPtr wParam,
System.IntPtr lParam,
ref bool handled)
{
switch (msg)
{
case 0x0024: // WM_GETMINMAXINFO
MINMAXINFO mmi =
(MINMAXINFO)Marshal.PtrToStructure(lParam, typeof(MINMAXINFO));
// Adjust the maximized size and position
// to fit the work area of the correct monitor
// int MONITOR_DEFAULTTONEAREST = 0x00000002;
System.IntPtr monitor = MonitorFromWindow(hwnd, 0x00000002);
if (monitor != System.IntPtr.Zero)
{
MONITORINFO monitorInfo = new MONITORINFO();
GetMonitorInfo(monitor, monitorInfo);
RECT rcWorkArea = monitorInfo.rcWork;
RECT rcMonitorArea = monitorInfo.rcMonitor;
mmi.ptMaxPosition.x =
Math.Abs(rcWorkArea.left - rcMonitorArea.left);
mmi.ptMaxPosition.y =
Math.Abs(rcWorkArea.top - rcMonitorArea.top);
mmi.ptMaxSize.x =
Math.Abs(rcWorkArea.right - rcWorkArea.left);
mmi.ptMaxSize.y =
Math.Abs(rcWorkArea.bottom - rcWorkArea.top);
}
Marshal.StructureToPtr(mmi, lParam, true);
handled = true;
break;
}
return (System.IntPtr)0;
}
使用 CompatibilityMaximizedNoneWindow API,您只需在窗口的构造函数中调用该 API,如下所示:
public MyWindow
{
[...]
MyNamespace.CompatibilityMaximizedNoneWindow(this);
}
第二个奇怪的行为必须得到解决。您会注意到,要使代码正常工作,您必须添加引用 PresentationFramework 和命名空间 System.Windows.Interop。
如果您有全屏应用程序(
WindowStyle
设置为None并且AllowTransparency
设置为true
),您需要对Noir的优秀答案进行一些调整:
不要使用工作区域来确定最大边界,而是使用
rcMonitor
:
mmi.ptMaxPosition.x = 0;
mmi.ptMaxPosition.y = 0;
mmi.ptMaxSize.x = Math.Abs(rcMonitorArea.left - rcMonitorArea.right);
mmi.ptMaxSize.y = Math.Abs(rcMonitorArea.bottom - rcMonitorArea.top);
要使全屏工作,窗口需要在最大化模式下完全删除
WindowChrome
:
// Run this whenever the window state changes (maximize, restore, ...)
WindowChrome chrome ;
if (WindowState == WindowState.Maximized)
chrome = null;
else
chrome = new WindowChrome() { ... }
WindowChrome.SetWindowChrome(this, chrome);
通过将逻辑包装在可以保持状态的类中,我们甚至可以使窗口随意进入和退出全屏模式:
if (IsFullScreen)
{
// Tell Windows that we want to occupy the entire monitor
mmi.ptMaxPosition.x = 0;
mmi.ptMaxPosition.y = 0;
mmi.ptMaxSize.x = Math.Abs(rcMonitorArea.left - rcMonitorArea.right);
mmi.ptMaxSize.y = Math.Abs(rcMonitorArea.bottom - rcMonitorArea.top);
}
else
{
// Tell Windows that we want to occupy the entire work area of the
// current monitor (leaves the task bar visible)
mmi.ptMaxPosition.x = Math.Abs(rcWorkArea.left - rcMonitorArea.left);
mmi.ptMaxPosition.y = Math.Abs(rcWorkArea.top - rcMonitorArea.top);
mmi.ptMaxSize.x = Math.Abs(rcWorkArea.right - rcWorkArea.left);
mmi.ptMaxSize.y = Math.Abs(rcWorkArea.bottom - rcWorkArea.top);
}
使用它的 WPF 窗口的完整示例可作为 github gist 获得。