自动缩放但仍在处理WM_DPICHANGED [英] Auto-Scale but still process WM_DPICHANGED

查看:607
本文介绍了自动缩放但仍在处理WM_DPICHANGED的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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



难题是,如果我不注意应用程序DPI,则在DefWndProc中将永远不会截获WM_DPICHANGED消息,并且永远也不会检索到正确的DPI比例,而是自动按我想要的方式缩放。但是,如果我使应用程序知道DPI,则将截获WM_DPICHANGED消息,并且可以计算出适当的DPI,但表格不会自动缩放。



我说过该应用程序非常复杂,并使用了许多第三方控件,因此我无法花时间在WPF中重新编写该应用程序或自己尝试扩展该应用程序。



我如何获得该应用程序以拦截WM_DPICHANGED消息,计算适当的DPI并仍允许Windows管理表单缩放?



Program.cs:

 静态类程序
{
[STAThread]
static void Main()
{
if(Environment.OSVersion.Version.Major> = 6)
{
//如果以下行被注释掉,则该应用程序将不再支持DPI,而
// WM_DPICHANGED事件将永远不会以
形式的DefWndProc触发int retValue = SetProcessDpiAwareness(ProcessDPIAware ness.ProcessPerMonitorDPIAware);

}

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

私有枚举ProcessDPIAwareness
{
ProcessDPIUnaware = 0,
ProcessSystemDPIAware = 1,
ProcessPerMonitorDPIAware = 2
}

[DllImport( shcore.dll)]
私有静态外部整数SetProcessDpiAwareness(ProcessDPIAwareness value);
}

Form1.cs:

 受保护的覆盖无效DefWndProc(ref Message m)
{
switch(m.Msg)
{
case 0x02E0:// WM_DPICHANGED
{
int newDpi = m.WParam.ToInt32()& 0xFFFF;
float scaleFactor =(float)newDpi /(float)96;
}
休息;
}

base.DefWndProc(ref m);
}

更新:
我正在使用Windows 10具有多个监视器设置。所有显示器均为同一型号,基本分辨率为1920x1080。我使用显示设置将其中一个监视器设置为大小的125%。

解决方案

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

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

 使用(图形屏幕= Graphics.FromHwnd(IntPtr.Zero) )
{
IntPtr hdc = screen.GetHdc();
int dpiX = GetDeviceCaps(hdc,DeviceCaps.LOGPIXELSX);
屏幕。ReleaseHdc(hdc);
}

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




  • 您使用Windows XP或在DPI设置中签入了兼容模式。 问题:您无法在用户身上强制实施。

  • DWM已关闭(您使用基本或经典主题)。 问题:与上述相同。

  • 您致电使用GetDeviceCaps之前的SetProcessDPIAware 函数。 问题:应在所有其他渲染之前调用此函数一次。如果您已有不支持DPI的应用程序,则更改感知状态将破坏整个外观。调用函数后无法将其关闭。

  • 您致电使用GetDeviceCaps之前和之后的SetProcessDpiAwareness 问题:此功能至少需要Windows 8.1



真正有效的解决方案



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

  public static int GetSystemDpi()
{
使用(图形屏幕= Graphics.FromHwnd(IntPtr.Zero))
{
IntPtr hdc = screen.GetHdc();

int virtualWidth = GetDeviceCaps(hdc,DeviceCaps.HORZRES);
int physicalWidth = GetDeviceCaps(hdc,DeviceCaps.DESKTOPHORZRES);
屏幕。ReleaseHdc(hdc);

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

以及以上示例中所需的其他代码:

 私有枚举DeviceCaps 
{
///< summary>
/// X的逻辑像素英寸
///< / summary>
LOGPIXELSX = 88,

///< summary>
///水平宽度(以像素为单位)
///< / summary>
HORZRES = 8,

///< summary>
///整个桌面的水平宽度(以像素为单位)
///< / summary>
桌面恐怖= 118
}

///< summary>
///检索指定设备的设备特定信息。
///< / summary>
///< param name = hdc> DC的句柄。< / param>
///< param name = nIndex>要返回的项目。< / param>
[DllImport( gdi32.dll)]
私有静态外部整数int GetDeviceCaps(IntPtr hdc,DeviceCaps nIndex);


I'm having a bit of a problem with a very complicated WinForms application written in C#. I want the application to let Windows auto-scale when the DPI is changed but I still need to hook the WM_DPICHANGED event in order to scale some custom drawn text.

The dilemma is that if I leave the application DPI unaware the WM_DPICHANGED message is never intercepted in the DefWndProc and the proper DPI scale can never be retrieved but the form "auto-scales" the way I want. But if I make the application DPI Aware then the WM_DPICHANGED message is intercepted and the proper DPI can be calculated but the form will not "auto-scale".

As I said the application is very complex and uses a lot of third-party controls so I am unable to take the time to re-write the app in WPF or try and scale the application myself.

How can I get the app to intercept the WM_DPICHANGED message, calculate the proper DPI and still allow Windows to manage the form scaling?

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);
    }

UPDATE: I am using Windows 10 with multiple monitor setup. All monitor are the same model with a base resolution of 1920x1080. I set one of my monitors to be at 125% of the size using the display settings.

解决方案

Instead of capturing the WM_DPICHANGED event, what about just asking the current DPI settings whenever you need it (in Paint events or whatever)?

This is also not obvious, though. If you search StackOverflow, usually you can find the following answer:

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

However, it will return always 96, regardless of actual DPI settings, unless...

  • You use Windows XP or the compatibility mode is checked in at DPI settings. Problem: you cannot enforce it at the users.
  • DWM is turned off (you use Basic or Classic themes). Problem: same as above.
  • You call SetProcessDPIAware function before using GetDeviceCaps. Problem: This function should be called once, before all other rendering. If you have an existing DPI-unaware app, changing the awareness will ruin the whole appearance. It cannot be turned off once you called the function.
  • You call SetProcessDpiAwareness before and after using GetDeviceCaps. Problem: This function requires at least Windows 8.1

The real working solution

It seems that the GetDeviceCaps function is not fully documented at MSDN. At least I discovered that pinvoke.net mentions a few further options that can be obtained by the function. At the end I came out with the following solution:

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);
    }
}

And the required additional code in the examples above:

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);

这篇关于自动缩放但仍在处理WM_DPICHANGED的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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