Jump to content

Need a way to test a Window's Context Menu Items


Recommended Posts

I have a application that contains many child windows, and they all should support the same right click drop down context menu items like copy, select all, toggle caption bar or status bar , etc. Is there a way within  Auto IT or even calling  Win32API to discover these items? Thanks
 

Link to comment
Share on other sites

Certainly. This can be done with the UI Automation framework.

The example creates an UI Automation event handler, that listens for menu open events, and print the menu items in Scite console:

#include "CUIAutomation2.au3"

Opt( "MustDeclareVars", 1 )

Global Const $S_OK = 0x00000000
Global Const $E_NOINTERFACE = 0x80004002
Global Const $sIID_IUnknown = "{00000000-0000-0000-C000-000000000046}"

Global $tIUIAutomationEventHandler, $oIUIAutomationEventHandler

Global $oUIAutomation

MainFunc()


Func MainFunc()

  $oIUIAutomationEventHandler = ObjectFromTag( "oIUIAutomationEventHandler_", $dtagIUIAutomationEventHandler, $tIUIAutomationEventHandler )
  If Not IsObj( $oIUIAutomationEventHandler ) Then Return

  $oUIAutomation = ObjCreateInterface( $sCLSID_CUIAutomation, $sIID_IUIAutomation, $dtagIUIAutomation )
  If Not IsObj( $oUIAutomation ) Then Return

  Local $pUIElement
  $oUIAutomation.GetRootElement( $pUIElement ) ; Desktop
  If Not $pUIElement Then Return

  ; Menu open events
  If $oUIAutomation.AddAutomationEventHandler( $UIA_MenuOpenedEventId, $pUIElement, $TreeScope_Subtree, 0, $oIUIAutomationEventHandler ) Then Exit

  HotKeySet( "{ESC}", "Quit" )

  While Sleep(100)
  WEnd

EndFunc


Func Quit()
  $oIUIAutomationEventHandler = 0
  DeleteObjectFromTag( $tIUIAutomationEventHandler )
  Exit
EndFunc


Func oIUIAutomationEventHandler_HandleAutomationEvent( $pSelf, $pMenu, $iEventId ) ; Ret: long  Par: ptr;int
  ; Print menu name
  Local $oMenu = ObjCreateInterface( $pMenu, $sIID_IUIAutomationElement, $dtagIUIAutomationElement ), $sName
  $oMenu.AddRef()
  $oMenu.GetCurrentPropertyValue( $UIA_NamePropertyId, $sName )
  If $sName = "" Then $sName = "Menu"
  ConsoleWrite( $sName & ":" & @CRLF )

  ; Condition to find menu items
  Local $pCondition
  $oUIAutomation.CreatePropertyCondition( $UIA_ControlTypePropertyId, $UIA_MenuItemControlTypeId, $pCondition )

  ; Find menu items
  Local $pUIElementArray, $oUIElementArray, $iElements
  $oMenu.FindAll( $TreeScope_Children, $pCondition, $pUIElementArray )
  $oUIElementArray = ObjCreateInterface( $pUIElementArray, $sIID_IUIAutomationElementArray, $dtagIUIAutomationElementArray )
  $oUIElementArray.Length( $iElements )

  ; Print menu item names
  Local $pItem, $oItem
  For $i = 0 To $iElements - 1
    $oUIElementArray.GetElement( $i, $pItem )
    $oItem = ObjCreateInterface( $pItem, $sIID_IUIAutomationElement, $dtagIUIAutomationElement )
    $oItem.GetCurrentPropertyValue( $UIA_NamePropertyId, $sName )
    ConsoleWrite( "  " & $sName & @CRLF )
  Next

  ConsoleWrite( @CRLF )
  Return $S_OK
EndFunc


Func oIUIAutomationEventHandler_QueryInterface( $pSelf, $pRIID, $pObj ) ; Ret: long  Par: ptr;ptr*
  Local $sIID = StringFromGUID( $pRIID )
  If $sIID = $sIID_IUnknown Then
    ;ConsoleWrite( "oIUIAutomationEventHandler_QueryInterface: IUnknown" & @CRLF )
    DllStructSetData( DllStructCreate( "ptr", $pObj ), 1, $pSelf )
    oIUIAutomationEventHandler_AddRef( $pSelf )
    Return $S_OK
  ElseIf $sIID = $sIID_IUIAutomationEventHandler Then
    ;ConsoleWrite( "oIUIAutomationEventHandler_QueryInterface: IUIAutomationEventHandler" & @CRLF )
    DllStructSetData( DllStructCreate( "ptr", $pObj ), 1, $pSelf )
    oIUIAutomationEventHandler_AddRef( $pSelf )
    Return $S_OK
  Else
    ;ConsoleWrite( "oIUIAutomationEventHandler_QueryInterface: " & $sIID & @CRLF )
    Return $E_NOINTERFACE
  EndIf
EndFunc


Func oIUIAutomationEventHandler_AddRef( $pSelf ) ; Ret: ulong
  ;ConsoleWrite( "oIUIAutomationEventHandler_AddRef" & @CRLF )
  Return 1
