Учитывая 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:
Вызов
GetWindowDC()
получить дескриптор контекста устройства (HDC
), который включает в себя все окно, включая не клиентские области (например, строку заголовка, меню, границы и т. д.)Вызов
GetClipBox()
сHDC
вернулся выше. Это вернет наименьший ограничивающий прямоугольник, который фактически виден (то есть на экране и не покрыт другими окнами). Кроме того, возвращаемое значение может сказать вам, закрыто ли окно полностью (NULLREGION
).Не забудьте позвонить
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