如何将 window 移动到另一个具有不同比例的屏幕?

How to move a window to another screen with a different scale?

我有这两个屏幕:

  1. 1080p,100% 比例。
    1. 绑定:{3840,0,1920,1080}
    2. 原生边界:{3840,0,1920,1080}
  2. 4k,200% 比例,设为主要。
    1. 边界:{0,0,1920,1080}
    2. 原生边界:{0,0,3840,2160}

现在,假设一个支持多 DPI 的应用程序在 屏幕 2.

内显示 window

如何将 window 移动到 屏幕 1 的左上角?

我不想局限于此配置,因为用户可以添加、删除或重新定位屏幕。


我了解 window 的 LeftTop 属性的设置与 window 的缩放有关。

设置 Left = 2000; 将根据 window 的比例转换到不同的位置。

考虑到比例差异,我已经尝试将位置设置为目标屏幕的边界。

Left = nextScreen.NativeBounds.Left / (this.Scale() / nextScreen.Scale) + 30; 
Top = nextScreen.NativeBounds.Top / (this.Scale() / nextScreen.Scale) + 30;

对于 200% 比例屏幕内的 window,计算得出的 Left 1950 将转化为超过 3840。

但是不行。它将 window 定位在 屏幕 2.

的中间顶部位置

如果不根据周围的屏幕集计算位置,我看不到任何其他方法可以做到这一点,如果有很多屏幕,这可能会很复杂。

我能够通过简单的 p/invoke 将 window 移动到 SetWindowPos,传递目标 window:

internal static class Windows
{
    /// <summary>
    /// Special window handles.
    /// </summary>
    internal enum SpecialWindowHandles
    {
        /// <summary>
        /// Places the window at the top of the Z order.
        /// </summary>
        Top = 0,
        
        /// <summary>
        /// Places the window at the bottom of the Z order.
        /// If the hWnd parameter identifies a topmost window, the window loses its topmost status and is placed at the bottom of all other windows.
        /// </summary>
        Bottom = 1,
        
        /// <summary>
        /// Places the window above all non-topmost windows.
        /// The window maintains its topmost position even when it is deactivated.
        /// </summary>
        TopMost = -1,
        
        /// <summary>
        /// Places the window above all non-topmost windows (that is, behind all topmost windows).
        /// This flag has no effect if the window is already a non-topmost window.
        /// </summary>
        NoTopMost = -2
    }

    [Flags]
    internal enum SetWindowPosFlags : uint
    {
        /// <summary>
        /// If the calling thread and the thread that owns the window are attached to different input queues, the system posts the request to the thread that owns the window.
        /// This prevents the calling thread from blocking its execution while other threads process the request.
        /// </summary>
        AsyncWindowPositioning = 0x4000,

        /// <summary>
        /// Prevents generation of the WM_SYNCPAINT message.
        /// </summary>
        DeferErase = 0x2000,

        /// <summary>
        /// Draws a frame (defined in the window's class description) around the window.
        /// </summary>
        DrawFrame = 0x0020,

        /// <summary>
        /// Applies new frame styles set using the SetWindowLong function.
        /// Sends a WM_NCCALCSIZE message to the window, even if the window's size is not being changed.
        /// If this flag is not specified, WM_NCCALCSIZE is sent only when the window's size is being changed.
        /// </summary>
        FrameChanged = 0x0020,

        /// <summary>
        /// Hides the window.
        /// </summary>
        HideWindow = 0x0080,

        /// <summary>
        /// Does not activate the window.
        /// If this flag is not set, the window is activated and moved to the top of either the topmost or non-topmost group (depending on the setting of the hWndInsertAfter parameter).
        /// </summary>
        NoActivate = 0x0010,

        /// <summary>
        /// Discards the entire contents of the client area.
        /// If this flag is not specified, the valid contents of the client area are saved and copied back into the client area after the window is sized or repositioned.
        /// </summary>
        NoCopyBits = 0x0100,

        /// <summary>
        /// Retains the current position (ignores X and Y parameters).
        /// </summary>
        NoMove = 0x0002,

        /// <summary>
        /// Does not change the owner window's position in the Z order.
        /// </summary>
        NoOwnerZOrder = 0x0200,

        /// <summary>
        /// Does not redraw changes. If this flag is set, no repainting of any kind occurs.
        /// This applies to the client area, the nonclient area (including the title bar and scroll bars), and any part of the parent window uncovered as a result of the window being moved.
        /// When this flag is set, the application must explicitly invalidate or redraw any parts of the window and parent window that need redrawing.
        /// </summary>
        NoRedraw = 0x0008,

        /// <summary>
        /// Same as the NoOwnerZOrder flag.
        /// </summary>
        NoReposition = 0x0200,

        /// <summary>
        /// Prevents the window from receiving the WM_WINDOWPOSCHANGING message.
        /// </summary>
        NoSendChanging = 0x0400,

        /// <summary>
        /// Retains the current size (ignores the cx and cy parameters).
        /// </summary>
        NoSize = 0x0001,

        /// <summary>
        /// Retains the current Z order (ignores the hWndInsertAfter parameter).
        /// </summary>
        NoZOrder = 0x0004,

        /// <summary>
        /// Displays the window.
        /// </summary>
        ShowWindow = 0x0040
    }

    [DllImport("user32.dll", SetLastError = true)]
    internal static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, SetWindowPosFlags uFlags);


    internal static void MoveToScreen(this Window window, Monitor next, bool fullScreen = false)
    {
        if (fullScreen)
        {
            SetWindowPos(new System.Windows.Interop.WindowInteropHelper(window).Handle, (IntPtr)SpecialWindowHandles.Top,
                (int)next.NativeBounds.Left, (int)next.NativeBounds.Top, (int)next.NativeBounds.Width, (int)next.NativeBounds.Height, SetWindowPosFlags.ShowWindow);
            return;
        }

        SetWindowPos(new System.Windows.Interop.WindowInteropHelper(window).Handle, (IntPtr)SpecialWindowHandles.Top,
            (int)next.NativeBounds.Left, (int)next.NativeBounds.Top, (int)window.Width, (int)window.Height, SetWindowPosFlags.ShowWindow);
    }
}

用法:

//targetMonitor is a custom class which holds the details about a screen,
//such as bounds, name, scale, etc.

this.MoveToScreen(targetMonitor);