EndFunc


Func oIUIAutomationEventHandler_Release( $pSelf ) ; Ret: ulong
  ;ConsoleWrite( "oIUIAutomationEventHandler_Release" & @CRLF )
  Return 1
EndFunc


Func StringFromGUID( $pGUID )
  Local $aResult = DllCall( "ole32.dll", "int", "StringFromGUID2", "struct*", $pGUID, "wstr", "", "int", 40 )
  If @error Then Return SetError( @error, @extended, "" )
  Return SetExtended( $aResult[0], $aResult[2] )
EndFunc


; Copied and slightly modified from this post by trancexx:
; http://www.autoitscript.com/forum/index.php?showtopic=153520&view=findpost&p=1143566
Func ObjectFromTag($sFunctionPrefix, $tagInterface, ByRef $tInterface, $fPrint = False, $bIsUnknown = Default, $sIID = "{00000000-0000-0000-C000-000000000046}") ; last param is IID_IUnknown by default
    If $bIsUnknown = Default Then $bIsUnknown = True
    Local $sInterface = $tagInterface ; copy interface description
    Local $tagIUnknown = "QueryInterface hresult(ptr;ptr*);" & _
            "AddRef dword();" & _
            "Release dword();"
    ; Adding IUnknown methods
    If $bIsUnknown Then $tagInterface = $tagIUnknown & $tagInterface
    ; Below line is really simple even though it looks super complex. It's just written weird to fit in one line, not to steal your attention
    ;Local $aMethods = StringSplit(StringReplace(StringReplace(StringReplace(StringReplace(StringTrimRight(StringReplace(StringRegExpReplace(StringRegExpReplace($tagInterface, "\w+\*", "ptr"), "\h*(\w+)\h*(\w+\*?)\h*(\((.*?)\))\h*(;|;*\z)", "$1\|$2;$4" & @LF), ";" & @LF, @LF), 1), "object", "idispatch"), "hresult", "long"), "bstr", "ptr"), "variant", "ptr"), @LF, 3)
    Local $aMethods = StringSplit(StringReplace(StringReplace(StringReplace(StringTrimRight(StringReplace(StringRegExpReplace(StringRegExpReplace($tagInterface, "\w+\*", "ptr"), "\h*(\w+)\h*(\w+\*?)\h*(\((.*?)\))\h*(;|;*\z)", "$1\|$2;$4" & @LF), ";" & @LF, @LF), 1), "hresult", "long"), "bstr", "ptr"), "variant", "ptr"), @LF, 3)
    Local $iUbound = UBound($aMethods)
    Local $sMethod, $aSplit, $sNamePart, $aTagPart, $sTagPart, $sRet, $sParams, $hCallback
    ; Allocation
    $tInterface = DllStructCreate("int RefCount;int Size;ptr Object;ptr Methods[" & $iUbound & "];int_ptr Callbacks[" & $iUbound & "];ulong_ptr Slots[16]") ; 16 pointer sized elements more to create space for possible private props
    If @error Then Return SetError(1, 0, 0)
    For $i = 0 To $iUbound - 1
        $aSplit = StringSplit($aMethods[$i], "|", 2)
        If UBound($aSplit) <> 2 Then ReDim $aSplit[2]
        $sNamePart = $aSplit[0]
        $sTagPart = $aSplit[1]
        $sMethod = $sFunctionPrefix & $sNamePart
        If $fPrint Then
            Local $iPar = StringInStr( $sTagPart, ";", 2 ), $t
            If $iPar Then
                $t = "Ret: " & StringLeft( $sTagPart, $iPar - 1 ) & "  " & _
                     "Par: " & StringRight( $sTagPart, StringLen( $sTagPart ) - $iPar )
            Else
                $t = "Ret: " & $sTagPart
            EndIf
            Local $s = "Func " & $sMethod & _
                "( $pSelf ) ; " & $t & @CRLF & _
                "EndFunc" & @CRLF
            ConsoleWrite( $s )
        EndIf
        $aTagPart = StringSplit($sTagPart, ";", 2)
        $sRet = $aTagPart[0]
        $sParams = StringReplace($sTagPart, $sRet, "", 1)
        $sParams = "ptr" & $sParams
        $hCallback = DllCallbackRegister($sMethod, $sRet, $sParams)
        ;ConsoleWrite(@error & @CRLF & @CRLF)
        DllStructSetData($tInterface, "Methods", DllCallbackGetPtr($hCallback), $i + 1) ; save callback pointer
        DllStructSetData($tInterface, "Callbacks", $hCallback, $i + 1) ; save callback handle
    Next
    DllStructSetData($tInterface, "RefCount", 1) ; initial ref count is 1
    DllStructSetData($tInterface, "Size", $iUbound) ; number of interface methods
    DllStructSetData($tInterface, "Object", DllStructGetPtr($tInterface, "Methods")) ; Interface method pointers
    Return ObjCreateInterface(DllStructGetPtr($tInterface, "Object"), $sIID, $sInterface, $bIsUnknown) ; pointer that's wrapped into object
