【问题标题】:Given a hwnd, determine if that window is not hidden by other windows (Z-Ordering)给定一个 hwnd,确定该窗口是否未被其他窗口隐藏(Z-Ordering)
【发布时间】:2016-03-05 16:42:36
【问题描述】:

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

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

问题的演示:

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

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


我在 Wikipedia 上阅读了有关 Z-Ordering 的信息,还看到了 this 示例和 MSDN 文档 herehere,但是,我仍然不明白如何实现这一点。

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

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

这是相关代码,该方法将我的窗口(@98​​7654332@)作为参数,我在过滤 WM_WINDOWPOSCHANGING 消息时获得的 WINDOWPOS 结构的句柄我的窗口的WndProc 过程,最后一个参数threshold 是我的窗口的边界与其他窗口之间的最小空间。

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

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

【问题讨论】:

  • EnumWindows 出了什么问题?
  • @David Heffernan 谢谢,它帮助了我,但我最好使用 EnumDesktopWindows func。但是,我发现了一个大问题,就是我正在获取位于其他窗口背景中的窗口,因此无论目标窗口是否在其他窗口的背景中,我的窗口都停靠到任何窗口,知道该怎么做过滤以了解哪些窗口仅比我的窗口低一个“前景级别”?例如,如果桌面上有 2 个资源管理器窗口,一个在另一个之上,我只想威胁上面的窗口。我会用我的进度更新这个问题。谢谢评论。
  • 我不太明白。你的问题是关于执行速度的。现在您似乎更关心如何处理列表,这似乎有点令人困惑。您能否缩小问题的重点。大概 EnumDesktopWindows 更好,因为您只希望活动桌面上的窗口。
  • 使用WindowFromPoint 找出特定坐标处的可见窗口。
  • 如果您有两个窗口 相互重叠,例如一个在桌面的左侧,另一个在桌面的右侧,情况会怎样。您是否希望能够将您的表单停靠到两者或具有更高 z-order 的一个?

标签: .net vb.net winapi window z-order


【解决方案1】:

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

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

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

  3. 别忘了致电ReleaseDC()

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

【讨论】:

  • +1 和谢谢,您的 cmets GetClipBox 似乎很适合确定窗口的可见部分是否靠近我的窗口边框,但是我仍然没有尝试过。稍后我会在实现这些功能时评论结果。
  • 在我的情况下 GetClipBox 总是返回 SimpleRegion 值,即使目标窗口完全被遮挡,同样,如果目标窗口被最小化,那么总是返回任何测试窗口的 160x28 大小的 win32 矩形。也许我做错了什么?,我确保每次都释放DC。您能否提供这些功能的 pinvoke 定义? (我使用的定义与 pinvoke.net 中的定义相同)
  • 这在 Vista 及更高版本中是否真的有效(假设您没有明确关闭窗口合成)?我相信启用窗口组合后,所有窗口都独立绘制,DWM 将事物合并在一起。
【解决方案2】:

使用GetWindowPlacement 避免检查最小化窗口:

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

【讨论】:

  • 这没有回答我提出的问题,但感谢您和 +1 提出改进算法逻辑的建议,因为似乎 IsWindowVisible 函数返回 True最小化的窗口,所以我将实现 GetWindowsPlacement 以避免最小化和最大化的窗口,以避免更多的麻烦。
【解决方案3】:

您需要做的第一件事(也是最困难的)是找到所有“真实的”非最小化(使用 IsIconic 函数)可见 窗户。我的意思是,如果你使用 EnumWindowsEnumDesctopWindows,你会得到一堆不需要的窗口,例如:windows 启动图标、任务栏、程序管理器等 em> 都是可见未最小化。如果你覆盖这个问题,其他一切都非常简单:将句柄存储在数组中。顺序是按 z 顺序从上到下,例如。

arrayHandles[0]->top、arrayHandles[1]->下面一级等...

然后取出矩形并将它们存储在另一个数组中。最后一步是排除被抽象的窗口(显然rect0就可以了):

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

最后你有一个 clean rects 数组,你可以在移动窗口时检查它们。

重要提示:上述所有步骤您只需执行一次,即您开始拖动窗口的那一刻,即在 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);

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-12
    • 1970-01-01
    • 2021-11-28
    • 2017-03-30
    • 1970-01-01
    • 2021-11-01
    相关资源
    最近更新 更多