Bekanntlich ist Windows ein grafisch arbeitendes Betriebssystem: Wir sehen auf
den Bildschirm, und auf diesem sehen wir Fenster. In einem Fenster wiederum kann
es weitere Objekte geben, wie z. B. Unterfenster oder Steuerelemente. All diese
Objekte müssen miteinander kommunizieren können. Dafür vergibt Windows automatisch
für jedes Objekt intern eine eindeutige Nummer - den sogenannten Handle.
Ein Handle wird oft auch als hWnd
-Eigenschaft bezeichnet.
Schließt man ein Fenster und öffnet es anschließend erneut, kann es sein, dass
es dann eine ganz andere Nummer zugeteilt bekommt. Und da Objekte häufig hierarchisch
sind, gilt das natürlich auch für untergeordnete Objekte: Beim Schließen eines
Fensters verlieren auch Unterfenster und darin enthaltenen Objekte ihre
hWnd
-Eigenschaft. Das in der Hierarchie oberste Objekt ist der Desktop.
In VBA interessiert gelegentlich der Handle eines Fensters, andere Handles sind
in der Praxis nur selten relevant. Übrigens ist es in Microsoft
Access nicht notwendig, den Handle von Formularen oder Berichten per API
herauszufinden: Ihre hWnd
-Eigenschaft lässt sich in VBA z. B.
mit Me.hWnd
ermitteln. Aber auch andere Fenster haben vereinzelt eine
hWnd
-Eigenschaft.
Um einen Handle zu ermitteln, gibt es verschiedene API-Funktionen. Oft geht man
dabei von einem bestimmten Handle aus und arbeitet sich von dort zum gesuchten
Handle vor. Als Ausgangspunkt kommen besonders der Desktop, das aktive Fenster oder
das Vordergrundfenster in Frage. Den Desktop ermittelt man beispielsweise mit
GetDesktopWindow
. Da der Desktop das
oberste Objekt der Hierarchie ist, kann man sich von dort aus zu allen untergeordneten
Handles vorarbeiten. Die folgende Demofunktion listet, ausgehend vom Handle des
Desktops, alle in der Taskleiste angezeigten Fenster auf.
#If VBA7 Then Private Declare PtrSafe Function GetDesktopWindow Lib "user32" () As LongPtr Private Declare PtrSafe Function GetWindow Lib "user32" _ (ByVal hwnd As LongPtr, ByVal uCmd As Long) As LongPtr Private Declare PtrSafe Function GetWindowTextA Lib "user32" _ (ByVal hwnd As LongPtr, ByVal lpString As String, ByVal nMaxCount As Long) As Long Private Declare PtrSafe Function GetWindowInfo Lib "user32" _ (ByVal hwnd As LongPtr, ByRef pwi As PWINDOWINFO) As Boolean #Else Private Declare Function GetDesktopWindow Lib "user32" () As Long Private Declare Function GetWindow Lib "user32" _ (ByVal hwnd As Long, ByVal uCmd As Long) As Long Private Declare Function GetWindowTextA Lib "user32" _ (ByVal hwnd As Long, ByVal lpString As String, ByVal nMaxCount As Long) As Long Private Declare Function GetWindowInfo Lib "user32" _ (ByVal hwnd As Long, ByRef pwi As PWINDOWINFO) As Boolean #End If 'Typen für GetWindowInfo Private Type RECT Left As Long Top As Long Right As Long Bottom As Long End Type Private Type PWINDOWINFO cbSize As Long 'Strukturgröße rcWindow As RECT 'Fensterkoordinaten rcClient As RECT 'Clientkoordinaten dwStyle As Long 'Gibt WINDOWS_STYLES zurück dwExStyle As Long 'Gibt EXTENDED_WINDOWS_STYLES zurück dwWindowStatus As Long '1=Fenster ist aktives Fenster, 0=Fenster nicht aktiv cxWindowBorders As Long 'Rahmenbreite in px cyWindowBorders As Long 'Rahmenhöhe in px atomWindowType As Integer wCreatorVersion As Integer End Type Private Const GW_CHILD = 5 'GetWindow: Erstes Kindfenster Private Const GW_HWNDNEXT = 2 'GetWindow: Nächstes Fenster gleicher Ebene
Public Sub demoTaskliste() Dim strTitel As String Dim wi As PWINDOWINFO #If Win64 Then Dim lngHwnd As LongLong wi.cbSize = LenB(wi) #Else Dim lngHwnd As Long wi.cbSize = Len(wi) #End If lngHwnd = GetWindow(GetDesktopWindow(), GW_CHILD) 'Erstes Kindfenster des Desktops Debug.Print " hWnd", "Titel" Debug.Print " ----", "-----" Do Until lngHwnd = 0 'Zeige das Fenster, wenn es einen Titel hat und sichtbar ist GetWindowInfo lngHwnd, wi If (wi.dwStyle And &H10CF0000) = &H10CF0000 Then 'Titel des Fensters ermitteln strTitel = String(255, vbNullChar) GetWindowTextA lngHwnd, strTitel, 255 strTitel = Replace(strTitel, vbNullChar, "") Debug.Print lngHwnd, strTitel End If lngHwnd = GetWindow(lngHwnd, GW_HWNDNEXT) Loop End Sub
Mit GetActiveWindow
bzw.
GetForegroundWindow
kann man auf
bestimmte Fenster zugreifen. Das aktive Fenster ist das Fenster, das gerade eine
Aktion ausführt, im Fall eines API-Aufrufs aus VBA heraus also das Fenster, das
den VBA-Code auslöst. Das Fenster im Vordergrund ist das Fenster, das aktuell den
Fokus hat. Es kann immer nur ein Fenster im Vordergrund geben. Meistens ist das
Fenster im Vordergrund identisch mit dem aktiven Fenster, aber nicht zwingend.
Den Unterschied kann man mit folgendem Code testen: Wenn man die Sub
demoAktiveVsForeground
im VBA-Direktfenster startet, werden die Handles
des aktiven Fensters und des Fensters im Vordergrund ausgegeben. Sie sind identisch.
Nach 5 Sekunden werden wieder diese beiden Handles ausgegeben. Hat man während
dieser Zeit ein anderes Fenster aus der Taskleiste aktiviert, unterscheiden sich
die Handles.
#If VBA7 Then Private Declare PtrSafe Function GetActiveWindow Lib "user32" () As LongPtr Private Declare PtrSafe Function GetForegroundWindow Lib "user32" () As LongPtr #Else Private Declare Function GetActiveWindow Lib "user32" () As Long Private Declare Function GetForegroundWindow Lib "user32" () As Long #End If
Public Sub demoAktiveVsForeground() Dim t As Single t = Timer 'Startzeit Debug.Print "Startzeit", "ActiveWindow", "ForegroundWindow" Debug.Print "---------", "------------", "----------------" Debug.Print t, GetActiveWindow, GetForegroundWindow Do '5 Sek warten Loop Until (t + 5) < Timer 'Wenn man in der Taskleiste inzwischen eine andere Applikation ausgewählt hat, 'wird ein anderer Foreground zurückgegeben Debug.Print Timer, GetActiveWindow, GetForegroundWindow End Sub
Ermittelt das Handle des aktiven Fensters.
#If VBA7 Then Public Declare PtrSafe Function GetActiveWindow Lib "user32" () As LongPtr #Else Public Declare Function GetActiveWindow Lib "user32" () As Long #End If
Ermittelt das Handle des Desktops.
#If VBA7 Then Public Declare PtrSafe Function GetDesktopWindow Lib "user32" () As LongPtr #Else Public Declare Function GetDesktopWindow Lib "user32" () As Long #End If
Ermittelt das Handle des Vordergrundfensters.
#If VBA7 Then Public Declare PtrSafe Function GetForegroundWindow Lib "user32" () As LongPtr #Else Public Declare Function GetForegroundWindow Lib "user32" () As Long #End If
Ermittelt ein Handle relativ zu einem anderen Handle.
#If VBA7 Then Private Declare PtrSafe Function GetActiveWindow Lib "user32" () As LongPtr Private Declare PtrSafe Function GetWindow Lib "user32" _ (ByVal hWnd As LongPtr, ByVal uCmd As eCmd) As LongPtr #Else Private Declare Function GetActiveWindow Lib "user32" () As Long Private Declare Function GetWindow Lib "user32" _ (ByVal hWnd As Long, ByVal uCmd As Long) As Long #End If Private Enum eCmd GW_FINDFIRST = 0 'Erstes Fenster gleicher Ebene GW_FINDLAST = 1 'Letztes Fenster gleicher Ebene GW_HWNDNEXT = 2 'Nächstes Fenster gleicher Ebene GW_HWNDPREV = 3 'Letztes Fenster gleicher Ebene GW_OWNER = 4 'Elternfenster GW_CHILD = 5 'Erstes Kindfenster End Enum
Public Function demoGetWindow() demoGetWindow = GetWindow(GetActiveWindow, GW_HWNDNEXT) End Function
Liefert verschiedene Informationen zu einem Fenster.
#If VBA7 Then Private Declare PtrSafe Function GetWindowInfo Lib "user32" _ (ByVal hwnd As LongPtr, ByRef pwi As PWINDOWINFO) As Boolean #Else Private Declare Function GetWindowInfo Lib "user32" _ (ByVal hwnd As Long, ByRef pwi As PWINDOWINFO) As Boolean #End If Private Type RECT Left As Long Top As Long Right As Long Bottom As Long End Type Private Type PWINDOWINFO cbSize As Long 'Strukturgröße rcWindow As RECT 'Fensterkoordinaten rcClient As RECT 'Clientkoordinaten dwStyle As Long 'Gibt WINDOWS_STYLES zurück dwExStyle As Long 'Gibt EXTENDED_WINDOWS_STYLES zurück dwWindowStatus As Long '1=Fenster ist aktives Fenster, 0=Fenster nicht aktiv cxWindowBorders As Long 'Rahmenbreite in px cyWindowBorders As Long 'Rahmenhöhe in px atomWindowType As Integer wCreatorVersion As Integer End Type Private Enum WINDOWS_STYLES WS_BORDER = &H800000 'Fenster hat einen Rahmen WS_CAPTION = &HC00000 'Fenster hat Titelleiste WS_CHILD = &H40000000 'Kindfenster. Kann kein Menü haben und nicht Popup WS_CLIPCHILDREN = &H2000000 'Verhindert Kindfenster außerhalb des Elternfensters WS_CLIPSIBLINGS = &H4000000 'Verhindert sich gegenseitig überlappende Kindfenster WS_DISABLED = &H8000000 'Fenster ist initial Disabled WS_DLGFRAME = &H400000 'Dialogfenster WS_HSCROLL = &H100000 'Fenster hat horizontale Scrollleiste WS_MINIMIZE = &H20000000 'Fenster ist initial minimiert WS_MAXIMIZE = &H1000000 'Fenster ist initial maximiert WS_MAXIMIZEBOX = &H10000 'Fenster hat Maximieren-Button in der Titelleiste WS_MINIMIZEBOX = &H20000 'Fenster hat Minimieren-Button in der Titelleiste WS_OVERLAPPED = &H0 'überlappendes Fenster. Hat Titelleiste und Rahmen WS_POPUP = &H80000000 'Popupfenster WS_SIZEBOX = &H40000 'Fenster hat größenveränderbaren Rahmen WS_SYSMENU = &H80000 'Fenster hat Windowsmenü in der Titelleiste WS_VISIBLE = &H10000000 'Fenster ist initial sichtbar WS_VSCROLL = &H200000 'Fenster hat vertikale Scrollleiste End Enum 'EXTENDED_WINDOWS_STYLES ist stark unvollständig, da in VBA kaum Bedarf dafür besteht Private Enum EXTENDED_WINDOWS_STYLES WS_EX_CLIENTEDGE = &H200 'Fenster hat abgesenkte Rahmen WS_EX_CONTEXTHELP = &H400 'Fenster hat Hilfebutton in der Titelleiste WS_EX_DLGMODALFRAME = &H1 'Fenster mit modalem Rahmen WS_EX_NOACTIVATE = &H8000000 'Fenster kommt beim Klicken nicht in den Vordergrund WS_EX_TOPMOST = &H8 'Fenster liegt selbst deaktiviert über allen anderen End Enum
Public Sub demoWindowInfo() Dim wi As PWINDOWINFO Dim IE As Object Set IE = CreateObject("InternetExplorer.Application") IE.Visible = True #If Win64 Then wi.cbSize = LenB(wi) #Else wi.cbSize = Len(wi) #End If If GetWindowInfo(IE.hwnd, wi) Then With wi Debug.Print "Fenstergröße:", Debug.Print (.rcWindow.Right - .rcWindow.Left) _ & "*" & (.rcWindow.Bottom - .rcWindow.Top) If .dwStyle And WINDOWS_STYLES.WS_VISIBLE Then Debug.Print "Fenster ist sichtbar" Else Debug.Print "Fenster ist nicht sichtbar" End If If .dwStyle And WINDOWS_STYLES.WS_MINIMIZEBOX Then Debug.Print "Fenster hat einen Minimieren-Button" End If End With End If End Sub
Gibt den Titel eines Fensters zurück, falls es einen hat.
#If VBA7 Then Private Declare PtrSafe Function GetActiveWindow Lib "user32" () As LongPtr Private Declare PtrSafe Function GetWindowTextA Lib "user32" _ (ByVal hWnd As LongPtr, ByVal lpString As String, ByVal nMaxCount As Long) As Long #Else Private Declare Function GetActiveWindow Lib "user32" () As Long Private Declare Function GetWindowTextA Lib "user32" _ (ByVal hWnd As Long, ByVal lpString As String, ByVal nMaxCount As Long) As Long #End If
Public Function apiWindowText() As String Dim strLCData As String Dim lngSize As Long lngSize = 255 strLCData = String(lngSize, vbNullChar) GetWindowTextA GetActiveWindow, strLCData, lngSize apiWindowText = Replace(strLCData, vbNullChar, "") End Function