當程序運行,窗口已經加載后,如果修改屏幕分辨率,會影響窗口的正常顯示。
舉個案例:
懸浮窗口,顯示在屏幕右下角。當分辨率、文本顯示比例變更后,窗口位置可能會超出屏幕范圍。
所以當屏幕變更時,我們需要知道准確的時機,然后針對的處理。
通過窗口消息監聽屏幕顯示變更
對窗口添加鈎子
1 var windowInteropHelper = new WindowInteropHelper(this); 2 var hwnd = windowInteropHelper.Handle; 3 HwndSource source = HwndSource.FromHwnd(hwnd); 4 source?.AddHook(Hook);
對窗口消息添加處理
1 private const int WmDisplayChange = 0x007e; 2 private IntPtr Hook(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam, ref bool handled) 3 { 4 if (msg == WmDisplayChange) 5 { 6 SetLocation(); 7 } 8 return IntPtr.Zero; 9 }
“0x007e”是屏幕分辨率以及文本顯示比例變更對應的消息標識。
“0x02E0”是文本顯示比例變更的消息標識。這個標識更具體,但需要在程序未開啟DPI感知后,才會收到0x02E0消息。
通過系統事件監聽屏幕顯示變更
上面通過鈎子來判斷相應的窗口消息,其實也有系統事件封裝了這類的處理:
1 public MainWindow() 2 { 3 InitializeComponent(); 4 Microsoft.Win32.SystemEvents.DisplaySettingsChanged += SystemEvents_DisplaySettingsChanged; 5 } 6 private void SystemEvents_DisplaySettingsChanged(object sender, EventArgs e) 7 { 8 9 }
屏幕變更時,回調事件參數如下:
這個系統靜態事件和窗口消息是一樣的,觸發次數和時機一樣。調試發現觸發順序:窗口消息->系統靜態事件。
因為這個事件沒有任何實際的數據,所以只能通過其它方式獲取DPI。
更新窗口位置
屏幕顯示變更的時機有了,可以根據時間添加相應的操作:
1 private void SetLocation() 2 { 3 var dpiForWindow = GetDpiForWindow(new WindowInteropHelper(this).Handle); 4 var windowRatio = (double)dpiForWindow / 96.0; 5 6 var intPtr = new WindowInteropHelper(this).Handle;//獲取當前窗口的句柄 7 var screen = Screen.FromHandle(intPtr);//獲取當前屏幕 8 var locationX = (screen.Bounds.Width - 300) / windowRatio; 9 var locationY = (screen.Bounds.Height - 300) / windowRatio; 10 Left = locationX; 11 Top = locationY; 12 }
獲取對應屏幕的DPI信息,並轉換成WPF的DPI比例,計算出窗口的最新位置。
其它的獲取方式,可以見:C# 獲取當前屏幕DPI - 唐宋元明清2188 - 博客園 (cnblogs.com)
程序開啟DPI感知
如果你通過上面代碼,獲取的DPI依舊是1,那應該是沒有開啟DPI感知,請按如下添加就行了:
true/pm,意思是開啟屏幕DPI感知。具體的dpiAware參數介紹,可以了解下毅仔的博客:支持 Windows 10 最新 PerMonitorV2 特性的 WPF 多屏高 DPI 應用開發 - walterlv
參考文章:
- Windows 下的高 DPI 應用開發(UWP / WPF / Windows Forms / Win32) - walterlv
- Windows DPI Awareness for WPF - walterlv
關鍵字:監聽分辨率、分辨率變更