新知引入
在许多壁纸应用程序中,它们大多都支持多显示器的分布,而对于Windows的多显示器而言,它的窗口不可能是多边形

整个大窗口的布局其实是这2块显示屏的外边缘组成的大矩形,即

而在.net里面,有一个专门的类去管理显示器的虚拟区域
vbnet
Dim s As Screen() = Screen.AllScreens
这一个方法可以给s附上所有显示器的数据,包括系统分配的设备名称、各个显示器的虚拟位置,其中,非常值得注意的一点是,虚拟桌面的坐标原点为主显示器(显示器1)的坐标原点(左上角)
因此,上图的红色大矩形数据为
转换为Rectangle为:-2048 -442 3968 1536
然后,事情就变得非常好办了,由于在显示器内,窗口的坐标是按照显示器的坐标原点来计算的,但是,WorkerW作为桌面背景层,它的区域就是上图的红色大矩形,并且中的并且,如果你想要设置一个窗口嵌入WorkerW,那么你就需要将不同显示器的坐标转换到WorkerW作为Parent的坐标。
说的有点绕了,简言之就是那个大矩形就是WorkerW的主窗体,然后你要嵌入窗口并且想要让壁纸准确出现在不同显示器上就要换算对应显示器区域在WorkerW上的哪一个位置
我们来使用代码来解释
vbnet
BackPalyer.Show()
Threading.Thread.Sleep(100)
Dim s As Screen() = Screen.AllScreens
Dim Wrect As RECT
GetWindowRect(&H60950, Wrect)
Dim ToWorkerWRect As New List(Of Rectangle)
For Each i In s
Dim p As New Point(i.Bounds.X - Wrect.Left, i.Bounds.Y - Wrect.Top)
ToWorkerWRect.Add(New Rectangle(p.X, p.Y, i.Bounds.Width, i.Bounds.Height))
Next
SetParent(BackPalyer.Handle, &H60950)
BackPalyer.DesktopBounds = ToWorkerWRect(0)
解读一下:
延时100ms可以不写,s()是当前所有显示器的集合,Wrect是WorkerW的窗口rect,&H60950是WorkerW的句柄(注意,此处为了方便理解,实则每一次重启explorer都会变!先不要着急复制代码,下面会给出方法!)
在循环过程中,p的作用是换算的显示器在WorkerW上的位置,循环添加到列表
最后使用SetParent设置桌面背景,然后按照显示器bound设置壁纸窗口的DesktopBounds
如何获取壁纸层的句柄?
参考文章:桌面壁纸层嵌入窗口(wallpaper engine核心)原理讲解
或者改一下代码
vbnet
Public Sub GetWorkerWHandle(ByVal videoPtr As IntPtr) As IntPtr
Dim windows_version As Double = Environment.OSVersion.Version.Major
progmanPtr = FindWindow("Progman", Nothing)
If progmanPtr = IntPtr.Zero Then
MessageBox.Show("当前系统可能不支持运行本程序或者卡死")
Return
Else
SendMessageTimeout(progmanPtr, &H52C, IntPtr.Zero, IntPtr.Zero, 0, &H3E8, sendMessageBack)
EnumWindows(Function(hwnd, param)
If Win32.FindWindowEx(hwnd, IntPtr.Zero, "SHELLDLL_DefView", Nothing) <> IntPtr.Zero Then
workWPtr = Win32.FindWindowEx(IntPtr.Zero, hwnd, "WorkerW", Nothing)
If windows_version < 6.2 Then
Win32.ShowWindow(workWPtr, 0)
End If
End If
Return True
End Function, 0)
If windows_version < 6.2 Then
return progmanPtr
Else
return workWPtr
End If
End If
End Sub
经过测试,至此窗口可以被正确的嵌入桌面背景层了