窗口铬调整边框厚度问题

4
我正在为一个窗口设置样式,但我注意到了WindowChrome(来自external Microsoft.Windows.Shell dll的.NET FW 4.0)的奇怪行为。
我将WindowChrome设置为AllowTransparency = true和WindowStyle = None。
如果我将WindowChrome的ResizeBorderThickness <= 7,则一切正常,但如果我这样做:
ResizeBorderThickness="8"
或更多,当窗口最大化时,我无法从靠近屏幕顶部边缘的最后一个顶部像素拖动它,对于每个超过7的+1,我必须从边缘向下拖动1个像素。
这很烦人,因为它禁用了关闭窗口时的常见行为,强制我将其设置为7或更少。
有人能解释一下这种行为吗?
谢谢!

这可能会有用:https://dev59.com/KnA85IYBdhLWcg3wCfHm#2975574 - Contango
这很有用:https://dev59.com/_FPTa4cB1Zd3GeqPiVQh - Contango
2个回答

14

窗口没有奇怪的行为,相反,窗口有两个奇怪的行为

  • (A) 第一个奇怪的行为:

"[...] 当窗口最大化时,我无法从屏幕顶部边缘附近的最后一个像素拖动窗口 [...]"

这个行为是因为当窗口变为最大化状态时,边框调整大小的边缘仍然处于活动状态。实际上,这个边缘总是活动的。通过设置ResizeBorderThickness属性,WindowChrome保留了那么多像素来控制调整窗口大小的行为。鉴于在最大化模式下不允许调整大小事件,你会注意到这些像素不允许任何类型的行为。这正是因为WindowChrome专门保留了控制调整大小行为的那些像素。

解决方案是什么?需要通知WindowChrome在窗口最大化时将ResizeBorderThickness属性改为0。这可以通过在xaml中再次设置WindowChrome触发器来简单完成:

<Trigger Property="WindowState" Value="Maximized">
     <Setter Property="WindowChrome.WindowChrome">
          <Setter.Value>
               <WindowChrome ResizeBorderThickness="0" [...] />
          </Setter.Value>
     </Setter>
</Trigger>

注意:此功能也可以在运行时代码中完成。

  • (B) 第二个奇怪的行为

"[...] 如果我将 WindowChrome 的 ResizeBorderThickness 设置为 <= 7,一切正常 [...] 而每超过 7 个像素,我就必须从边缘向下拖动多一个像素。 [...]"

请注意:这种行为并不是由于设置了 ResizeBorderThickness,而是由于设置了属性 WindowStyle=None。当设置了此属性时,窗口在最大化时会表现出奇怪的行为:

  1. 窗口的左上角不位于当前屏幕的点 (0,0),而是会变得异常负数(在您的情况下,在 Y 轴上的值似乎为 -7)。

  2. 窗口的大小取决于当前屏幕的尺寸,而正常行为应该是窗口的大小取决于当前屏幕的工作区域(当前屏幕除任务栏等之外的部分)。

这种带有窗口的奇怪行为使得当前屏幕上没有显示出预留给“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


3
救了我的一天!谢谢,老兄。 - Vlad
我尝试过的所有解决方案中,这个实际上起作用了! - Jason Stevenson

0
如果您有一个全屏应用程序(WindowStyle设置为NoneAllowTransparency设置为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上找到。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接