给定一个 hwnd,确定 window 是否未被其他 windows 隐藏(Z 排序)

Given a hwnd, determine if that window is not hidden by other windows (Z-Ordering)

我有一个 NativeWindow,我已经覆盖了 WndProc 函数来处理 WM_WINDOWPOSCHANGING 消息,以便移动我的 [=72] =] 我会把它贴在桌面上最近的 window 的边框上。

我遇到的问题是我的 window 正在对接到 windows,它们在其他 top-windows 的背景中,例如,如果我有一个 explorer [=72] =] open and other window below the explorer window, 然后我的 window 可以停靠到另一个 window 下面的 window, 这是一个z 顺序级别低于资源管理器 window。我想避免这种情况。

问题演示:

在上面的GIF中,有我的window(Form1),Visual StudioIDEwindow,一个探险家window,还有一个window 的名称为 "Hot Corners" 的应用程序。当我将 "Hot Corners" window 发送到后台时,我仍然可以将 window 粘贴到 "Hot corners" 边框。我想避免这种情况。

注意 Visual Studio 的 Output window 中的调试信息。


我在 Wikipedia, and I also seen this example and the MSDN docs here and here 上阅读了有关 Z 顺序的信息,但是,我仍然不明白如何实现它。

当我尝试将我的 window 坚持到其他 window 时,我需要确定该目标 window 是否低于其他 windows 或不是,以避免我解释的问题。

我希望我能很好地解释问题并清楚我需要什么,在上面的 GIF 中我的 window 不应该遵守 "Hot Corners" window 因为不可见,因为探险家window在上面

这是相关代码,该方法将我的 window (a Form) 作为参数,WINDOWPOS 的句柄我在 window 的 WndProc 过程中过滤 WM_WINDOWPOSCHANGING 消息时得到的结构,最后一个参数 threshold 是之间所需的最小值 space我的 window 对其他 windows 的限制要遵守。

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

请注意,在上面的代码中,我只显示了将我的 window 的右边框粘附到其他 windows 的威胁,这是为了避免为此增加代码问题,和缺少P/Invokes.

的原因相同

使用 GetWindowPlacement 避免检查最小化 window:

Public Class YourUtilityClass

    Private Declare Function GetWindowPlacement Lib "user32" (ByVal hwnd As IntPtr, ByRef lpwndpl As WINDOWPLACEMENT) As Integer

    Private Structure WINDOWPLACEMENT
        Public Length As Integer
        Public flags As Integer
        Public showCmd As Integer
        Public ptMinPosition As POINTAPI
        Public ptMaxPosition As POINTAPI
        Public rcNormalPosition As RECT
    End Structure

    Private Structure POINTAPI
        Public x As Integer
        Public y As Integer
    End Structure

    Private Structure RECT
        Public Left As Integer
        Public Top As Integer
        Public Right As Integer
        Public Bottom As Integer
    End Structure

    Enum Placements
        Normal = 1
        Minimized = 2
        Maximized = 3
    End Enum


    Shared Function GetPlacement(ByVal hwnd As IntPtr) As Placements

        Dim wpTemp As WINDOWPLACEMENT

        wpTemp.Length = System.Runtime.InteropServices.Marshal.SizeOf(wpTemp)
        Return CType(GetWindowPlacement(hwnd, wpTemp), Placements)

    End Function
End Class

...

For Each hwnd As IntPtr In desktopWindows
   If YourUtilityClass.GetPlacement(hwnd) != YourUtilityClass.Placements.Normal Then
      Continue For
   End If

警告:代码未经测试。

Api参考: https://msdn.microsoft.com/en-us/library/windows/desktop/ms633518(v=vs.85).aspx

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

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

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

  3. 别忘了打电话给ReleaseDC().

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

您需要做的第一件事(也是最困难的)是找到所有 "real" 非最小化(使用 IsIconic函数)可见windows。我的意思是,如果你使用 EnumWindowsEnumDesctopWindows 你会得到一堆不需要的 windows 例如: windows 开始图标、任务栏、程序管理器等 都是可见 并且 未最小化。如果您解决了这个问题,其他一切都非常简单:像您在数组中所做的那样存储句柄。顺序是从上到下的 z 顺序,例如。

arrayHandles[0]->顶部,arrayHandles[1]->一级波纹管等...

然后取出矩形并将它们存储在另一个数组中。最后一步是排除抽象的windows(显然rect0是Ok):

检查 rect1 和 rect0 是否相交。如果为真,则移除 rect1
用 rect1 和 rect0
检查 rect2 用 rect2 和 rect1 和 rect0
检查 rect3 ....

最后你有一个 clean 矩形数组,你可以在移动 window 时检查它们。

重要提示:以上所有步骤你只做一次一次,当你开始拖动你的window,在 WM_ENTERSIZEMOVE 消息中。

矩形交集代码(C代码中的rect0和rect1,我太懒了):

HRGN rgn1 = {0, 0, 0, 0}, rgn2 = {0, 0, 0, 0}, rgn3 = {0, 0, 0, 0};

rgn1 = CreateRectRgn(rect0.left, rect0.top, rect0.right, rect0.bottom);

rgn2 = CreateRectRgn(rect1.left, rect1.top, rect1.right, rect1.bottom);

rgn3 = CreateRectRgn(0, 0, 0, 0);

int rslt = CombineRgn(rgn3, rgn1, rgn2, RGN_AND);

if( rslt == ERROR ){
    printf("Error in combineRgn function \n");

    //do something
}

if( rslt != NULLREGION ){
    //They DONT intersect
}

DeleteObject(rgn1);
DeleteObject(rgn2);
DeleteObject(rgn3);