c# - System.Windows.Forms.Screen 在显示更改后未更新
问题描述
正如标题所说,System.Windows.Forms.Screen
在显示更改后没有正确更新其成员。我会具体:
我有两个显示器,我通过使用ChangeDisplaySettingsEx函数以编程方式交换主显示器。当我交换监视器时,SystemEvents.DisplaySettingsChanged
会引发事件,并且我还可以在 Windows 控制面板中看到更改生效(我的意思是启用或禁用使监视器成为主监视器的复选框),但是,在我的应用程序中,System.Windows.Forms.Screen.PrimaryScreen
对象保持与交换主监视器之前相同。
我从一个注意到相同的人那里来到这个线程,正如在Screen 类源代码中看到的那样,它应该在拦截WM_DISPLAYCHANGE消息时更新/更新屏幕对象,所以我尝试了这些调用:
Const HWND_BROADCAST As Integer = &HFFFF
Const WM_DISPLAYCHANGE As Integer = &H7e
PostMessage(New IntPtr(HWND_BROADCAST), WM_DISPLAYCHANGE, IntPtr.Zero, IntPtr.Zero)
SendMessage(New IntPtr(HWND_BROADCAST), WM_DISPLAYCHANGE, IntPtr.Zero, IntPtr.Zero)
但它似乎不起作用,或者我没有使用正确的 lParam 和 wParam 调用它,因为System.Windows.Forms.Screen.PrimaryScreen
在将消息发送到所有窗口后对象保持不变,并且SystemEvents.DisplaySettingsChanged
在这些调用之后引发事件。
在 C# 或 VB.NET 中,在显示更改后,我需要在交换它们之后检测 REAL 主监视器。
因为我Screen
在交换监视器而不是使用 Windows API 来枚举监视器时使用该类,所以有什么方法可以解决Screen
该类,可能是通过反射或调度WM_DISPLAYCHANGESystem.Windows.Forms.Screen.PrimaryScreen
,以在显示更改后获得一个新的/更新的对象? .
更新
我还在MSDN 论坛上找到了另一个关于这个 ...bug?的证据证明。请注意,从 2012 年开始的线程并没有提供解决方案......
根据评论中的要求,我分享了(部分、相关的)代码。
''' <summary>
''' Sets the specified <see cref="Screen"/> as primary.
''' <para></para>
''' This functionality will work only with a setup of two monitors.
''' This functionality does not support three or more monitors.
''' </summary>
'''
''' <param name="scr">
''' The new primary <see cref="Screen"/>.
''' </param>
<DebuggerStepThrough>
Public Shared Sub SetPrimaryScreen(scr As Screen)
If (Screen.AllScreens.Count = 1) Then
Throw New InvalidOperationException("There is only one available monitor.")
End If
If (Screen.AllScreens.Count > 2) Then
Throw New NotSupportedException("Three or more monitor setups are not supported.")
End If
If scr.Equals(Screen.PrimaryScreen) Then
Exit Sub
End If
'' DIRTY FIX:
' If scr.IsRealPrimaryScreen() Then
' Exit Sub
' End If
Dim scrPrimary As Screen = (Screen.AllScreens().Where(Function(s As Screen) s.Primary)).Single()
'' DIRTY FIX:
' Dim scrPrimary As Screen = (Screen.AllScreens().Where(Function(s As Screen) s.IsRealPrimaryScreen())).Single()
' Set current primary device position.
Dim setPositionPrimary As New DevMode(initializeSize:=True) With {.Fields = DeviceModeFields.Position}
setPositionPrimary.Mode.DisplayDevMode.Position.X = scr.Bounds.Width
setPositionPrimary.Mode.DisplayDevMode.Position.Y = 0
NativeMethods.ChangeDisplaySettingsEx(scrPrimary.DeviceName, setPositionPrimary, IntPtr.Zero, ChangeDisplaySettingsFlags.UpdateRegistry Or ChangeDisplaySettingsFlags.NoReset, IntPtr.Zero)
' Set secondary device position, and set it as the new primary device.
Dim setPositionSecondary As New DevMode(initializeSize:=True) With {.Fields = DeviceModeFields.Position}
setPositionSecondary.Mode.DisplayDevMode.Position.X = 0
setPositionSecondary.Mode.DisplayDevMode.Position.Y = 0
NativeMethods.ChangeDisplaySettingsEx(scr.DeviceName, setPositionSecondary, IntPtr.Zero, ChangeDisplaySettingsFlags.SetPrimary Or ChangeDisplaySettingsFlags.UpdateRegistry Or ChangeDisplaySettingsFlags.Reset, IntPtr.Zero)
' Update current primary device.
Dim currentPrimary As New DevMode(initializeSize:=True)
NativeMethods.ChangeDisplaySettingsEx(scrPrimary.DeviceName, currentPrimary, IntPtr.Zero, ChangeDisplaySettingsFlags.UpdateRegistry Or ChangeDisplaySettingsFlags.NoReset, IntPtr.Zero)
' Set secondary device as current primary device.
Dim newPrimary As New DevMode(initializeSize:=True)
NativeMethods.ChangeDisplaySettingsEx(scr.DeviceName, newPrimary, IntPtr.Zero, ChangeDisplaySettingsFlags.SetPrimary Or ChangeDisplaySettingsFlags.UpdateRegistry Or ChangeDisplaySettingsFlags.NoReset, IntPtr.Zero)
End Sub
交换监视器的使用示例(在两个监视器设置中):
Dim secondaryMonitor As Screen = (Screen.AllScreens().Where(Function(s As Screen) Not s.Primary)).Single()
SetPrimaryScreen(secondaryScreen)
不能在同一个正在运行的应用程序中调用两次,因为Screen.PrimaryScreen
在第一次更改后不会更新,所以在随后的方法调用中SetPrimaryScreen
,它会处理错误的 false/non-updated Screen.PrimaryScreen
,我的意思是因为Screen.PrimaryScreen
始终保持指向同一个设备的同一个对象即使不再是主要设备。
请注意,如果尝试将 设置为主屏幕,则该SetPrimaryScreen
方法具有终止执行的错误处理(因为它已经应该是主屏幕......但它不是,因为对象未正确刷新)。Screen.PrimaryScreen
Screen.PrimaryScreen
请注意,我正在寻找一个简单、自然、最佳的解决方案,但我已经设法通过为具有我将使用的名称而不是默认属性的Screen
类创建一个扩展方法,以一种肮脏的方式来解决所有这些问题。IsRealPrimaryScreen
Screen.Primary
<HideModuleName>
Public Module ScreenExtensions
''' <summary>
''' Determine whether the source <see cref="Screen"/> is the primary device.
''' <para></para>
''' You must always call <see cref="ScreenExtensions.IsRealPrimaryScreen"/> function
''' instead of <see cref="Screen.Primary"/>,
''' which does not properly update the class members after a display change:
''' <para></para>
''' <see href="https://stackoverflow.com/questions/67061729/system-windows-forms-screen-is-not-updating-after-a-display-change"/>
''' <para></para>
''' <see href="https://stackoverflow.com/questions/7901247/screen-allscreens-bug-and-posting-a-wm-displaychange-to-a-single-winform-applic"/>
''' </summary>
''' <example> This is a code example.
''' <code language="VB.NET">
''' Dim primaryMonitor As Screen = (Screen.AllScreens().Where(Function(scr As Screen) scr.IsRealPrimaryScreen)).Single()
''' Dim secondaryMonitor As Screen = (Screen.AllScreens().Where(Function(s As Screen) Not s.IsRealPrimaryScreen)).First()
''' Console.WriteLine(primaryMonitor.DeviceName)
''' Console.WriteLine(secondaryMonitor.DeviceName)
''' </code>
''' </example>
''' <param name="scr">
''' The source <see cref="Screen"/>.
''' </param>
''' <returns>
''' <see langword="True"/> if the source <see cref="Screen"/> is the primary device;
''' <see langword="False"/> otherwise.
''' </returns>
<DebuggerStepThrough>
<Extension>
<EditorBrowsable(EditorBrowsableState.Always)>
Public Function IsRealPrimaryScreen(scr As Screen) As Boolean
Dim result As Boolean
Dim callback As Delegates.EnumMonitorProc =
Function(hMonitor As System.IntPtr, hdc As System.IntPtr, ByRef rc As NativeRectangle, data As System.IntPtr) As Boolean
Dim mi As New MonitorInfoEx(initializeSize:=True)
NativeMethods.GetMonitorInfo(hMonitor, mi)
If (mi.DeviceName.Equals(scr.DeviceName, StringComparison.OrdinalIgnoreCase)) Then
result = mi.Flags.HasFlag(MonitorInfoFlags.PrimaryDevice)
Return False
End If
Return True
End Function
NativeMethods.EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, callback, IntPtr.Zero)
Return result
End Function
End Module
我将使用一种GetRealPrimaryScreen
方法来获取Screen
主设备的实例,而不是使用Screen.PrimaryScreen
:
''' <summary>
''' Gets a <see cref="Screen"/> object that represents the primary device.
''' <para></para>
''' You must always call <see cref="DeviceUtil.GetRealPrimaryScreen"/> function
''' instead of <see cref="Screen.PrimaryScreen"/>,
''' which does not properly update the class members after a display change:
''' <para></para>
''' <see href="https://stackoverflow.com/questions/67061729/system-windows-forms-screen-is-not-updating-after-a-display-change"/>
''' <para></para>
''' <see href="https://stackoverflow.com/questions/7901247/screen-allscreens-bug-and-posting-a-wm-displaychange-to-a-single-winform-applic"/>
''' </summary>
''' <example> This is a code example.
''' <code language="VB.NET">
''' Dim primary As Screen = GetRealPrimaryScreen()
''' Console.WriteLine(primary.DeviceName)
''' </code>
''' </example>
''' <returns>
''' The resulting <see cref="Screen"/> object.
''' </returns>
<DebuggerStepThrough>
Public Shared Function GetRealPrimaryScreen() As Screen
Dim result As Screen = Nothing
Dim callback As Delegates.EnumMonitorProc =
Function(hMonitor As IntPtr, hdc As IntPtr, ByRef rc As NativeRectangle, data As IntPtr) As Boolean
Dim mi As New MonitorInfoEx(initializeSize:=True)
NativeMethods.GetMonitorInfo(hMonitor, mi)
If mi.Flags.HasFlag(MonitorInfoFlags.PrimaryDevice) Then
result = DeviceUtil.GetScreenFromDeviceName(mi.DeviceName)
Return False
End If
Return True
End Function
NativeMethods.EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero, callback, IntPtr.Zero)
Return result
End Function
解决方案
推荐阅读
- javascript - 添加不重新定位页面的关闭功能
- java - Java 复制 ArrayList 值未参考
- laravel - Connect GIATA hotel mapping with postman
- asp.net - 如何使用 C# 在引导模式中添加/获取标签和文本框对
- linux - Where can I find the definition of the function PEM_read_bio_X509()?
- c# - Group by 子句不允许在类级别上分组
- makefile - makefile 模式匹配是否可以同时包含前缀和后缀作为变量?
- unity3d - 根据 Unity 中的另一个纹理更改纹理的颜色?
- python - 在python中,哪些二元运算符可以同时具有NoneType的操作数?
- php - 在这种情况下如何使用 Laravel 存储多个数据?