Screen.AllScreens 错误并将 WM_DISPLAYCHANGE 发布到单个 WinForm 应用程序
首先,对这么长的帖子表示抱歉。
关于如何限制 WM_DISPLAYCHANGE 消息的发布范围有什么建议吗?
场景:
Screen.AllScreens
返回在客户端上检测到的所有监视器的坐标和分辨率数组。如果在工作站锁定时启动应用程序(在夜间应用程序重新启动期间),Screen.AllScreens
仅返回一个详细说明单个屏幕的元素,其中所有多个显示器的尺寸为一个。
随后,在这种情况下,当用户解锁工作站并开始使用应用程序时,由于 Screen.AllScreens
的原因,正在使用的 Infragistics 控件 (UltraWinDock) 不允许将浮动窗口拖动到主屏幕之外。 code> 属性不返回系统的真实监视器配置。 Infragistics 控件实际上查看的是 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
。 当工作站解锁时,会收到此事件并且满足条件,因此我使用
[DllImport("user32.dll")]
public static extern bool PostMessage(IntPtr hWnd, uint wMsg, UIntPtr wParam, IntPtr lParam);
带有以下参数的 P/Invoke 方法发布一条消息:
PostMessage(HWND_BROADCAST, WM_DISPLAYCHANGE,UIntPtr.Zero,IntPtr.Zero)
这非常有效,并且达到了预期的结果! Screen.AllScreens
已重置,Infragistics 控制功能正常。
在我看来,这就像一个不起眼的错误,当应用程序在锁定的工作站上启动然后解锁时,Screen.AllScreens
不会重新评估自身。
我承认这是一个罕见的问题,但仍然是一个问题。
对于 WM_DISPLAYCHANGE 消息,lParam 和wParam 描述为:
wParam
- 显示的新图像深度,以每像素位数为单位。
lParam
低位字指定屏幕的水平分辨率。
高位字指定屏幕的垂直分辨率。
我为这些参数发送 nulls IntPtr.Zero
,因为我不知道发送消息时的实际值是什么。
我在这里担心的是,我正在使用空参数在整个系统中广播 WM_DISPLAYCHANGE
消息,并且可能有正在运行的进程消耗 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 的源代码,并查明了代码中出现问题的位置,并考虑重新编译控件以集成修复程序,但决定不这样做。我已向 Infragistics 通报了该问题,但迫不及待地等待修复,并且并不特别将其视为 Infragistics 问题,因为是
Screen.AllScreens
导致了该问题。 - 应用程序需要在夜间重新启动,并且不能更改为等到用户早上登录。
- 我创建了一个测试应用程序,它锁定用户的工作站,重新启动自身(应用程序,而不是工作站),并在应用程序锁定时评估 Screen.AllScreens 属性,然后在发送后评估另一个快照
WM_DISPLAYCHANGE
方法。我想添加屏幕截图,但作为新的 StackOverflow 用户,我不允许这样做!
Firstly, sorry about the long post.
Any suggestions as to how I can limit the publish scope of the WM_DISPLAYCHANGE message?
Scenario:
Screen.AllScreens
returns an array of coordinates and resolutions for all monitors detected on a client. If an application is started when the workstation is locked (during an overnight application restart), Screen.AllScreens
returns only one element detailing a single screen with the dimensions of all the multiple monitors as one.
Subsequently, in this scenario, when a user unlocks the workstation, and starts using the application, an Infragistics control (UltraWinDock) that is being used does not allow dragging floating windows outside the primary screen due to the Screen.AllScreens
property not returning the true monitor configuration for the system. The Infragistics control actually looks at Screen.PrimaryScreen.Bounds
, but the Screen.PrimaryScreen
property in turn calls the cached Screen.AllScreens
array, which returns an enormous primary screen!
When the application is started normally (with the workstation unlocked), the control functions correctly.
The only means by which I can see that Screen.AllScreens
is reset, and can be refreshed is via the SystemEvents.DisplayChanging
event being raised,
at which point the internal field is set to null. (Screen.AllScreens
hooks into this event.) Screen.AllScreens
will then repopulate the next time it is called.
From what I can determine, the SystemEvents.DisplayChanging
event can be raised via a WM_DISPLAYCHANGE
WMI message.
The means by which I have managed a workaround is by calling:
[DllImport("user32.dll")]
public static extern int GetSystemMetrics(int nIndex);
with a parameter of SM_CMONITORS which represents the number of displays on the system. This appears to always return the actual number of monitors present, regardless of whether the workstation is locked or not.
I then evaluate whether the length of Screen.AllScreens
array is less than the result of GetSystemMetrics(SM_CMONITORS)
and if it is, I hook into theSystemEvents.SessionSwitch
static event and check the SessionSwitchEventArgs.Reason
property, for a value of SessionUnlock
.
When the workstation is unlocked, this event is received and the condition satisfied, so I post a message using P/Invoke method
[DllImport("user32.dll")]
public static extern bool PostMessage(IntPtr hWnd, uint wMsg, UIntPtr wParam, IntPtr lParam);
with the following args:
PostMessage(HWND_BROADCAST, WM_DISPLAYCHANGE,UIntPtr.Zero,IntPtr.Zero)
This works very nicely and the desired result is acheived! Screen.AllScreens
is reset and the Infragistics control functions correctly.
It looks to me like an obscure bug with Screen.AllScreens
not re-evalutating itself when an application is started on a locked workstation, and then unlocked.
A rare issue, I acknowldege, but an issue nonetheless.
For the WM_DISPLAYCHANGE message, lParam and wParam are described as:
wParam
- The new image depth of the display, in bits per pixel.
lParam
The low-order word specifies the horizontal resolution of the screen.
The high-order word specifies the vertical resolution of the screen.
I am sending in nulls IntPtr.Zero
for these arguments, as I don't know what the actual values are at the time of sending the message.
My concern here is that I am broadcasting the WM_DISPLAYCHANGE
message across the entire system with null arguments, and that there may be processes running that consume the WM_DISPLAYMESSAGE
and utilise the arguments. I would like to hope that if null arguments are send, any consumers would ignore the arguments, but this is a very dangerous assumption to make.
Is there a way to send or post the message to only the application in question, and remove the risk of affecting other processes?
I have tried the following to no avail:
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)
NOTES:
- I don't have a great deal of experience with P/Invoke or WMI.
- Target Framework is .Net 3.5.
- I haven't seen any side effects to broadcasting a PostMessage of the
WM_DISPLAYCHANGE
method as yet. - I have already downloaded the source code for Infragistics, and pinpointed where the issue occurs in their code, and considered re-compiling the control to integrate a fix, but decided against this. I have informed Infragistics of the issue, but cannot wait for a fix, and don't especially see it as an Infragistics issue, as it is
Screen.AllScreens
causing the issue. - The application restart overnight is necessary, and cannot be changed to wait until a user logging on in the morning.
- I have created a test application that locks a user's workstation, restarts itself (the application, not the workstation), and evaluates the
Screen.AllScreens
property when the application is locked, and then another snapshot after I send theWM_DISPLAYCHANGE
method. I'd like to add a screenshot, but am not allowed to as am a new StackOverflow user!!!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
在这里给出答案可能已经晚了,但这里的另一个选择是仅 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 消息。
Probably late to give an answer here, but another option here would be to just P/Invoke the same call that AllScreens uses. Then you would get the actual value instead of the cached value that AllScreens is keeping. Take a look at: http://www.pinvoke.net/default.aspx/user32/EnumDisplayMonitors.html
If you are in WPF, could do something like what is posted here: 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
Basically, find the SystemResourceNotifyWindow and post a WM_DISPLAYCHANGE message to it.
您需要获取应用程序的 hwnd,而不是:
使用间谍++找出应用程序的窗口类,然后使用 FindWindow() 来获取它的hwnd。
但我可能在这里遗漏了一些东西——你似乎有足够的能力认识到这一点,所以也许我误解了,那是你重新编译的 Infragistics 应用程序中的代码?
You need to get the hwnd of the app, not:
Find out the window class of the app using spy++, then use FindWindow() to get its hwnd.
But there is probably something I'm missing here -- you seem plenty competent enough to realize that, so perhaps I am misunderstanding and that is code you have inside your recompiled Infragistics app?