XAML WindowChrome ResizeBorderThickness问题

bf1o4zei  于 2023-09-28  发布在  其他
关注(0)|答案(2)|浏览(67)

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

wmtdaxz3

wmtdaxz31#

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

*(A)第一个奇怪的行为

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

**此行为是由于当窗口更改为最大化状态时,要调整大小的边缘仍处于活动状态。实际上,此边缘始终处于活动状态。**设置 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>
  • 注意:这也可以在运行时代码中执行 *
    *(B)第二个奇怪的行为

“[...]如果我将WindowChrome的ResizeBorderThickness设置为<= 7,那么一切都可以完美地工作[...],并且对于每个超过7的+1,我必须从边缘开始向下拖动1个像素。[...]”

**保重。实际上,这种行为不是由于 ResizeBorderThickness 中设置的值,而是由于设置了属性 WindowStyle=None。设置此属性后,窗口在最大化时会呈现奇怪的行为:

1.窗口的左上边缘没有位于当前屏幕的点(0,0),而是不规则地变为负值(在您的示例中,在Y轴上的值似乎是-7)。
1.窗口的大小取当前屏幕的大小,而正常行为应该是窗口的大小取当前屏幕的当前 * 工作区 *(当前屏幕除任务栏等)的大小。
这个奇怪的行为,使保留给'WindowChrome'的7个像素在当前屏幕中不可见(显然 ResizeBorderThickness=“7”),因此给你的感觉是属性 ResizeBorderThickness=“7” 工作正常,当它不是。事实上,当 ResizeBorderThickness 的值为8或更大时,这证明了行为的正确性。

**解决方案是什么?在当前屏幕的工作区域上最大化一个大小和位置时,需要 * 强制 * 窗口。 警告:如果您只对主屏幕执行此操作,则maximize事件无法在多个屏幕上正常工作。

我通过调用外部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

neskvpey

neskvpey2#

如果你有一个全屏应用程序(WindowStyle设置为None,AllowTransparency设置为true),你需要对excellent answer from 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);

通过将逻辑 Package 在一个可以保持状态的类中,我们甚至可以让我们的窗口随意进入和退出全屏模式:

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

相关问题