确定 WPF window 位置,使其始终在屏幕边界内可见(如 OS 的右键单击上下文菜单)

Determine WPF window location such that it is visible within screen bounds all the time (like OS's right click context menu)

如您在图中所见,我想在 Launch window(WinForms 应用程序)的位置打开一个固定大小的 WPF window。如何确保即将打开的 WPF windows 放置在 Launch window 的任何一侧都完全可见。 Windows 桌面的右键单击菜单中有类似的行为,就好像您单击屏幕的最边缘一样,它会在左侧打开上下文菜单,如果您在屏幕中间,它会打开任一侧。

我已经尝试了一些东西,也尝试了这个 SO 答案,但仍在弄清楚如何计算 Windows 的边界,使其在可见区域内。 WPF determine element is visible on screen

过程将是:

  1. 使用其句柄获取 window 的矩形。
  2. 获取window所在监视器的句柄。
  3. 使用监视器的句柄获取监视器的信息(特别是工作区域的矩形)。
  4. 计算可用方向space.
using System;
using System.Runtime.InteropServices;

public enum Direction { None, TopLeft, TopRight, BottomRight, BottomLeft }

public static class WindowHelper
{
    public static Direction GetAvailableDirection(IntPtr windowHandle)
    {
        if (!GetWindowRect(windowHandle, out RECT buffer))
            return Direction.None;

        System.Drawing.Rectangle windowRect = buffer;

        IntPtr monitorHandle = MonitorFromWindow(windowHandle, MONITOR_DEFAULTTO.MONITOR_DEFAULTTONULL);
        if (monitorHandle == IntPtr.Zero)
            return Direction.None;

        MONITORINFO info = new() { cbSize = (uint)Marshal.SizeOf<MONITORINFO>() };
        if (!GetMonitorInfo(monitorHandle, ref info))
            return Direction.None;

        System.Drawing.Rectangle workingAreaRect = info.rcWork;

        bool isWindowAlignedTop = (windowRect.Top - workingAreaRect.Top) < (workingAreaRect.Bottom - windowRect.Bottom);
        bool isWindowAlignedLeft = (windowRect.Left - workingAreaRect.Left) < (workingAreaRect.Right - windowRect.Right);

        return (isWindowAlignedTop, isWindowAlignedLeft) switch
        {
            (true, true) => Direction.BottomRight,
            (true, false) => Direction.BottomLeft,
            (false, true) => Direction.TopRight,
            (false, false) => Direction.TopLeft
        };
    }

    [DllImport("User32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool GetWindowRect(
        IntPtr hWnd,
        out RECT lpRect);

    [DllImport("User32.dll")]
    private static extern IntPtr MonitorFromWindow(
        IntPtr hwnd,
        MONITOR_DEFAULTTO dwFlags);

    private enum MONITOR_DEFAULTTO : uint
    {
        MONITOR_DEFAULTTONULL = 0x00000000,
        MONITOR_DEFAULTTOPRIMARY = 0x00000001,
        MONITOR_DEFAULTTONEAREST = 0x00000002,
    }

    [DllImport("User32.dll", CharSet = CharSet.Unicode)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool GetMonitorInfo(
        IntPtr hMonitor,
        ref MONITORINFO lpmi);

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    private struct MONITORINFO
    {
        public uint cbSize;
        public RECT rcMonitor;
        public RECT rcWork;
        public uint dwFlags;
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct RECT
    {
        public int left;
        public int top;
        public int right;
        public int bottom;

        public static implicit operator System.Drawing.Rectangle(RECT rect)
        {
            return new System.Drawing.Rectangle(
                rect.left,
                rect.top,
                rect.right - rect.left,
                rect.bottom - rect.top);
        }
    }
}

请注意调用应用程序必须有一个应用程序清单,其中包括正确计算的 DPI 感知。