簡體   English   中英

WPF Window Per-Monitor-DPI 的啟動位置

[英]WPF Window startup location for Per-Monitor-DPI

努力讓 WPF Window 出現在帶有混合 DPI 監視器的輔助屏幕上。 可在 .NET Framework 4.8 和 .NET Standard 2.0 中重現

設置:

主顯示器:4K,250%

輔助顯示器:1080p,100%

在此處輸入圖像描述

步驟1:

為 PerMonitorV2 添加清單

    <?xml version="1.0" encoding="utf-8"?>

    <assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
  
      <application xmlns="urn:schemas-microsoft-com:asm.v3">
        <windowsSettings>
          <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2, PerMonitor</dpiAwareness>
          <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
        </windowsSettings>
      </application>

    </assembly>

第2步:

    public MainWindow()
    {
      SourceInitialized += (_, __) =>
      {
        WindowStartupLocation = WindowStartupLocation.Manual;
        WindowState = WindowState.Normal;

        Width = 1920;
        Height = 1050;

        Left = -1920;
        Top = 0;
      };

      InitializeComponent();
    }

結果:

MainWindow 確實顯示在輔助屏幕上,但 Left/Top 錯誤並且使用了主屏幕的 DPI。 只有寬度和高度是正確的。

參考:

我找到的唯一參考資料是關於記事本的,寫在 MFC 中:

https://blogs.windows.com/windowsdeveloper/2016/10/24/high-dpi-scaling-improvements-for-desktop-applications-and-mixed-mode-dpi-scaling-in-the-windows-10-周年更新/#jwYiMyGKQRTHkBP7.97

https://github.com/Microsoft/Windows-classic-samples/tree/main/Samples/DPIAwarenessPerWindow

關於GitHub的討論(WPF workarounds)

https://github.com/do.net/wpf/issues/4127

它在說一些關於 SetThreadDpiAwarenessContext 的事情,但我不清楚如何讓它在 C# 中工作....

DPI_AWARENESS_CONTEXT previousDpiContext = 
SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_UNAWARE);
BOOL ret = SetWindowPlacement(hwnd, wp);
SetThreadDpiAwarenessContext(previousDpiContext);

您可以將 window 移到任何顯示器的中央。 這只是一個計算問題。

using System;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;

public static class WindowHelper
{
    public static void MoveToCenter(Window window)
    {
        if (!GetCursorPos(out POINT cursorPoint))
            return;

        IntPtr monitorHandle = MonitorFromPoint(cursorPoint, MONITOR_DEFAULTTO.MONITOR_DEFAULTTONULL);

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

        IntPtr windowHandle = new WindowInteropHelper(window).EnsureHandle();

        if (!GetWindowPlacement(windowHandle, out WINDOWPLACEMENT windowPlacement))
            return;

        int left = monitorInfo.rcWork.left + Math.Max(0, (int)((monitorInfo.rcWork.Width - windowPlacement.rcNormalPosition.Width) / 2D));
        int top = monitorInfo.rcWork.top + Math.Max(0, (int)((monitorInfo.rcWork.Height - windowPlacement.rcNormalPosition.Height) / 2D));

        windowPlacement.rcNormalPosition = new RECT(left, top, windowPlacement.rcNormalPosition.Width, windowPlacement.rcNormalPosition.Height);
        SetWindowPlacement(windowHandle, ref windowPlacement);
    }

    [DllImport("User32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool GetCursorPos(out POINT lpPoint);

    [DllImport("User32.dll")]
    private static extern IntPtr MonitorFromPoint(POINT pt, MONITOR_DEFAULTTO dwFlags);

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

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

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

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

    [DllImport("User32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool SetWindowPlacement(IntPtr hWnd, [In] ref WINDOWPLACEMENT lpwndpl);

    [StructLayout(LayoutKind.Sequential)]
    private struct WINDOWPLACEMENT
    {
        public uint length;
        public uint flags;
        public uint showCmd;
        public POINT ptMinPosition;
        public POINT ptMaxPosition;
        public RECT rcNormalPosition;
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct POINT
    {
        public int x;
        public int y;
    }

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

        public int Width => right - left;
        public int Height => bottom - top;

        public RECT(int x, int y, int width, int height)
        {
            left = x;
            top = y;
            right = x + width;
            bottom = y + height;
        }
    }
}
using System.Windows;

public partial class MainWindow : Window
{
    public MainWindow()
    {
        WindowStartupLocation = WindowStartupLocation.CenterScreen;
        InitializeComponent();        
    }

    private bool _isMoved;

    protected override Size ArrangeOverride(Size arrangeBounds)
    {
        if (!_isMoved)
        {
            _isMoved = true;
            WindowHelper.MoveToCenter(this);
        }
        return base.ArrangeOverride(arrangeBounds);
    }
}

但我發現標題欄的 DPI 與主顯示器的 DPI 保持一致。 一般來說,非客戶區的 DPI 很難修復。 因此,除非刪除默認標題欄,否則此 hack 不太實用。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM