Dot-Net

給定一個 hwnd,確定該視窗是否未被其他視窗隱藏(Z-Ordering)

  • December 11, 2015

我有一個NativeWindow,我已經覆蓋了WndProc處理**WM_WINDOWPOSCHANGING**消息的函式,因為當移動我的視窗時,我會將它粘附到桌面中最近視窗的邊框上。

我遇到的問題是我的視窗停靠在其他頂部視窗的背景中的視窗上,例如,如果我打開了一個資源管理器視窗並且在資源管理器視窗下方有其他視窗,那麼我的視窗可以停靠到那個視窗在另一個視窗的下方,這是一個低於資源管理器視窗的 z 順序級別。我想避免這種情況。

問題的展示:

在此處輸入圖像描述

在上面的 GIF 中,有我的視窗 (Form1)、Visual Studio IDE 視窗、資源管理器視窗和名為“Hot Corners”的應用程序視窗。當我將“Hot Corners”視窗發送到背景時,我仍然可以將我的視窗粘附到“Hot Corners”邊框。我想避免這種情況。

注意 Visual Studio 擷取的輸出視窗中的調試資訊。


我在Wikipedia上閱讀了有關 Z-Ordering 的資訊,並且我還看到了這個範例和MSDN文件**herehere**,但是,我仍然不明白如何實現這一點。

當我嘗試將我的視窗粘附到其他視窗時,我需要確定該目標視窗是否低於其他視窗,以避免我解釋的問題。

我希望我能很好地解釋這個問題並且清楚我需要什麼,在我的視窗上方的 GIF 中不應該堅持“熱角”視窗,因為它不可見,因為資源管理器視窗在上面。

這是相關程式碼,該方法將我的視窗(a Form)作為參數,我在視窗的過程中過濾消息時獲得的**WINDOWPOS**結構的句柄,最後一個參數是我的視窗的邊界到其他視窗以堅持它。WM_WINDOWPOSCHANGING``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 的相同原因。

給定一個視窗句柄,您應該能夠使用一些 Win32 函式來確定該視窗是完全還是部分被其他視窗遮擋:

  1. 呼叫GetWindowDC()以檢索包括整個視窗的設備上下文句柄 ( HDC),包括非客戶區(例如,標題欄、菜單、邊框等)
  2. GetClipBox()使用HDC上面返回的呼叫。這將返回實際可見的最小邊界矩形(即在螢幕上且未被其他視窗覆蓋)。此外,返回值可以告訴您視窗是否完全被遮擋(NULLREGION)。
  3. 別忘了打電話ReleaseDC()

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

引用自:https://stackoverflow.com/questions/34025833