Zurück zur Homepage

Fensterliste, Menüs anderer Programme

Vor Jahren hatte ich mal vor dem Problem gestanden, ein anderes Programm zu veranlassen, seine Arbeit alle Stunde zu sichern. Leider war da nix mit OLE und Co.
Also habe ich mit SendKeys gearbeitet. Was soll ich sagen, eine mittlere Katastrophe. Nicht nur, dass es immer nur ein paar Stunden geklappt hat, nein, auch ging das SendKeys danach überall hin und richtete zusätzlichen Schaden an.
Das Programm enthielt aber ein Menü. Und seit "Hardcore Visual Basic" wusste ich, dass man Menüs anderer Programme manipulieren kann. Das habe ich dann auch mit Erfolg hinbekommen und es hat über Jahre problemlos gefunzt. Hier ein Beispiel, wie man das macht. Leider enthalten neuerdings immer weniger Programme echte Menüs. Immer häufiger werden Commandbars eingesetzt. Diese zu manipulieren, habe ich noch nicht probiert. Ist vielleicht ein zukünftiges Thema.

Beispieldatei (menu.zip 47 KB)

'########################################################
'# Eine Mappe mit dem Blattern Menü.
'# Eine Klasse mit dem Namen clsRunMenu.
'# Ein Modul
' ########################################################

'########################################################
'# In das Modul
'########################################################

Private Sub cmbHilfe()
Dim x As New clsRunMenu
    x.HilfeFensterliste
    x.HilfeParentWindow
    x.HilfeMenülisteAsArray
    x.HilfeMenülisteAsCollection
End Sub

 


Sub TestenNotepad()
Dim x As New clsRunMenu, m As Collection
Dim a, b, c As String, d As String
Dim i As Long, k As Long, l As Long
On Error Resume Next
'Liste aller Top-Level-Fenster!
'Im zweidimensionalen Array a ist
'1. Element Hwnd
'2. Element Titelleiste
'3. Element Klassenname

a = x.Fensterliste
With Worksheets("Menü")
    For i = 1 To UBound(a)
        'Suche nach Notepad
        If InStr(1, LCase(a(i, 3)), "notepad") Then
            'Fenster Notepad gefunden. Der Klasse die
            'Fensterzugriffsnummer übergeben

            x.AktuellesFensterHwnd = a(i, 1)
            'Zur Info den Titel und den Klassenname ausgeben
            c = x.AktuellesFensterCaption
            d = x.AktuellesFensterClassName
            MsgBox c & vbCrLf & d, _
                vbOK, "Menü ausgeben des folgenden Fensters"
            .Range("6:1000").Delete
            'Menüs als Array holen
            b = x.MenülisteAsArray
            .Range("C2") = x.AktuellesFensterCaption
            .Range("C3") = x.AktuellesFensterClassName
            .Range("C1") = x.AktuellesFensterHwnd
            For l = 1 To UBound(b)
                For k = 1 To 5
                    .Cells(l + 5, k) = b(l, k)
                Next
            Next
            'Menüs als Collection holen
'            Set m = x.MenülisteAsCollection
'            For l = 1 To m.Count
'                For k = 1 To 5
'                    .Cells(l + 3, k + 6) = m(l)(k)
'                Next
'            Next

        End If
    Next
End With
End Sub

'########################################################
'# Als Klasse clsRunMenu
'########################################################

Option Explicit
Private Declare Function GetMenu Lib "user32" _
    (ByVal hwnd As Long) As Long
Private Declare Function GetSubMenu Lib "user32" _
    (ByVal hMenu As Long, ByVal nPos As Long) _
    As Long
Private Declare Function GetMenuItemCount Lib "user32" _
    (ByVal hMenu As Long) As Long
Private Declare Function GetMenuItemID Lib "user32" _
    (ByVal hMenu As Long, ByVal nPos As Long) As Long
Private Declare Function GetMenuString Lib "user32" _
    Alias "GetMenuStringA" _
    (ByVal hMenu As Long, ByVal wIDItem As Long, _
    ByVal lpString As String, ByVal nMaxCount As Long, _
    ByVal wFlag As Long) As Long
