Screen.AllScreens 错误并将 WM_DISPLAYCHANGE 发布到单个 WinForm 应用程序

发布于 2024-12-11 17:22:45 字数 3515 浏览 0 评论 0原文

首先,对这么长的帖子表示抱歉。

关于如何限制 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 the
SystemEvents.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 the WM_DISPLAYCHANGE method. I'd like to add a screenshot, but am not allowed to as am a new StackOverflow user!!!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

风铃鹿 2024-12-18 17:22:45

在这里给出答案可能已经晚了,但这里的另一个选择是仅 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.

阿楠 2024-12-18 17:22:45

我只是(!)需要知道如何使用 P/Invoke 将 PostMessage 发送到特定应用程序

您需要获取应用程序的 hwnd,而不是:

SendMessage(this.Handle...

使用间谍++找出应用程序的窗口类,然后使用 FindWindow() 来获取它的hwnd。

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

What I simply(!) need to know how to use P/Invoke to PostMessage to a specific application

You need to get the hwnd of the app, not:

SendMessage(this.Handle...

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?

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文