缩放一组多边形以监视WPF中的分辨率 [英] Scaling a set of polygons to monitor resolution in WPF

查看:102
本文介绍了缩放一组多边形以监视WPF中的分辨率的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个不了解DPI的WPF应用程序,我想在无边界窗口中绘制一组多边形以使其完全适合监视器.我有一个算法可以缩放和绘制多边形到任何给定的分辨率.在我的设置中,我有一个4K显示器和一个FullHD显示器彼此相邻.我的4K显示器的缩放比例设置为150%,FullHD显示器的缩放比例设置为100%.对于4K显示器,这意味着如果将窗口的宽度和高度设置为3840x2160,则实际渲染的分辨率为2560x1440.现在,如果我将一组多边形缩放为4K,则这些多边形将在画布和窗口外部渲染.我怀疑这是因为多边形不知道我的4K显示器的DPI设置.如果我在FullHD显示器上绘制多边形,则由于该显示器的比例设置为100%,所以它们非常适合.

I have a non-DPI aware WPF application where I want to draw a set of polygons in a borderless window to fit exactly on a monitor. I have an algorithm in place to scale and draw my polygons to any given resolution. In my setup I have a 4K and a FullHD monitor next to each other. My 4K monitor has its scale set to 150% and the FullHD monitor is set to 100%. For the 4K monitor, this means that if I set a windows width and height to 3840x2160, the actual rendered resolution is 2560x1440. Now if I scale my set of polygons to 4K, the polygons get rendered outside the canvas and the window. I suspect this is because the polygons are not aware of the DPI setting of my 4K monitor. If I draw the polygons on my FullHD monitor, they fit perfectly since that monitors scale is set to 100%.

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

To combat this problem, I have tried the following:

  • 获取每个监视器的DPI,并在考虑DPI的情况下缩放多边形.

这部分起作用.由于我的应用程序不了解DPI(请注意,由于引入了一系列新问题,因此我不愿意使其了解DPI),因此,检索监视器DPI的任何方法都会导致两个监视器获得144(150%)的结果.这样一来,多边形可以完美地适合我的4K显示器,但是在FullHD显示器上它们的比例将变得太小.我尝试了以下方法来检索DPI:从Shcore.dllVisualTreeHelper

This works partly. Since my application is non-DPI aware (note that I am not willing to make it DPI aware since that introduces a whole new set of problems), any method for retrieving a monitors DPI results in getting 144 (150%) for both monitors. This results in the polygons fitting perfectly on my 4K monitor, but they will be scaled too small on my FullHD monitor. I have tried the following methods for retrieving DPI: GetDpiForMonitor from Shcore.dll, VisualTreeHelper and Matrixes. Note that these methods do work if I set my application to be DPI aware, but I can not do that for all the extra work that introduces.

  • 包装画布的ViewBox

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

ViewBox does not automatically downscale the contents when I set the canvas width and height to 3840x2160 (ViewBox requires its contents, the canvas, to have a set width and height).

  • 获取监视器的真实"/缩放分辨率

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

With this I mean I need to access an API of some kind which will return a resolution of 2560x1440 for my 4K monitor. I have tried the classic Windows.Forms.Screen API as well as the newer WindowsDispalyAPI. But both always return a 4K resolution for my 4K monitor.

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

So all my algorithms are working, I only need to find any of the following:

  • 一种可靠的方式来获取单个监视器的DPI,同时使我的应用程序不了解DPI.
  • 一种检索监视器的缩放分辨率的方法.
  • 通过其他方式缩放一组多边形以适合屏幕.

感谢您的帮助.

这是无边界窗口的xaml示例,可以在比例为150%的4K屏幕上重现该问题:

Here is an xaml exmaple of a borderless window which reproduces the problem on a 4K screen with 150% scaling:

<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.但是,多边形会在其外部渲染,部分渲染到我的第二个屏幕上.

As you can see, both polygons (triangles) are scaled to fit the 4K resolution of the window. The window itself gets rendered as 2560x1440, because of the 150% scaling of the monitor. The polygons however, get rendered outside of it, partly onto my second screen.

Edit2: 感谢Jeff,在他的项目中使用了GetScreenScaleFactorNonDpiAware方法.

Got it working thanks to Jeff, using the GetScreenScaleFactorNonDpiAware method in his project.

推荐答案

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

I needed to account for screen scaling at some point and, as AlwaysLearning notes, I had to import and use user32.dll as I too use 4K monitors but mine are scaled to 125%. I created a separate class for this.

我有一个利用此类的测试程序.这是测试输出:

I have a test program to utilize this class. Here is the test output:

             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

For future reference, I updated the code a bit to allow for Non-DPI aware monitors. I placed the updated code here ScreenParameters

这篇关于缩放一组多边形以监视WPF中的分辨率的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