如何在.NET Framework环境下触发WinForms的DpiChanged事件?
WinForms DpiChanged 事件无法触发解决方案
根因说明
.NET Framework 4.7+ 版本的 WinForms 内部DpiHelper类存在隐藏的触发条件限制,即使完成manifest和app.config的基础配置,也可能因为内部开关未激活、控件非顶层等原因,导致原生DpiChanged事件被拦截,无法正常触发。
一、基础配置校验
首先确认配置项的位置和格式完全符合要求:
- app.config 中必须同时配置
DpiAwareness和EnableWindowsFormsHighDpiAutoResizing,仅添加后者无效,配置节点如下:
<configuration> <System.Windows.Forms.ApplicationConfigurationSection> <add key="DpiAwareness" value="PerMonitorV2" /> <add key="EnableWindowsFormsHighDpiAutoResizing" value="true" /> </System.Windows.Forms.ApplicationConfigurationSection> </configuration>
二、强制激活原生DpiChanged事件
通过反射修改内部开关,绕开DpiHelper的条件限制:
在程序入口Program.cs的Application.Run执行前添加以下代码:
using System.Reflection; using System.Windows.Forms; static void Main() { // 强制开启DpiChanged事件的内部触发开关 Type dpiHelperType = typeof(Control).Assembly.GetType("System.Windows.Forms.DpiHelper"); if (dpiHelperType != null) { FieldInfo enableEventField = dpiHelperType.GetField("EnableDpiChangedEvent", BindingFlags.NonPublic | BindingFlags.Static); enableEventField?.SetValue(null, true); } Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new YourMainForm()); }
修改后顶层窗体的原生DpiChanged事件即可正常触发,DeviceDpi属性也会同步更新为当前最新DPI值。
三、无反射替代方案
如果生产环境不允许使用反射,可以基于已监听到的WM_DPICHANGED消息自定义封装事件:
public class DpiAwareBaseForm : Form { /// <summary> /// 自定义DPI变化事件,替代原生不触发的DpiChanged /// </summary> public event EventHandler<DpiChangedEventArgs> CustomDpiChanged; private const int WM_DPICHANGED = 0x02E0; protected override void WndProc(ref Message m) { if (m.Msg == WM_DPICHANGED) { int oldDpi = DeviceDpi; int newDpi = (m.WParam.ToInt32() & 0xFFFF); CustomDpiChanged?.Invoke(this, new DpiChangedEventArgs(oldDpi, newDpi, Bounds)); // 可选:手动更新DeviceDpi属性为最新值 PropertyInfo deviceDpiProp = typeof(Control).GetProperty("DeviceDpi", BindingFlags.Instance | BindingFlags.NonPublic); deviceDpiProp?.SetValue(this, newDpi); } base.WndProc(ref m); } }
所有需要监听DPI变化的窗体继承该基类,订阅CustomDpiChanged事件即可。
注意事项
- .NET Framework版本的WinForms不会对DpiChanged事件做控件冒泡,子控件需要主动订阅父窗体的DPI变化事件
- 不要在窗体构造函数中访问
DeviceDpi属性,该属性在窗体渲染完成前只会返回程序启动时的初始DPI值
内容的提问来源于stack exchange,提问作者Sören Kuklau
相关产品推荐
相关产品推荐

