WinUI 3检测显示器插拔无通知,求问题排查与解决方法
WinUI 3中检测显示器插拔状态失败的问题分析与解决方案
现有代码的问题
你的代码无法收到通知主要有以下几个原因:
1. 未指定显示器设备类GUID
RegisterDeviceNotification调用时使用了Guid.Empty,系统无法精准识别显示器设备的插拔事件。需要替换为显示器设备接口的专用GUID:GUID_DEVINTERFACE_MONITOR,其值为{6BDD1FC6-810F-11D0-BEC7-08002BE2092F}。
2. WinUI 3窗口消息未转发到自定义WndProc
你定义的WndProc方法并没有被系统调用,WinUI 3的窗口消息处理机制和传统Win32不同,需要手动拦截窗口消息并转发到你的处理逻辑中。
3. 窗口句柄获取时机可能过早
在MainWindow构造函数中获取_hwnd可能时机不对,此时窗口尚未完全初始化,导致句柄无效。建议在Window.Activated事件触发后再获取窗口句柄并启动监听。
修复后的代码示例
1. 修改ScreenMonitor类,指定正确的GUID并处理消息转发
public class ScreenMonitor { private const int WM_DEVICECHANGE = 0x0219; public const int DBT_DEVICEARRIVAL = 0x8000; public const int DBT_DEVICEREMOVECOMPLETE = 0x8004; private const int DBT_DEVTYP_DEVICEINTERFACE = 0x00000005; // 显示器设备接口GUID private static readonly Guid GUID_DEVINTERFACE_MONITOR = new Guid("6BDD1FC6-810F-11D0-BEC7-08002BE2092F"); [StructLayout(LayoutKind.Sequential)] public struct DEV_BROADCAST_DEVICEINTERFACE { public int dbcc_size; public int dbcc_devicetype; public int dbcc_reserved; public Guid dbcc_classguid; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 255)] public string dbcc_name; } [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern IntPtr RegisterDeviceNotification(IntPtr hRecipient, ref DEV_BROADCAST_DEVICEINTERFACE NotificationFilter, int Flags); [DllImport("user32.dll")] public static extern bool UnregisterDeviceNotification(IntPtr hHandle); // 用于替换窗口过程的委托 private delegate IntPtr WndProcDelegate(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam); private WndProcDelegate _originalWndProc; private IntPtr _hwnd; private IntPtr notificationHandle; public void StartMonitoring(IntPtr hwnd) { _hwnd = hwnd; DEV_BROADCAST_DEVICEINTERFACE dbdi = new DEV_BROADCAST_DEVICEINTERFACE(); dbdi.dbcc_size = Marshal.SizeOf(dbdi); dbdi.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE; dbdi.dbcc_reserved = 0; // 指定显示器设备GUID dbdi.dbcc_classguid = GUID_DEVINTERFACE_MONITOR; notificationHandle = RegisterDeviceNotification(hwnd, ref dbdi, 0); if (notificationHandle == IntPtr.Zero) { throw new Exception($"Failed to register: {Marshal.GetLastWin32Error()}"); } // 替换窗口过程,拦截消息 _originalWndProc = (WndProcDelegate)Marshal.GetDelegateForFunctionPointer( GetWindowLongPtr(hwnd, GWLP_WNDPROC), typeof(WndProcDelegate)); SetWindowLongPtr(hwnd, GWLP_WNDPROC, Marshal.GetFunctionPointerForDelegate(new WndProcDelegate(CustomWndProc))); } public void StopMonitoring() { if (notificationHandle != IntPtr.Zero) { UnregisterDeviceNotification(notificationHandle); notificationHandle = IntPtr.Zero; } // 恢复原窗口过程 if (_hwnd != IntPtr.Zero && _originalWndProc != null) { SetWindowLongPtr(_hwnd, GWLP_WNDPROC, Marshal.GetFunctionPointerForDelegate(_originalWndProc)); _originalWndProc = null; _hwnd = IntPtr.Zero; } } public event EventHandler<DeviceChangeEventArgs> DeviceChange; protected virtual void OnDeviceChange(DeviceChangeEventArgs e) { DeviceChange?.Invoke(this, e); } private IntPtr CustomWndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) { if (msg == WM_DEVICECHANGE) { int wParamInt = wParam.ToInt32(); if (wParamInt == DBT_DEVICEARRIVAL || wParamInt == DBT_DEVICEREMOVECOMPLETE) { // 验证设备类型是否为显示器 var dbdi = Marshal.PtrToStructure<DEV_BROADCAST_DEVICEINTERFACE>(lParam); if (dbdi.dbcc_classguid == GUID_DEVINTERFACE_MONITOR) { OnDeviceChange(new DeviceChangeEventArgs(wParamInt)); } } } // 调用原窗口过程 return _originalWndProc(hWnd, msg, wParam, lParam); } // 补充P/Invoke函数 private const int GWLP_WNDPROC = -4; [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern IntPtr GetWindowLongPtr(IntPtr hWnd, int nIndex); [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr dwNewLong); } public class DeviceChangeEventArgs : EventArgs { public int EventType { get; } public DeviceChangeEventArgs(int eventType) { EventType = eventType; } }
2. 修改MainWindow中的初始化逻辑
private ScreenMonitor _screenMonitor; public MainWindow() { this.InitializeComponent(); this.Activated += MainWindow_Activated; this.Closed += MainWindow_Closed; } private void MainWindow_Activated(object sender, WindowActivatedEventArgs args) { // 确保只初始化一次 if (_screenMonitor == null) { _screenMonitor = new ScreenMonitor(); _screenMonitor.DeviceChange += (s, e) => { if (e.EventType == ScreenMonitor.DBT_DEVICEARRIVAL) { Log.Logger.Debug("Screen plugged in"); } else if (e.EventType == ScreenMonitor.DBT_DEVICEREMOVECOMPLETE) { Log.Logger.Debug("Screen removed"); } }; // 获取窗口句柄 var hwnd = WinRT.Interop.WindowNative.GetWindowHandle(this); _screenMonitor.StartMonitoring(hwnd); } } private void MainWindow_Closed(object sender, WindowEventArgs args) { _screenMonitor?.StopMonitoring(); }
替代方案:使用WinUI原生API
如果不想处理Win32消息,可以使用WinUI提供的DisplayInformation类,通过监听DisplayContentsChanged事件来检测显示状态变化,再结合屏幕数量判断是否为插拔事件:
private int _previousScreenCount; public MainWindow() { this.InitializeComponent(); this.Activated += MainWindow_Activated; } private void MainWindow_Activated(object sender, WindowActivatedEventArgs args) { _previousScreenCount = System.Windows.Forms.Screen.AllScreens.Length; DisplayInformation.Current.DisplayContentsChanged += Current_DisplayContentsChanged; } private void Current_DisplayContentsChanged(DisplayInformation sender, object args) { int currentCount = System.Windows.Forms.Screen.AllScreens.Length; if (currentCount > _previousScreenCount) { Log.Logger.Debug("Screen plugged in"); } else if (currentCount < _previousScreenCount) { Log.Logger.Debug("Screen removed"); } _previousScreenCount = currentCount; }
注意:使用
System.Windows.Forms.Screen需要引用System.Windows.FormsNuGet包,或者改用Win32 API获取屏幕数量。
内容的提问来源于stack exchange,提问作者phiwhynot
相关产品推荐
相关产品推荐

