【问题标题】:Scaling a set of polygons to monitor resolution in WPF缩放一组多边形以监视 WPF 中的分辨率
【发布时间】:2019-08-10 21:29:13
【问题描述】:

我有一个不支持 DPI 的 WPF 应用程序,我想在无边界窗口中绘制一组多边形以完全适合显示器。我有一个算法可以将我的多边形缩放和绘制到任何给定的分辨率。在我的设置中,我有一个 4K 和一个 FullHD 显示器并排放置。我的 4K 显示器的比例设置为 150%,全高清显示器设置为 100%。对于 4K 显示器,这意味着如果我将窗口宽度和高度设置为 3840x2160,则实际渲染的分辨率为 2560x1440。现在,如果我将我的一组多边形缩放到 4K,多边形会在画布和窗口之外渲染。我怀疑这是因为多边形不知道我的 4K 显示器的 DPI 设置。如果我在我的全高清显示器上绘制多边形,它们非常适合,因为显示器比例设置为 100%。

为了解决这个问题,我尝试了以下方法:

  • 检索每台显示器的 DPI,并在考虑 DPI 的情况下缩放多边形。

这部分有效。由于我的应用程序不支持 DPI(请注意,我不愿意让它支持 DPI,因为这会带来一系列全新的问题),任何检索显示器 DPI 的方法都会导致两个显示器的 DPI 为 144 (150%)。这导致多边形完美地适合我的 4K 显示器,但在我的全高清显示器上它们会被缩放得太小。我尝试了以下检索 DPI 的方法:GetDpiForMonitor from Shcore.dllVisualTreeHelperMatrixes。请注意,如果我将我的应用程序设置为 DPI 感知,这些方法确实有效,但对于引入的所有额外工作,我无法做到这一点。

  • 包裹 Canvas 的 ViewBox

当我将画布宽度和高度设置为 3840x2160 时,ViewBox 不会自动缩小内容(ViewBox 要求其内容,即画布,具有设置的宽度和高度)。

  • 检索监视器的“真实”/缩放分辨率

我的意思是我需要访问某种 API,它会为我的 4K 显示器返回 2560x1440 的分辨率。我尝试过经典的Windows.Forms.Screen API 以及更新的WindowsDispalyAPI。但两者总是为我的 4K 显示器返回 4K 分辨率。

所以我所有的算法都在工作,我只需要找到以下任何一个:

  • 一种可靠的方法来检索单个显示器的 DPI,同时让我的应用程序不感知 DPI。
  • 一种检索显示器缩放分辨率的方法。
  • 缩放一组多边形以适应屏幕的其他方式。

感谢任何帮助。

编辑:

这是一个无边框窗口的 xaml 示例,它在 4K 屏幕上以 150% 缩放重现问题:

<Window x:Class="Test.Views.FullscreenPolygon"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d" Width="3840" Height="2160"
        WindowStyle="None" AllowsTransparency="True" Background="Transparent">
    <Grid>
        <Canvas x:Name="CanvasArea">
            <Polygon Points="2888,0 3360,2140 3840,0" Fill="Black"></Polygon>
            <Polygon Points="1920,20 1450,2160 2400,2160" Fill="Black"></Polygon>
        </Canvas>
    </Grid>
</Window>

如您所见,两个多边形(三角形)都经过缩放以适应窗口的 4K 分辨率。由于监视器的 150% 缩放,窗口本身被渲染为 2560x1440。然而,多边形在它之外渲染,部分渲染到我的第二个屏幕上。

编辑2: 感谢 Jeff,它在他的 project 中使用了 GetScreenScaleFactorNonDpiAware 方法。

