Jump to content

Insert Menu Item into another process and retrieve selection Without Subclassing

Recommended Posts

I was curious about adding menus to other programs

I found Melbas Excellent Answer and realized maybe there was another way besides subclassing with a separate dll

or checking every item and forgoing accelerator keys

Here is my try at it

#Region ;**** Directives created by AutoIt3Wrapper_GUI ****
#EndRegion ;**** Directives created by AutoIt3Wrapper_GUI ****
#include <WinAPI.au3>
#include <GuiMenu.au3>
#include <GUIConstants.au3>
;;#include <SendMessage.au3>

Global $aCmdID[4][2] = [ _
        ["&Insert", 0x0], _ ;Top Level
        ["&Date", 0x2000], _
        ["&Time", 0x2100], _
        ["&Os", 0x2200]]

Global $hWnd, $hMenu, $hItem, $iItemIndex ;Need to be accessible from _Exit

HotKeySet("{F1}", "_Exit")

MsgBox(0, "Menu Insert", "Activate a Notepad window or Press [F1] to quit")


Func _Exit()
    _MenuRemove($hWnd, $hMenu, $hItem, $iItemIndex)
EndFunc   ;==>_Exit

Func _MenuInsert($hWnd, ByRef $hMenu, ByRef $hItem, ByRef $iItemIndex) ;Melba23
    $hMenu = 0
    $hItem = 0
    $hMenu = 0
    $iItemIndex = -1
    $sTopLevel = StringReplace($aCmdID[0][0], "&", "") ;need to remove accelerator
    If Not IsHWnd($hWnd) Then Return SetError(1, 0, False)

    $hMenu = _GUICtrlMenu_GetMenu($hWnd)
    $iItemIndex = _GUICtrlMenu_FindItem($hMenu, $sTopLevel)
    If $iItemIndex <> -1 Then ;Already Exists
        $hItem = _GUICtrlMenu_GetItemSubMenu($hMenu, $iItemIndex)
        Return SetError(2, 0, True)

    $hItem = _GUICtrlMenu_CreatePopup($MNS_AUTODISMISS) ;_GUICtrlMenu_CreateMenu()
    For $i = 1 To UBound($aCmdID) - 1
        _GUICtrlMenu_InsertMenuItem($hItem, 0, $aCmdID[$i][0], $aCmdID[$i][1])
    ; Insert new menu
    _GUICtrlMenu_AppendMenu($hMenu, $MF_POPUP, $hItem, $aCmdID[0][0])

    $iItemIndex = _GUICtrlMenu_FindItem($hMenu, $sTopLevel)
    If $iItemIndex < 0 Then
        Return SetError(3, 0, False)

    Return True
EndFunc   ;==>_MenuInsert

Func _MenuWaitForSelection()
    Local Const $M_ITEM_FOCUSED = 0x7
    Local Const $M_LEFTALIGN = 0x1
    Local Const $M_TOPALIGN = 0x1
    Local Const $M_RETURNCMD = 0x2
    Local Const $WM_NCACTIVATE = 0x86

    Local $aInfo
    Local $hGui
    Local $iSelected = -1
    While $iSelected < 0 And WinActive($hWnd)
        ; Check status of menubar
        $aInfo = _GUICtrlMenu_GetMenuBarInfo($hWnd, $iItemIndex + 1, 1)
        ; If menu is active
        If $aInfo[$M_ITEM_FOCUSED] = True Then
            $hGui = GUICreate("", 0, 0, $aInfo[0], $aInfo[1], 0, -1, $hWnd)
            _WinAPI_PostMessage($hWnd, $WM_NCACTIVATE, 1, 0)
            _WinAPI_PostMessage($hWnd, $WM_CANCELMODE, 0, 0) ;Close the real menu
            $iSelected = _GUICtrlMenu_TrackPopupMenu($hItem, $hGui, $aInfo[0], $aInfo[3], $M_LEFTALIGN, $M_TOPALIGN, $M_RETURNCMD)
            If WinActive($hGui) Then
                _WinAPI_PostMessage($hWnd, $WM_NCACTIVATE, 0, 0)
                _MenuRemove($hWnd, $hMenu, $hItem, $iItemIndex)
    If Not WinActive($hWnd) Then _MenuRemove($hWnd, $hMenu, $hItem, $iItemIndex)
    Return $iSelected
EndFunc   ;==>_MenuWaitForSelection

Func _MenuRemove($hWnd, $hMenu, $hItem, $iItemIndex)
    If IsHWnd($hWnd) Then
        ConsoleWrite("REMOVING " & $hWnd & " " & $hMenu & ":" & $hItem & ":" & $iItemIndex & @CRLF)
        _GUICtrlMenu_RemoveMenu($hMenu, $iItemIndex)
EndFunc   ;==>_MenuRemove

Func Edit_Send_Selected($hWnd, $iSelected)
    ConsoleWrite("Item: 0x" & Hex($iSelected) & " Selected" & @CRLF)
    Switch $iSelected
        Case 0x2000
            ConsoleWrite("Sending Date" & @CRLF)
            ControlSend($hWnd, "", "Edit1", @MDAY & "/" & @MON & "/" & @YEAR)
        Case 0x2100
            ConsoleWrite("Sending Time" & @CRLF)
            ControlSend($hWnd, "", "Edit1", @HOUR & ":" & @MIN & "." & @SEC)
        Case 0x2200
            ConsoleWrite("Sending Os" & @CRLF)
            ControlSend($hWnd, "", "Edit1", @OSVersion & " " & @OSArch)
EndFunc   ;==>Edit_Send_Selected

Func Notepad_Menu_Handler()
    While (1)
        $hWnd = WinWaitActive("[Class:Notepad]")

        If IsHWnd($hWnd) Then
            _MenuInsert($hWnd, $hMenu, $hItem, $iItemIndex)

            ConsoleWrite("New Item is Index: " & $iItemIndex & " Error:" & @error & @CRLF)

            Edit_Send_Selected($hWnd, _MenuWaitForSelection())

            ConsoleWrite("Invalid hWnd" & @CRLF)
EndFunc   ;==>Notepad_Menu_Handler


Its not perfect it is a hack after all and yes I know F5 returns time and date in notepad its just an example :P


How does it work?

We start off adding a menu to a notepad window only when it becomes active

(We remove this menu item if the window is no longer active since we are no longer watching it)

When a user selects our top level menu item we cancel the original menu with a $WM_CANCELMODE message

We then popup the same menu only this time from our process

This does cause a deactivation of the notepad window but we fake it with a $WM_NCACTIVATE message

Unfortunately when I instead made the new gui window a child of the notepad window it works better but then I can't get the accelerator keys to work

It also makes this menu item deviate from the behavior of the normal menus in that you can't arrow to the others without closing it 

but hey, you can't get everything


Edited by Bilgus
Link to post
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
  • Recently Browsing   0 members

    No registered users viewing this page.

  • Create New...