Private Declare Function FindWindowEx Lib "user32" _
    Alias "FindWindowExA" (ByVal hWnd1 As Long, _
    ByVal hWnd2 As Long, ByVal lpsz1 As String, _
    ByVal lpsz2 As String) As Long
Private Declare Function GetWindowText Lib "user32" _
    Alias "GetWindowTextA" (ByVal hwnd As Long, _
    ByVal lpString As String, ByVal cch As Long) As Long
Private Declare Function GetClassName Lib "user32" _
    Alias "GetClassNameA" (ByVal hwnd As Long, _
    ByVal lpClassName As String, ByVal nMaxCount As Long) _
    As Long
Private Declare Function SendMessage Lib "user32" _
    Alias "SendMessageA" (ByVal hwnd As Long, _
    ByVal wMsg As Long, ByVal wParam As Integer, _
    ByVal lParam As Any) As Long
Private Declare Function SendMessageTimeout Lib "user32" _
    Alias "SendMessageTimeoutA" (ByVal hwnd As Long, _
    ByVal msg As Long, ByVal wParam As Long, _
    ByVal lParam As String, ByVal fuFlags As Long, _
    ByVal uTimeout As Long, lpdwResult As Long) As Long
Private Const SMTO_ABORTIFHUNG = &H2
Private Const WM_COMMAND = &H111
Private Const MF_BYCOMMAND = &H0&
Private Const MF_BYPOSITION = &H400&
Private iHwnd As Long, iWindow, iParentWindow As Long


Private Function Durchlaufe(myHwnd As Long, _
    MyCol As Collection, _
    Optional myMenu As Long, _
    Optional myParentName As String, _
    Optional myActPath As String, _
    Optional myParentID As String)

Dim hwndHauptmenu As Long, hwndSubmenu As Long
Dim myName As String
Dim MenuID As String, MyEintrag(1 To 5) As String
Dim myAnzahl As Long, i As Long, myPfad As String
hwndHauptmenu = myMenu
If myMenu = 0 Then
    'Noch kein Menü ubergeben, deshalb holen
    hwndHauptmenu = GetMenu(myHwnd)
    'Keins gefunden, beeenden
    If hwndHauptmenu = 0 Then Exit Function
Else
    'Menü vorhanden
    If myActPath <> "" Then
        'Menü ist ein Submenü eines anderen
        myPfad = myActPath & "\"
        MyEintrag(3) = myPfad
        MyEintrag(5) = TrimTabUndKaufmännischesUnd(myPfad)
    Else
        'Menü ist kein Submenü
        myPfad = "\\" & myParentName & "\"
        MyEintrag(3) = "\\"
        MyEintrag(5) = "\\"
    End If
    'Infos in Array
    MyEintrag(1) = myParentID
    MyEintrag(2) = myParentName
    MyEintrag(4) = TrimTabUndKaufmännischesUnd(myParentName)
    'und der Collection übergeben
    MyCol.Add MyEintrag, "Eintrag_" & MyCol.Count
End If
myAnzahl = GetMenuItemCount(hwndHauptmenu)
'Alle Menüs auf dieser Ebene durchlaufen
For i = 0 To myAnzahl - 1
    'MenüID ermitteln
    MenuID = GetMenuItemID(hwndHauptmenu, i)
    myName = String(100, 0)
    'Menütext ermitteln
    GetMenuString hwndHauptmenu, i, myName, 100, MF_BYPOSITION
    myName = Left(myName, InStr(1, myName, Chr(0)) - 1)
    'Ermitteln, ob dieses Menü ein Submenü enthält
    hwndSubmenu = GetSubMenu(hwndHauptmenu, i)
    'Infos in Array
    MyEintrag(1) = MenuID
    MyEintrag(2) = myName
    MyEintrag(3) = myPfad & myName
    MyEintrag(4) = TrimTabUndKaufmännischesUnd(myName)
    MyEintrag(5) = TrimTabUndKaufmännischesUnd(myPfad & myName)
    If hwndSubmenu = 0 Then
        'Kein Submenü,
        'Array der Collection übergeben

        MyCol.Add MyEintrag, "Eintrag_" & MyCol.Count
    Else
        'Submenü vorhanden, diese Funktion für das Submenü
        'rekursiv durchlaufen

        Durchlaufe myHwnd, MyCol, hwndSubmenu, myName, myPfad, MenuID
    End If
