Fensterhandles

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
Active
Foreground

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

GetActiveWindow

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

GetDesktopWindow

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

GetForegroundWindow

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

GetWindow

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

GetWindowInfo

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

GetWindowText

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