【问题讨论】:

    标签: c# wpf canvas polygon


    【解决方案1】:

    我需要在某些时候考虑屏幕缩放,正如 AlwaysLearning 所指出的,我必须导入和使用user32.dll,因为我也使用 4K 显示器,但我的显示器缩放到 125%。我为此创建了一个单独的类。

    我有一个测试程序来使用这个类。这是测试输出:

                 monitor name| \\.\DISPLAY2
                   native dpi| 96
                   screen dpi| 120
                 scale factor| 1.25
               scaling factor| 0.8
           native screen size| {X=0,Y=0,Width=3840,Height=2160}
           scaled screen size| {X=0,Y=0,Width=3072,Height=1728}
    

    以上是由此创建的:

    logMsgLn2("monitor name", ScreenParameters.GetMonitorName(this));
    logMsgLn2("native dpi", ScreenParameters.GetNativeScreenDpi);
    logMsgLn2("screen dpi", ScreenParameters.GetScreenDpi(this));
    logMsgLn2("scale factor", ScreenParameters.GetScreenScaleFactor(this));
    logMsgLn2("scaling factor", ScreenParameters.GetScreenScalingFactor(this));
    logMsgLn2("native screen size", ScreenParameters.GetNativeScreenSize(this));
    logMsgLn2("scaled screen size", ScreenParameters.GetScaledScreenSize(this));
    

    这是全班:

    public class ScreenParameters
    {
        private const double NativeScreenDpi = 96.0;
        private const int CCHDEVICENAME = 32;
    
        // private method to get the handle of the window
        // this keeps this class contained / not dependant
        public static double GetNativeScreenDpi
        {
            get => (int) NativeScreenDpi;
        }
    
        public static string GetMonitorName(Window win)
        {
            MONITORINFOEX mi = GetMonitorInfo(GetWindowHandle(win));
    
            return mi.DeviceName;
        }
    
        private static IntPtr GetWindowHandle(Window win)
        {
            return new WindowInteropHelper(win).Handle;
        }
    
        // the actual screen DPI adjusted for the scaling factor
        public static double GetScreenDpi(Window win)
        {
            return GetDpiForWindow(GetWindowHandle(win));
        }
    
        // this is the ratio of the current screen Dpi
        // and the base Dpi
        public static double GetScreenScaleFactor(Window win)
        {
            return (GetScreenDpi(win)  / NativeScreenDpi);
        }
    
        // this is the conversion factor between screen coordinates 
        // and sizes and their actual actual coordinate and size
        // e.g. for a screen set to 125%, this factor applied 
        // to the native screen dimensions, will provide the 
        // actual screen dimensions
        public static double GetScreenScalingFactor(Window win)
        {
            return (1 / (GetScreenDpi(win)  / NativeScreenDpi));
        }
    
        // get the dimensions of the physical / native screen
        // ignoring any applied scaling
        public static Rectangle GetNativeScreenSize(Window win)
        {
            MONITORINFOEX mi = GetMonitorInfo(GetWindowHandle(win));
    
            return ConvertRectToRectangle(mi.rcMonitor);
        }
    
        // get the screen dimensions taking the screen scaling into account
        public static Rectangle GetScaledScreenSize2(Window win)
        {
            double ScalingFactor = GetScreenScalingFactor(win);
    
            Rectangle rc = GetNativeScreenSize(win);
    
            if (ScalingFactor == 1) return rc;
    
            return rc.Scale(ScalingFactor);
        }
    
        public static Rectangle GetScaledScreenSize(Window win)
        {
            double dpi = GetScreenDpi(win);
    
            Rectangle rc = GetNativeScreenSize(win);
    
            return ScaleForDpi(rc, dpi);
        }
    
        internal static MONITORINFOEX GetMonitorInfo(IntPtr ptr)
        {
            IntPtr hMonitor = MonitorFromWindow(ptr, 0);
    
            MONITORINFOEX mi = new MONITORINFOEX();
            mi.Init();
            GetMonitorInfo(hMonitor, ref mi);
    
            return mi;
        }
    
        #region + Utility methods
    
        public static Rectangle ConvertRectToRectangle(RECT rc)
        {
            return new Rectangle(rc.Top, rc.Left, 
                rc.Right - rc.Left, rc.Bottom - rc.Top);
        }
    
    
        public static System.Drawing.Point ScaleForDpi(System.Drawing.Point pt, double dpi)
        {
            double factor = NativeScreenDpi / dpi;
    
            return new System.Drawing.Point((int) (pt.X * factor), (int) (pt.Y * factor));
        }
    
    
        public static Point ScaleForDpi(Point pt, double dpi)
        {
            double factor = NativeScreenDpi / dpi;
    
            return new Point(pt.X * factor, pt.Y * factor);
        }
    
        public static Size ScaleForDpi(Size size, double dpi)
        {
            double factor = NativeScreenDpi / dpi;
    
            return new Size(size.Width * factor, size.Height * factor);
        }
    
        public static System.Drawing.Size ScaleForDpi(System.Drawing.Size size, double dpi)
        {
            double factor = NativeScreenDpi / dpi;
    
            return new System.Drawing.Size((int) (size.Width * factor), (int) (size.Height * factor));
        }
    
        public static Rectangle ScaleForDpi(Rectangle rc, double dpi)
        {
            double factor = NativeScreenDpi / dpi;
    
            return new Rectangle(ScaleForDpi(rc.Location, dpi),
                    ScaleForDpi(rc.Size, dpi));
        }
    
            #endregion
    
        #region + Dll Imports
    
        [DllImport("user32.dll")]
        internal static extern UInt16 GetDpiForWindow(IntPtr hwnd);
    
    
        [DllImport("user32.dll")]
        internal static extern IntPtr MonitorFromWindow(IntPtr hwnd, uint dwFlags);
    
    
        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        internal static extern bool GetMonitorInfo(IntPtr hMonitor, ref MONITORINFOEX lpmi);
    
    
        [DllImport("user32.dll")]
        internal static extern UInt16 GetProcessDpiAwareness(IntPtr hwnd);
    
        #endregion
    
    
        #region + Dll Enums
    
        internal enum dwFlags : uint
        {
            MONITORINFO_PRIMARY = 1
        }
    
            #endregion
    
        #region + Dll Structs
    
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
        internal struct MONITORINFOEX
        {
            public uint    cbSize;
            public RECT    rcMonitor;
            public RECT    rcWorkArea;
            public dwFlags Flags;
    
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCHDEVICENAME)]
            public string DeviceName;
    
            public void Init()
            {
                this.cbSize     = 40 + 2 * CCHDEVICENAME;
                this.DeviceName = String.Empty;
            }
        }
    
        public struct RECT
        {
            public int Left;
            public int Top;
            public int Right;
            public int Bottom;
        }
    
        #endregion
    }
    

    我希望这会有所帮助。

    为了将来参考,我稍微更新了代码以允许非 DPI 感知监视器。我把更新的代码放在这里ScreenParameters

    【讨论】:

    • 如果我理解正确的话,我必须先在所需的监视器上创建全屏窗口,然后使用你的帮助类来获取监视器信息?
    • 不,不。助手类只需要任何窗口作为起点。窗口所在的监视器是提供数据的监视器。
    • 所以我用你的代码做了一些测试,当我询问在我的第二台显示器上打开的窗口的缩放时,它仍然返回 1.5 (150%),而它应该是 1 (100% ) 用于我的第二台显示器。当您将第二台显示器设置为与第一台显示器不同的缩放尺寸,然后尝试为第二台显示器获取缩放比例时,会发生什么情况?
    • 不确定我是否理解发生了什么。我进行了测试,并根据窗口所在的屏幕获得特定于屏幕的结果——这是我所期望的,也是我认为你想要的。您说您“要求缩放”窗口。你是怎么做的?这可能是个愚蠢的问题,但你确定你的第二台显示器是 100% 的吗?
    • 双重检查,设置为 100%。我的方法如下:我在单击按钮时创建了一个新窗口,将其 X 和 Y 设置为在我的第二台显示器上可见,并在其上调用 .Show()。此后,我使用您的助手检索 DPI,仍然得到 1.5。
    猜你喜欢
    • 2016-02-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-09-12
    • 1970-01-01
    • 1970-01-01
    • 2021-06-20
    • 1970-01-01
    相关资源
    最近更新 更多