Next
End Function


Public Sub Menüitem_anklicken(MenüID As Long)
     SendMessageTimeout iHwnd, WM_COMMAND, MenüID, ByVal 0&, SMTO_ABORTIFHUNG, 5000, 0&
End Sub


Public Sub HilfeMenülisteAsArray()
Dim a As String
a = "Ein zweidimensionales Array mit Menüinfos wird geliefert." & vbCrLf
a = a & "Alle Menüs eines Fensters stecken darin." & vbCrLf
a = a & "In der zweiten Dimension das erste Element Array(i,1) = MenüID" & vbCrLf
a = a & "In der zweiten Dimension das zweite Element Array(i,2) = Menütext, wie er wirklich ist" & vbCrLf
a = a & "      Beispiel:""&Neu"" & vbtab & ""Strg+N""" & vbCrLf
a = a & "In der zweiten Dimension das dritte Element Array(i,3) = MenüPfad, unbearbeitet" & vbCrLf
a = a & "In der zweiten Dimension das vierte Element Array(i,4) = Menütext, ohne &, bis zum Tab" & vbCrLf
a = a & "      Beispiel:""Neu""" & vbCrLf
a = a & "In der zweiten Dimension das fünfte Element Array(i,5) = MenüPfad, bearbeitet" & vbCrLf
MsgBox a, vbOKOnly, "Hilfe zu MenülisteAsArray"
End Sub


Public Sub HilfeMenülisteAsCollection()
Dim a As String
a = a & "Alle Menüs eines Fensters stecken in der Collection." & vbCrLf
a = a & "Jedes Element der Collection enthalt ein Menü in Form eines eindimensionalen Arrays." & vbCrLf
a = a & "Im ersten Element des Arrays, Collection(i)(1) = MenüID" & vbCrLf
a = a & "Im zweiten Element des Arrays, Collection(i)(2) = Menütext, wie er wirklich ist" & vbCrLf
a = a & "      Beispiel:""&Neu"" & vbtab & ""Strg+N""" & vbCrLf
a = a & "Im dritten Element des Arrays, Collection(i)(3) = MenüPfad, unbearbeitet" & vbCrLf
a = a & "Im vierten Element des Arrays, Collection(i)(4) = Menütext, ohne &, bis zum Tab" & vbCrLf
a = a & "      Beispiel:""Neu""" & vbCrLf
a = a & "Im fünften Element des Arrays, Collection(i)(5) = MenüPfad, bearbeitet" & vbCrLf
MsgBox a, vbOKOnly, "Hilfe zu MenülisteAsCollection"
End Sub


Public Property Get MenülisteAsCollection() As Collection
'Es wird eine Menüliste als Collection erzeugt
Dim Col As New Collection
If iHwnd Then Durchlaufe iHwnd, Col
Set MenülisteAsCollection = Col
End Property


Public Property Get MenülisteAsArray() As Variant
'Es wird eine Menüliste als Collection erzeugt, und in ein
'normales Array umgewandelt. Die meisten Leute können
'bedauerlicherweise mit Collections nichts anfangen.

Dim Col As New Collection, MyArray(), i As Long, k As Long
On Error Resume Next
If iHwnd Then Durchlaufe iHwnd, Col
ReDim MyArray(1 To Col.Count, 1 To 5)
For i = 1 To Col.Count
    For k = 1 To 5
        MyArray(i, k) = Col(i)(k)
    Next
Next
MenülisteAsArray = MyArray
End Property


Public Property Get AktuellesFensterHwnd() As Long
'Die Zugriffsnummer des aktuellen Fensters liefern.
    AktuellesFensterHwnd = iHwnd
End Property


Public Property Let AktuellesFensterHwnd(ByVal vNewValue As Long)
'Die Zugriffsnummer des aktuellen Fensters setzen.
'Es muss sich um eine aktuell gültige Zugriffsnummer handeln.

    iHwnd = vNewValue
End Property


