屏幕上.所有屏幕错误和张贴WM_DISPLAYCHANGE,到一个单一的WinForm应用程序

本文关键字:屏幕 一个 应用程序 WinForm 单一 错误 WM 张贴 DISPLAYCHANGE | 更新日期: 2023-09-27 18:12:39

首先,很抱歉发了这么长的帖子。

关于如何限制WM_DISPLAYCHANGE消息的发布范围的任何建议?

场景:

Screen.AllScreens返回客户端检测到的所有监视器的坐标和分辨率数组。如果应用程序在工作站被锁定时启动(在一夜之间重新启动应用程序期间),Screen.AllScreens只返回一个元素,该元素详细描述了单个屏幕,其中所有多个监视器的尺寸为一个。

随后,在这种情况下,当用户解锁工作站并开始使用该应用程序时,正在使用的inffragistics控件(UltraWinDock)不允许将浮动窗口拖到主屏幕之外,因为Screen.AllScreens属性不返回系统的真正监视器配置。inffragistics控件实际上查看Screen.PrimaryScreen.Bounds,但Screen.PrimaryScreen属性反过来调用缓存的Screen.AllScreens数组,它返回一个巨大的主屏幕!

当应用程序正常启动时(工作站未锁定),控制功能正常。

我可以看到Screen.AllScreens被重置,并且可以刷新的唯一方法是通过SystemEvents.DisplayChanging事件被引发,此时,内部字段被设置为空。(Screen.AllScreens钩入这个事件。)Screen.AllScreens将在下一次调用时重新填充。

根据我所能确定的,SystemEvents.DisplayChanging事件可以通过WM_DISPLAYCHANGE WMI消息引发。

我使用的方法是调用:

[DllImport("user32.dll")]
public static extern int GetSystemMetrics(int nIndex);

带有参数SM_CMONITORS,该参数表示系统上的显示器数量。这似乎总是返回当前监视器的实际数量,而不管工作站是否被锁定。

然后评估Screen.AllScreens数组的长度是否小于GetSystemMetrics(SM_CMONITORS)的结果,如果是,则钩入SystemEvents.SessionSwitch静态事件和检查SessionSwitchEventArgs.Reason属性,为SessionUnlock的值。当工作站解锁时,接收到此事件并且条件满足,因此我使用P/Invoke方法

发布消息
[DllImport("user32.dll")]
public static extern bool PostMessage(IntPtr hWnd, uint wMsg, UIntPtr wParam, IntPtr lParam);

与以下参数:

PostMessage(HWND_BROADCAST, WM_DISPLAYCHANGE,UIntPtr.Zero,IntPtr.Zero)

这工作得很好,并实现了预期的结果! Screen.AllScreens复位,inffragistics控制功能正常。

在我看来,这就像一个模糊的bug,当一个应用程序在一个锁定的工作站上启动时,Screen.AllScreens没有重新评估自己,然后解锁。

我承认这是一个罕见的问题,但仍然是一个问题。

对于WM_DISPLAYCHANGE消息,lParam和wParam被描述为:

按钮

  • 显示的新图像深度,单位为每像素位。

lParam

  • 低阶字指定屏幕的水平分辨率

  • 高阶字指定屏幕的垂直分辨率

对于这些参数,我发送的是null IntPtr.Zero,因为我不知道发送消息时的实际值是什么。

我这里关心的是,我在整个系统中广播WM_DISPLAYCHANGE消息,并使用null参数,并且可能有正在运行的进程消耗WM_DISPLAYMESSAGE并利用这些参数。我希望如果发送空参数,任何消费者都会忽略这些参数,但这是一个非常危险的假设。

是否有一种方法可以仅向有问题的应用程序发送或发布消息,并消除影响其他进程的风险?

我尝试了以下方法,但没有效果:

PostMessage(IntPtr.Zero, WM_DISPLAYCHANGE, UIntPtr.Zero, IntPtr.Zero)
PostMessage(this.Handle, WM_DISPLAYCHANGE, UIntPtr.Zero, IntPtr.Zero)
PostThreadMessage(AppDomain.GetCurrentThreadId(), WM_DISPLAYCHANGE, UIntPtr.Zero, IntPtr.Zero)
SendMessage(this.Handle, WM_DISPLAYCHANGE, UIntPtr.Zero, IntPtr.Zero)

指出:

  • 我对P/Invoke或WMI没有很多经验。
  • 目标框架是。net 3.5。
  • 我还没有看到任何副作用广播WM_DISPLAYCHANGE方法的PostMessage。
  • 我已经下载了Infragistics的源代码,并确定了问题发生在他们的代码中,并考虑重新编译控件以集成修复,但决定反对这一点。我已经通知了inffragistics这个问题,但不能等待修复,也不认为这是inffragistics的问题,因为是Screen.AllScreens造成的。
  • 一夜之间重启应用程序是必要的,不能更改为等到用户早上登录。
  • 我创建了一个测试应用程序,它锁定用户的工作站,重新启动自己(应用程序,而不是工作站),并在应用程序被锁定时评估Screen.AllScreens属性,然后在我发送WM_DISPLAYCHANGE方法后评估另一个快照。我想添加一个屏幕截图,但我不允许作为一个新的StackOverflow用户!!

屏幕上.所有屏幕错误和张贴WM_DISPLAYCHANGE,到一个单一的WinForm应用程序

在这里给出答案可能太晚了,但这里的另一种选择是p/Invoke与AllScreens使用的相同的调用。然后你会得到实际值,而不是AllScreens保存的缓存值。看看:http://www.pinvoke.net/default.aspx/user32/EnumDisplayMonitors.html

如果你使用WPF,可以做一些类似于这里发布的东西:http://social.msdn.microsoft.com/Forums/vstudio/en-US/41e0bf65-2e96-4d0f-98aa-2c0cf31aa493/wpf-application-controls-does-not-work-when-an-external-monitor-is-connected?forum=wpf

基本上,找到SystemResourceNotifyWindow并向它发布一个WM_DISPLAYCHANGE消息。

我只是(!)需要知道如何使用p/Invoke来PostMessage到特定的应用程序

你需要得到应用的hwnd,而不是:

SendMessage(this.Handle...

使用spy++找到应用程序的窗口类,然后使用FindWindow()获得它的hwnd。

但我可能在这里遗漏了一些东西——你似乎有足够的能力意识到这一点,所以也许我是误解,这是你重新编译的Infragistics应用程序中的代码?