给定一个 hwnd,确定该窗口是否未被其他窗口隐藏(Z-Ordering)

Ele*_*ios 7 .net vb.net winapi window z-order

我有一个NativeWindow,我已经覆盖了WndProc处理WM_WINDOWPOSCHANGING消息的函数,当移动我的窗口时,我会将它粘在桌面上最近窗口的边框上。

我遇到的问题是我的窗口停靠在其他顶部窗口背景中的窗口,例如,如果我打开了一个资源管理器窗口并且在资源管理器窗口下方有其他窗口,那么我的窗口可以停靠到那个窗口在另一个窗口的下方,这是比资源管理器窗口低的 z 顺序级别。我想避免这种情况。

问题演示:

在此处输入图片说明

在上面的 GIF 中,有我的窗口 (Form1)、Visual Studio IDE 窗口、资源管理器窗口和一个名为“Hot Corners”的应用程序窗口。当我将“热角”窗口发送到背景时,我仍然可以将我的窗口粘附到“热角”边框上。我想避免这种情况。

请注意捕获的 Visual Studio输出窗口中的调试信息。


我在Wikipedia上阅读了 Z-Ordering ,我也在这里这里看到了这个例子和MSDN文档,但是,我仍然不明白如何实现这一点。

当我尝试将我的窗口粘附到其他窗口时,我需要确定该目标窗口是否在其他窗口下方,以避免出现我解释的问题。

我希望我能很好地解释问题并且清楚我需要什么,在我的窗口上方的 GIF 中,我的窗口不应该坚持“热角”窗口,因为它不可见,因为资源管理器窗口在上方。

这是相关的代码,该方法将我的窗口 (a Form)、我在窗口过程中过滤消息时获得的WINDOWPOS结构的句柄作为参数,最后一个参数是我的窗口到其他窗口的边界以坚持它。WM_WINDOWPOSCHANGINGWndProcthreshold

Protected Overridable Sub DockToNearestWindowBorder(ByVal window As IWin32Window,
                                                    ByVal windowPosHandle As IntPtr,
                                                    ByVal threshold As Integer)

    Dim windowPos As WindowPos =
        CType(Marshal.PtrToStructure(windowPosHandle, GetType(WindowPos)), WindowPos)

    If (windowPos.Y = 0) OrElse (windowPos.X = 0) Then
        ' Nothing to do.
        Exit Sub
    End If

    ' Enumerate all the visible windows in the current desktop.
    Dim desktopWindows As New List(Of IntPtr)()

    Dim callBack As EnumWindowsProc =
        Function(hwnd As IntPtr, lParam As IntPtr) As Boolean
            If (NativeMethods.IsWindowVisible(hwnd)) Then
                desktopWindows.Add(hwnd)
            End If
            Return True
        End Function

    If (NativeMethods.EnumDesktopWindows(IntPtr.Zero, callBack, IntPtr.Zero)) Then

        ' Window rectangles
        Dim srcRect As Rectangle
        Dim tgtRect As Rectangle

        NativeMethods.GetWindowRect(window.Handle, srcRect)

        For Each hwnd As IntPtr In desktopWindows

            ' This is just for testing purposes.
            Dim pid As Integer
            NativeMethods.GetWindowThreadProcessId(hwnd, pid)
            If Process.GetProcessById(pid).ProcessName.EndsWith("vshost") Then
                Continue For
            End If

            NativeMethods.GetWindowRect(hwnd, tgtRect)

            ' Right border of the source window
            If ((windowPos.X + srcRect.Width) <= (tgtRect.Left + threshold)) AndAlso
               ((windowPos.X + srcRect.Width) >= (tgtRect.Left - threshold)) AndAlso
               ((windowPos.Y) <= (tgtRect.Y + tgtRect.Height)) AndAlso
               ((windowPos.Y + srcRect.Height) >= (tgtRect.Y)) Then

                    windowPos.X = (tgtRect.Left - srcRect.Width)
                    Console.WriteLine("Window adhered to: " & Process.GetProcessById(pid).ProcessName)

               ' This is not working as expected.
               ' If hwnd = NativeMethods.GetWindow(window.Handle, GetWindowCmd.HwndNext) Then
               '     windowPos.X = (tgtRect.Left - srcRect.Width)
               '     Exit For
               ' End If

            End If

        Next hwnd

    End If

    ' Marshal it back.
    Marshal.StructureToPtr(structure:=windowPos, ptr:=windowPosHandle, fDeleteOld:=True)

End Sub
Run Code Online (Sandbox Code Playgroud)

请注意,在上面的代码中,我只显示了将我的窗口的右边框粘附到其他窗口的威胁,这是为了避免增加此问题的代码,以及丢失 P/Invokes 的相同原因。

cbr*_*nch 5

给定一个窗口句柄,您应该能够使用一些 Win32 函数来确定该窗口是完全或部分被其他窗口遮挡:

  1. 调用GetWindowDC()以检索HDC包含整个窗口的设备上下文句柄 ( ),包括非客户区域(例如,标题栏、菜单、边框等)

  2. GetClipBox()HDC上面的返回值调用。这将返回实际可见的最小边界矩形(即,在屏幕上且未被其他窗口覆盖)。此外,返回值可以告诉您窗口是否完全被遮挡 ( NULLREGION)。

  3. 不要忘记打电话ReleaseDC()

API 参考:https : //msdn.microsoft.com/en-us/library/dd144865%28v=vs.85%29.aspx

  • 这在 Vista 及更高版本中是否真的有效(假设您没有明确关闭窗口组合)?我相信启用窗口组合后,所有窗口都会独立绘制,而 DWM 会将事物合并在一起。 (2认同)