Public Property Get AktuellesFensterClassName() As String
'Aktuellen Klassenname liefern.
Dim d As Long, buffer As String
    buffer = String(256, 0)
    d = GetClassName(iHwnd, buffer, 255)
    AktuellesFensterClassName = Left(buffer, d)
End Property


Public Property Get AktuellesFensterCaption() As String
'Aktuellen Fenstertitel liefern.
Dim d As Long, buffer As String
    buffer = String(256, 0)
    d = GetWindowText(iHwnd, buffer, 255)
    AktuellesFensterCaption = Left(buffer, d)
End Property


Public Sub HilfeFensterliste()
Dim a As String
a = "Ein zweidimensionales Array mit Fensterinfos wird geliefert." & vbCrLf
a = a & "Alle Fenster einer Ebene, also alle mit dem selben Papa stecken darin." & vbCrLf
a = a & "In der zweiten Dimension das erste Element Array(i,1) = Fenster Hwnd" & vbCrLf
a = a & "In der zweiten Dimension das zweite Element Array(i,2) = Fenster Titelleiste" & vbCrLf
a = a & "In der zweiten Dimension das dritte Element Array(i,3) = Fenster Klassenname" & vbCrLf
MsgBox a, vbOKOnly, "Hilfe zu Fensterliste"
End Sub


Public Property Get Fensterliste() As Variant
'Eine Liste mit Fenstern liefern.
    GetWindowlist
    Fensterliste = iWindow
End Property


Public Sub HilfeParentWindow()
Dim a As String
a = "Auch Fenster haben Eltern und manchmal selbst Kinder." & vbCrLf
a = a & "Top-Level-Fenster haben als Papa den Desktop. Zugriffsnummer=0" & vbCrLf
a = a & "Alle anderen haben normale Fenster als Eltern." & vbCrLf
a = a & "Um Kindfenster zu suchen, muss hier das Elternfenster gesetzt werden." & vbCrLf
a = a & "Diese gefundenen Fenster können wiederum Kindfenster enthalten, usw."
MsgBox a, vbOKOnly, "Hilfe zu ParentWindow"
End Sub


Public Property Let ParentWindow(ByVal vNewValue As Long)
    'Aktuelle Elternfensternummer setzen.
    iParentWindow = vNewValue
End Property


Public Property Get ParentWindow() As Long

    'Aktuelle Elternfensternummer liefern.
    ParentWindow = iParentWindow
End Property

Private Sub GetWindowlist()
Dim a As New Collection, b(1 To 3), c As Long, d As Long
Dim buffer As String
'Eine Liste mit Fenstern, dessen Elternfenster iParentWindow ist,
'wird erzeugt. Wenn iParentWindow=0, dann alle Top-Level Fenster.

c = FindWindowEx(iParentWindow, c, vbNullString, vbNullString)
Do While c <> 0
    b(1) = c
    buffer = String(256, 0)
    'Fenstertitel
    d = GetWindowText(c, buffer, 255)
    b(2) = Left(buffer, d)
    buffer = String(256, 0)
    'Klassenname
    d = GetClassName(c, buffer, 255)
    b(3) = Left(buffer, d)
    'Zur Collection hinzu
    a.Add b, "Key" & b(1)
    c = FindWindowEx(iParentWindow, c, vbNullString, vbNullString)
Loop
ReDim iWindow(1 To a.Count, 1 To 3)
For d = 1 To a.Count
    iWindow(d, 1) = a(d)(1)
    iWindow(d, 2) = a(d)(2)
    iWindow(d, 3) = a(d)(3)
Next
End Sub


Private Function TrimTabUndKaufmännischesUnd(ByVal myText As String)
'Der Buchstabe "&" (der folgende Buchstabe wird unterstrichen dargestellt) wird entfernt.
'Nur bis zum Tab bleibt der Text, alles andere wird entfernt.

On Error Resume Next
    myText = Left(myText, InStr(1, myText, vbTab) - 1)
    Do While InStr(1, myText, "&")
        myText = Left(myText, InStr(1, myText, "&") - 1) _
            & Right(myText, Len(myText) - InStr(1, myText, "&"))
    Loop
    TrimTabUndKaufmännischesUnd = myText
End Function