EndFunc


Func DeleteObjectFromTag(ByRef $tInterface)
    For $i = 1 To DllStructGetData($tInterface, "Size")
        DllCallbackFree(DllStructGetData($tInterface, "Callbacks", $i))
    Next
    $tInterface = 0
EndFunc
You need CUIAutomation2.au3.

This is output from the context menu in Scite console, the Edit menu and the Paragraph submenu.

Context:
  Undo
  Redo
  Cut
  Copy
  Paste
  Delete
  Select All
  Hide

Edit:
  Undo  Ctrl+Z
  Redo  Ctrl+Y
  Cut   Ctrl+X
  Copy  Ctrl+C
  Paste Ctrl+V
  Duplicate Ctrl+D
  Delete    Del
  Select All    Ctrl+A
  Copy as RTF
  Match Brace   Ctrl+E
  Select to Brace   Ctrl+Shift+E
  Show Calltip  Ctrl+Shift+Space
  Complete Symbol   Ctrl+I
  Complete Word Ctrl+Enter
  Expand Abbreviation   Ctrl+B
  Insert Abbreviation   Ctrl+Shift+R
  Block Comment or Uncomment    Ctrl+Q
  Box Comment   Ctrl+Shift+B
  Stream Comment    Ctrl+Shift+Q
  Make Selection Uppercase  Ctrl+Shift+U
  Make Selection Lowercase  Ctrl+U
  Paragraph

Paragraph:
  Join
  Split
Link to comment
Share on other sites

Another example of getting the scite menu items:

#include <GuiMenu.au3>
$hWin = WinGetHandle("[CLASS:SciTEWindow]")
$hPrimaryMenu = _GUICtrlMenu_GetMenu($hWin)
For $i = 0 To _GUICtrlMenu_GetItemCount($hPrimaryMenu) - 1
    ConsoleWrite(_GUICtrlMenu_GetItemText($hPrimaryMenu,$i) & @CRLF)
    $hSub1 = _GUICtrlMenu_GetItemSubMenu($hPrimaryMenu,$i)
    For $j = 0 To _GUICtrlMenu_GetItemCount($hSub1) - 1
        ConsoleWrite(@TAB & _GUICtrlMenu_GetItemText($hSub1,$j) & @CRLF)
    Next
Next

Example of recurssion to get all child menus:

#include <GuiMenu.au3>
$hWin = WinGetHandle("[CLASS:SciTEWindow]")
$hPrimaryMenu = _GUICtrlMenu_GetMenu($hWin)

Global $iTabs = ""
GetMenuItems($hPrimaryMenu)
Func GetMenuItems($hParentMenu)
    For $i = 0 To _GUICtrlMenu_GetItemCount($hParentMenu) - 1
        ConsoleWrite($iTabs & _GUICtrlMenu_GetItemText($hParentMenu,$i) & @CRLF)
        $iPriorTabs = $iTabs
        $iTabs &= @TAB
        $hChild = _GUICtrlMenu_GetItemSubMenu($hParentMenu,$i)
        If $hChild Then
            GetMenuItems($hChild)
        EndIf
        $iTabs = $iPriorTabs
    Next
EndFunc
Edited by jdelaney
IEbyXPATH-Grab IE DOM objects by XPATH IEscriptRecord-Makings of an IE script recorder ExcelFromXML-Create Excel docs without excel installed GetAllWindowControls-Output all control data on a given window.
Link to comment
Share on other sites

Link to comment
Share on other sites

You can still use the old code:

#include <GuiMenu.au3>
#include <SendMessage.au3>
#include <WindowsConstants.au3>

Opt( "MustDeclareVars", 1 )

MainFunc()


Func MainFunc()

  Local Static $hLastMenu = 0

  HotKeySet( "{ESC}", "Quit" )

  While Sleep( 100 )

    Local $hDesktop = _WinAPI_GetDesktopWindow(), $hMenu = 0
    Local $hChild = _WinAPI_GetWindow( $hDesktop, $GW_CHILD ), $i = 0
    While $hChild And $i < 3
      If _WinAPI_GetClassName( $hChild ) = "#32768" Then
        $hMenu = _SendMessage( $hChild, $MN_GETHMENU )
        ExitLoop
      EndIf
      $hChild = _WinAPI_GetWindow( $hChild, $GW_HWNDNEXT )
      $i += 1
    WEnd

    If $hMenu And $hLastMenu <> $hMenu Then
      Local $iCount = _GUICtrlMenu_GetItemCount( $hMenu ), $sText
      ConsoleWrite( "Menu:" & @CRLF )
      For $i = 0 To $iCount - 1
        $sText = _GUICtrlMenu_GetItemText( $hMenu, $i )
        If $sText <> "" Then ConsoleWrite( "  " & $sText & @CRLF )
      Next
      ConsoleWrite( @CRLF )
      $hLastMenu = $hMenu
    EndIf

  WEnd

EndFunc


Func Quit()
  Exit
EndFunc
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...