自动缩放但仍处理WM_DPICHANGED

时间:2015-10-26 16:31:12

标签: c# winforms dpi

我在使用C#编写的非常复杂的WinForms应用程序时遇到了一些问题。我希望应用程序在更改DPI时让Windows自动缩放但我仍然需要挂钩WM_DPICHANGED事件以缩放某些自定义绘制文本。

困境是,如果我离开应用程序DPI没有意识到WM_DPICHANGED消息永远不会在DefWndProc中被拦截,并且永远无法检索到正确的DPI比例,但表单“自动缩放”我想要的方式。但是,如果我将应用程序设置为DPI Aware,则会拦截WM_DPICHANGED消息,并且可以计算出正确的DPI,但表单不会“自动缩放”。

正如我所说,应用程序非常复杂并且使用了很多第三方控件,因此我无法花时间在WPF中重写应用程序或尝试自行扩展应用程序。

如何让应用拦截WM_DPICHANGED消息,计算正确的DPI并仍允许Windows管理表单缩放?

的Program.cs:

static class Program
{       
    [STAThread]
    static void Main()
    {
        if (Environment.OSVersion.Version.Major >= 6)
        {
            // If the line below is commented out then the app is no longer DPI aware and the 
            // WM_DPICHANGED event will never fire in the DefWndProc of the form
            int retValue = SetProcessDpiAwareness(ProcessDPIAwareness.ProcessPerMonitorDPIAware);

        }

        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new Form1());
    }

    private enum ProcessDPIAwareness
    {
        ProcessDPIUnaware = 0,
        ProcessSystemDPIAware = 1,
        ProcessPerMonitorDPIAware = 2
    }

    [DllImport("shcore.dll")]
    private static extern int SetProcessDpiAwareness(ProcessDPIAwareness value);
}

Form1.cs中:

protected override void DefWndProc(ref Message m)
    {
        switch (m.Msg)
        {
            case 0x02E0: //WM_DPICHANGED
                {                       
                    int newDpi = m.WParam.ToInt32() & 0xFFFF;
                    float scaleFactor = (float)newDpi / (float)96;                      
                }
                break;
        }

        base.DefWndProc(ref m);
    }

更新 我正在使用Windows 10与多显示器设置。所有显示器都是相同的型号,基本分辨率为1920x1080。我使用显示设置将我的一个显示器设置为大小的125%。

2 个答案:

答案 0 :(得分:5)

不是捕获WM_DPICHANGED事件,而是在需要时(在Paint事件或其他事件中)询问当前的DPI设置?

但这并不明显。如果您搜索StackOverflow,通常可以找到以下答案:

using (Graphics screen = Graphics.FromHwnd(IntPtr.Zero))
{
    IntPtr hdc = screen.GetHdc();
    int dpiX = GetDeviceCaps(hdc, DeviceCaps.LOGPIXELSX);
    screen.ReleaseHdc(hdc);
}

但是,无论实际DPI设置如何,它都将始终返回96,除非......

  • 您使用Windows XP或在DPI设置中签入兼容模式。 问题:您无法在用户处执行此操作。
  • DWM已关闭(您使用的是Basic或Classic主题)。 问题:与上述相同。
  • 在使用GetDeviceCaps之前调用SetProcessDPIAware函数。 问题:在所有其他渲染之前,应调用此函数一次。如果你有一个现有的DPI-unaware应用程序,改变意识将破坏整个外观。调用该功能后无法关闭它。
  • 您在使用GetDeviceCaps之前和之后致电SetProcessDpiAwareness问题:此功能至少需要Windows 8.1

真正有效的解决方案

似乎MSDN上没有完整记录GetDeviceCaps function。至少我发现pinvoke.net提到了函数可以获得的一些其他选项。最后,我提出了以下解决方案:

public static int GetSystemDpi()
{
    using (Graphics screen = Graphics.FromHwnd(IntPtr.Zero))
    {
        IntPtr hdc = screen.GetHdc();

        int virtualWidth = GetDeviceCaps(hdc, DeviceCaps.HORZRES);
        int physicalWidth = GetDeviceCaps(hdc, DeviceCaps.DESKTOPHORZRES);
        screen.ReleaseHdc(hdc);

        return (int)(96f * physicalWidth / virtualWidth);
    }
}

以上示例中所需的附加代码:

private enum DeviceCaps
{
    /// <summary>
    /// Logical pixels inch in X
    /// </summary>
    LOGPIXELSX = 88,

    /// <summary>
    /// Horizontal width in pixels
    /// </summary>
    HORZRES = 8,

    /// <summary>
    /// Horizontal width of entire desktop in pixels
    /// </summary>
    DESKTOPHORZRES = 118
}

/// <summary>
/// Retrieves device-specific information for the specified device.
/// </summary>
/// <param name="hdc">A handle to the DC.</param>
/// <param name="nIndex">The item to be returned.</param>
[DllImport("gdi32.dll")]
private static extern int GetDeviceCaps(IntPtr hdc, DeviceCaps nIndex);

答案 1 :(得分:3)

我终于在taffer的帮助下完成了这项工作。设置此方法的方法是不将应用程序设置为DPI识别,并计算在进行任何自定义绘图或字体大小调整时要使用的特定mointor的DPI设置。无需捕获无论如何都不会发射的WM_DPICHANGED事件。

hwnd参数是程序窗口的句柄。 Win32类用于保存所有PInvoke内容。这是解决方案:

public static int GetSystemDpi(IntPtr hwnd)
    {
        Screen screen = Screen.FromHandle(hwnd);
        IntPtr screenHdc = Win32.CreateDC(null, screen.DeviceName, null, IntPtr.Zero);

        int virtualWidth = Win32.GetDeviceCaps(screenHdc, (int)Win32.DeviceCap.HORZRES);
        int physicalWidth = Win32.GetDeviceCaps(screenHdc, (int)Win32.DeviceCap.DESKTOPHORZRES);

        Win32.DeleteDC(screenHdc);

        return (int)(96f * physicalWidth / virtualWidth);
    }

<强>示例:

此方法的典型用法是在不支持DPI的应用程序中执行自定义绘图,并允许操作系统自动缩放它。例如,如果我在主屏幕上在屏幕上绘制字体,那么我将使用代码:

private void DrawText()
    {
        // Figure out any screen scaling needed         
        float fScale = ((float)GetSystemDpi(this.Handle) / 96f);
        Font myfont = new Font("whatever", 10f * fScale, FontStyle.Regular);

        using (Graphics g = this.CreateGraphics())
        {
            g.DrawString("My Custom Text", myfont, Brushes.Black, new PointF(0, 0));
        }       
    }