Учитывая hwnd, определите, не скрывается ли это окно другими окнами (Z-упорядочивание)

У меня есть NativeWindowЯ переопределил WndProc функция для обработки сообщения WM_WINDOWPOSCHANGING, так как при перемещении моего окна я буду привязывать его к границам ближайшего окна на рабочем столе.

У меня проблема в том, что мое окно пристыковывается к окнам, которые находятся на заднем плане других верхних окон, например, если у меня открыто окно проводника и другое окно под окном проводника, то мое окно может пристыковаться к окну, которое ниже другого окна, которое является уровнем z-порядка ниже, чем окно проводника. Я хочу избежать этого.

Демострация проблемы:

В приведенном выше GIF есть мое окно (Form1), окно IDE Visual Studio, окно проводника и окно приложения с именем "Hot Corners". Когда я отправляю окно "Горячие углы" на задний план, я все еще могу привязать свое окно к границам "Горячие углы". Я хочу избежать этого.

Обратите внимание на отладочную информацию в захваченном окне вывода Visual Studio.


Я читал о Z-Ordering в Википедии, и я также видел этот пример и документы MSDN здесь и здесь, однако я до сих пор не понимаю, как этого добиться.

Когда я пытаюсь привязать свое окно к другому окну, мне нужно определить, находится ли это целевое окно ниже других окон или нет, чтобы избежать проблемы, которую я объяснил.

Я надеюсь, что я хорошо объяснил проблему и ясно, что мне нужно, в GIF над моим окном не должно прилипать к окну "Горячие углы", потому что он не виден, потому что окно обозревателя находится выше.

Это соответствующий код, метод принимает в качестве аргументов мое окно (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/.

3 ответа

Решение

Учитывая дескриптор окна, вы должны быть в состоянии определить, полностью или частично закрыто окно другими окнами, используя несколько функций Win32:

  1. Вызов GetWindowDC() получить дескриптор контекста устройства (HDC), который включает в себя все окно, включая не клиентские области (например, строку заголовка, меню, границы и т. д.)

  2. Вызов GetClipBox() с HDC вернулся выше. Это вернет наименьший ограничивающий прямоугольник, который фактически виден (то есть на экране и не покрыт другими окнами). Кроме того, возвращаемое значение может сказать вам, закрыто ли окно полностью (NULLREGION).

  3. Не забудьте позвонить ReleaseDC(),

Справка по API: https://msdn.microsoft.com/en-us/library/dd144865%28v=vs.85%29.aspx

Первое, что вам нужно сделать (и самое сложное), это найти все "реальные" не минимизированные (используйте функцию IsIconic) видимые окна. Я имею в виду, что если вы используете EnumWindows или EnumDesctopWindows, вы получаете ряд нежелательных окон, таких как: значок запуска Windows, панель задач, менеджер программ и т. Д., Которые все видимы и не свернуты. Если вы переопределите эту проблему, все остальное очень просто: сохраните дескрипторы, как вы делали в массиве. Порядок сверху вниз в z-порядке, например.

arrayHandles [0] -> top, arrayHandles [1] -> один уровень ниже и т.д...

Затем возьмите прямоугольники и сохраните их в другом массиве. Последний шаг - исключить абстрагированные окна (по-видимому, rect0 - это ОК):

проверьте rect1 и rect0, если они пересекаются. Если true, тогда удалите rect1
проверить rect2 с помощью rect1 и rect0
проверить rect3 с помощью rect2, rect1 и rect0
....

В конце концов, у вас есть массив чистых линий, которые вы можете проверить во время перемещения окна.

Важно: все вышеперечисленные шаги вы делаете это только один раз, в тот момент, когда вы начинаете перетаскивать свое окно, которое находится в сообщении WM_ENTERSIZEMOVE.

Код для пересечения прямоугольника (rect0 и rect1 в коде C, я слишком ленив):

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);

Использование 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

Другие вопросы по тегам