我在使用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%。
答案 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,除非......
似乎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));
}
}