Jump to content

Please assist me in solving a dilemma regarding adding Menu items to several external applications


Recommended Posts

I've been researching this topic for about three weeks, but I'm still very confused. What I'm trying to do is to add a menu item to an arbitrary, third- party applications title bar system menu and process user events relating to it. The main problem I'm having is that there appears to be  a wealth of UDFs and other techniques to accomplish this, and I can't figure out which to concentrate on as a good solution.  For example, I've looked closely at @BrewManNHB 's  GUIRegisterMsg replacement for GUICtrlSetOnEvent and GUIGetMsg, and @LarsJJ 's stunning product GUIRegisterMsg20 UDF, as well as standard build- in AutoIt functions and WinAPI functions, But before I ask for recommendations, I'll elaborate my needs in more detail...

I'm nearing completion of my muilt-monitor UDF and AutoWinSwitch stand-alone application, but one tricky issue remains. The purpose of the application is to automatically move newly arisen windows and context menus and the like from a monitor which is being used for some other purpose (like playing a game or watching videos) to the other monitor so that the user can see them and act upon them.

The feature I need all this for is to allow the user to prevent a user-designated window from being moved, even though it exists on the "blocked" or used monitor so that it would normally require moving. In my testing, I've run into this situation often enough that a solution is necessary.

So what I've done so far is to call _GUICtrlMenu_GetSystemMenu() for the window in question, then called  _GUICtrlMenu_AddMenuItem() to add a new menu item named "Pinned". This is working perfectly, but of course that's far from sufficient. Because obviously I need to establish some kind of event handler so that when the user selects that item, I can then toggle or alter the state and style of that item to show whether it's pinned or not.

I've tried simply adding an on-event handler when I created the menu item, but even with the proper "Opt" value for On Event mode, the handler is never invoked.

I also tried BrewmanNH's UDF and then LarsJ's, but even though I get no errors, again the handlers are never invoked.

Finally, before I post the code of my last try using -- probably erroneously -- LarsJ's GUIRegisterMsg20 UDF, may I please ask for each of your recommendations and advice as to what approach you would favor? It's clear I don't know the most apt solution. Thanks!

When you examine my code snippets, please overlook the mistakes and false starts that still remain there until I'm in the final stages of cleanup. Note also that the code depends vitally on Maps, so it will only run properly on the latest Beta version of AutoIt. But I guess that's actually irrelevant, since the posted code won't pass syntax checking...

 

#AutoIt3Wrapper_Res_Fileversion=1.3
#AutoIt3Wrapper_icon=iPulse.ico
#AutoIt3Wrapper_UseX64=Y
#AutoIt3Wrapper_Run_After=copy "%out%" AutoWinSwitch.exe
#include <WinAPIGdi.au3>
#include <APIGdiConstants.au3>
#include <GUIConstants.au3>
#include <Misc.au3>
#include <MsgBoxConstants.au3>
#include <Process.au3>
#include <String.au3>
#include <StringConstants.au3>
#include <Timers.au3>
#include <WinAPIProc.au3>
#include <GUIConstantsEx.au3>
#include <GuiMenu.au3>
;~ #include <TrayConstants.au3>
#include "LocalIncludes\GetMonitorTables_v1.1.au3"
#include "LocalIncludes\_GetHwndFromPID.au3"
#include "LocalIncludes\GUIRegisterMsg20.au3"
AutoItSetOption("MustDeclareVars", 1)           ; Variables must be declared before use
AutoItSetOption("GUIOnEventMode", 1)            ; We will temporarily disable this later for StateChange GUI
AutoItSetOption("WinTitleMatchMode", 1)         ; 1 = Matches partial titles from the start
AutoItSetOption("MouseCoordMode", 0)            ; 0 = Coords are relative to window (not screen)
AutoItSetOption("GUICloseOnESC", 1 )            ; Close if user presses ESC
AutoItSetOption("TrayOnEventMode", 0)           ; We will enable this later
;
;
Const $C_ThisAppName = "AutoWinSwitch"
Const $C_VersionStr = "v1.3 Beta"
Global Const $C_Title = $C_ThisAppName & " " & $C_VersionStr
;
;-  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -
;
Const $C_AutoSwitchRegRootKey = "HKCU\Environment"
Const $C_AutoSwitchRegValName = "AUTOWINSWITCHSTATE"
Const $C_PathToTestApp = "B:\AutoIt Scripts\!ISN My Folder\My Projects\NFS Mounter\NFS_Mounter.exe"
Const $C_StateDisabled=1, $C_StateBlockPrimary=2, $C_StateBlockSecondary=3, $C_StateUnset=4, $C_NumStateStrs=8
Const $C_ValidStateStrAra[$C_NumStateStrs] = [ "DIS", "TOP", "TOS", "DISABL", "BLOCKN", "BLOCKS", "BLOCKP", "NOTSET" ]
Const $C_MoveTblNumCols=8
Global Enum $C_MoveTblHdlIx, $C_MoveTblMonIxIx, $C_MoveTblTitleIx, $C_MoveTblClassIx, $C_MoveTblProcIx, _
            $C_MoveTblTypeIx, $C_MoveTblWinLocIx, $C_MoveTblDimsIx
Const $C_NumDimCols=6
Global Enum $C_DimsWidthIx, $C_DimsHeightIx, $C_DimsLeftIx, $C_DimsTopIx, $C_DimsRightIx, $C_DimsBottomIx
Const $C_CalcAbsLeft=1, $C_CalcAbsRight=2, $C_CalcAbsTop=3, $C_CalcAbsBot=4
Const $C_WinTypeMain=1, $C_WinTypePopup=2
Const $C_OnOpenMon = 1, $C_OnBlockedMon = 2, $C_SpansMon = 3
Const $C_TitlesToIgnoreAraCount = 7
Const $C_TitlesToIgnoreAra[$C_TitlesToIgnoreAraCount] = [ "Microsoft Text Input", _
                                  "Program Manager", _
                                  "*AutoIt", _
                                  "Settings", _
                                  "WorkerW", _
                                  "*Debugger", _
                                  "*DBUG" ]
;- - - - - -
Global $G_GenMonTbl[$Gc_GMTblNumCols], $G_MMonitorTbl[1][$Gc_MMTblNumCols]
Global $G_CurWinMap[], $G_PrevWinMap[], $G_PinnedMap[], $L_EmptyMap[]
Global $G_MoveTimerAra[1][2], $G_MoveTimerAraIdx=-1, $G_AWS_HidWinHdl = -1
Global $G_MutexHdl, $G_WeOwnMutex, $G_Gid_PriRadioBtn, $G_Gid_SecRadioBtn, $G_Gid_DisabledRadioBtn
Global $G_MonIxToShowGUI = 0
Global $G_SwitchStateStr, $G_SwitchStateCode, $G_SwitchStateChanged = False
Global $G_NumValidMons=0, $G_TestAppPID, $G_TestAppWinHdl,  $G_TestAppTitle="NFS_Mounter", $G_TestAppPosAra[2]
;~ $WM_CLOSE = 0x0010
;~ $WM_MENUCOMMAND = 0x0126
;~ $WM_MENUSELECT = 0x011F
;~ $WM_MOVE = 0x0003
;~ $WM_MOVING = 0x0216
Global Enum $C_UseExtWinHdl, $C_UseExtCtrlHdl
Global $G_SysWinMsgCodesRegAraCount = 5
Global $G_SysWinMsgCodesRegAra[$G_SysWinMsgCodesRegAraCount][3] = [ _
            [ $C_UseExtWinHdl, $WM_CLOSE, __ExtCloseCmdHandler ], _
            [ $C_UseExtWinHdl, $WM_MOVE, __ExtMoveWinHandler ], _
            [ $C_UseExtWinHdl, $WM_MOVING, __ExtMovIngWinHandler ], _
            [ $C_UseExtCtrlHdl, $WM_MENUCOMMAND, __ExtMenuCmdHandler ], _
            [ $C_UseExtCtrlHdl, $WM_MENUSELECT, __ExtMenuSelectHandler ] ]
;
; - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
;
Local $L_WorkDir = "B:\ISN AutoIt Studio\Projects\AutoWinSwitch\AutoWinSwitch Release 1.3 Beta"
$L_RptPath = _UniqueOutFilePath( "WinReport", "txt", $L_WorkDir, 99 )
If @error <> 0 Then
    MsgBox($MB_OK, $C_Title, "Error: Unable to generate unique output filepath -- Exiting...")
    Exit 0
EndIf
Global $G_RptFileHdl = FileOpen( $L_RptPath, $FO_OVERWRITE + $FO_UTF8_NOBOM )
If $G_RptFileHdl = -1 Then
    MsgBox($MB_OK, $C_Title, "ERROR - Unable to open report file -- Exiting...")
    Exit
EndIf
OnAutoItExitRegister("_ExitCapture")
FileWriteLine( $G_RptFileHdl, "-- Begin run --" & @CRLF )
;
;===================================================================================================================
;
;
;   The following code ensures that no other instance of AutoWinSwitch is running.
;
;~ $G_RptFileHdl = FileOpen( $L_RptPath, "About to check Mutex")
;~ $G_MutexHdl = _Singleton( "Global\AutoWinSwitchMUTEX", 3 )
;~ If $G_MutexHdl = 0 Then
;~  FileWriteLine( $G_RptFileHdl, "We do NOT own the Mutex -- Exiting" )
;~  MsgBox($MB_OK + $MB_TOPMOST, $C_Title, "Another copy of this tool is already running -- This copy will now exit")
;~  Exit 47
;~ EndIf
$G_WeOwnMutex = True
;
; Setup all the information about whatever monitors are present in this system
;
;~ <snip>    <snip>    <snip>
;
;===================================================================================================================
;
$G_SwitchStateStr = RegRead( $C_AutoSwitchRegRootKey, $C_AutoSwitchRegValName )
If @error <> 0 Then
    FileWriteLine( $G_RptFileHdl, "Error reading Switch State -- Setting Reg to DIS for now" )
    $L_Stat = RegWrite( $C_AutoSwitchRegRootKey, $C_AutoSwitchRegValName, "REG_SZ", "DIS" ) ; Set registry to disabled
    $G_SwitchStateStr = "NOTSET"
    $G_SwitchStateCode = $C_StateUnset
Else
    FileWriteLine( $G_RptFileHdl, "Switch State read from Registry is: " & $G_SwitchStateStr )
EndIf
$G_SwitchStateCode = _StateStrToCode( $G_SwitchStateStr )
If @error <> 0 Then
    FileWriteLine( $G_RptFileHdl, "_StateStrToCode() Returned @error = " & @error & " -- Exiting")
    MsgBox($MB_OK + $MB_TOPMOST, $C_Title, "AutoWinSwitch - ERROR: Unrecognizable State in Registry - Exiting")
    Exit 6
EndIf
$L_VerboseStateStr = _StateCodeToStr( $G_SwitchStateCode, "Verbose" )
If @error <> 0 Then
    FileWriteLine( $G_RptFileHdl, "_StateCodeToStr() Returned @error = " & @error & " -- Exiting")
    MsgBox($MB_OK + $MB_TOPMOST, $C_Title, "_StateCodeToStr() Returned @error = " & @error & " -- Exiting")
    Exit 7
EndIf
$L_SpashMonIdx = $L_PrimaryMonTblIdx
If $G_SwitchStateCode = $C_StateBlockPrimary Then
    $L_SpashMonIdx = $L_SecondaryMonTblIdx
EndIf
$L_SplashStr = "Initial State: " & $L_VerboseStateStr
$L_SplashStrLen = StringLen( $L_SplashStr )
$L_SplashX = $G_MMonitorTbl[$L_SpashMonIdx][$Gc_MMTblCenterXIx] - Floor($L_SplashStrLen / 2)
$L_SplashY = $G_MMonitorTbl[$L_SpashMonIdx][$Gc_MMTblTopIx] + 4
;~ For $i = 1 To 2
    $L_SplashHdl = SplashTextOn( "AutoWinSwitch Current State", $L_SplashStr, 400, 40, $L_SplashX, $L_SplashY, _
                        $DLG_CENTERONTOP + $DLG_MOVEABLE, "Lucida Sans Unicode", 11, $FW_SEMIBOLD )
    Sleep( 1000 )
    SplashOff()
;~  Sleep( 500 )
;~ Next
$L_OrigStateCode = $G_SwitchStateCode
If ($L_OrigStateCode = $C_StateDisabled) Or ($L_OrigStateCode = $C_StateUnset) Then
    $G_SwitchStateCode = _ShowSwitchStateChangeGUI()
    If $G_SwitchStateChanged Then
        If $G_SwitchStateCode = $C_StateDisabled Then
            FileWriteLine( $G_RptFileHdl, "The GUI told us to disable and exit. Code = " & $G_SwitchStateCode )
            MsgBox($MB_OK + $MB_TOPMOST, $C_Title, "The GUI told us to disable and exit!")
            Exit 5
        EndIf
    EndIf
    $G_SwitchStateChanged = False
EndIf
Switch $G_SwitchStateCode
    Case $C_StateBlockPrimary
        $L_BlockedMonIdx = $L_PrimaryMonTblIdx
        $L_OpenMonIdx = $L_SecondaryMonTblIdx
        $L_CurMonTblIdx = $L_SecondaryMonTblIdx
        $G_MonIxToShowGUI = $L_OpenMonIdx
    Case $C_StateBlockSecondary
        $L_BlockedMonIdx = $L_SecondaryMonTblIdx
        $L_OpenMonIdx = $L_PrimaryMonTblIdx
        $L_CurMonTblIdx = $L_PrimaryMonTblIdx
        $G_MonIxToShowGUI = $L_OpenMonIdx
EndSwitch
$L_MoveTblCount = -1                                    ; We'll increment this when we want to add new windows to be moved
$L_NumPrevWins = 0
$L_NumPrevPopups = 0
;
;   Begin Primary Operations...
;
    $G_TestAppWinHdl = WinGetHandle( $G_TestAppTitle )
    If $G_TestAppWinHdl = 0 Then
        $G_TestAppPID =  ShellExecute( $C_PathToTestApp )   ; This launches the test app asynchronously
        If $G_TestAppPID = 0 Then                           ; If the launch failed...
            MsgBox($MB_OK, $C_Title, "Fatal Error: Unable to launch test app -- Exiting")
            Exit 12121
        EndIf
        $G_TestAppWinHdl = _GetHwndFromPID( $G_TestAppPID )
        $G_TestAppTitle = WinGetTitle( $G_TestAppWinHdl )
    Else
        $G_TestAppPID = WinGetProcess( $G_TestAppWinHdl )
    EndIf
    $L_Stat = _AddPinMenuItem( $G_TestAppWinHdl )
;~ <snip>    <snip>    <snip>
$L_Stat = HotKeySet( "+!a", "_ShowSwitchStateGUIWrapper" )          ; Shift-Alt-A brings up the State Change GUI
While True
    Local $L_CurTopWinAra = _WinAPI_EnumWindowsTop()
    $L_NumCurWins = $L_CurTopWinAra[0][0]
    If $L_NumPrevWins = 0 Then              ; If this is the first time through this watch loop...
        For $i = 1 To $L_NumCurWins         ; Build the Map of all top-level wins, to provide a basis for later comparison
            $L_CurWinHdl = $L_CurTopWinAra[$i][0]
            If WinExists( $L_CurWinHdl ) <> 1 Then          ; This window may have been closed by this time
                ContinueLoop
            EndIf
            If ($L_CurWinHdl <> 0) And (IsHWnd($L_CurWinHdl) = 1) Then
                $L_ThisClass = $L_CurTopWinAra[$i][1]
            EndIf
        Next
        $L_NumPrevWins = $L_NumCurWins
    Else        ; If we've been through this loop more than once, we'll have something to compare against
        $G_PrevWinMap = $G_CurWinMap        ; Move the contents of the "current" Map (from from the previous pass) here.
        $G_CurWinMap = $L_EmptyMap          ; We must empty that old Map so that we can load it up with ONLY the current windows
        $L_NumPrevWins = $L_NumCurWins      ; That count now represents the number of top-level windows from previous pass
        For $i = 1 To $L_NumCurWins         ; Re-build the current window Map
            $L_CurWinHdl = $L_CurTopWinAra[$i][0]
            If WinExists( $L_CurWinHdl ) <> 1 Then          ; This window may have been closed by this time
                ContinueLoop
            EndIf
            If ($L_CurWinHdl <> 0) And (IsHWnd($L_CurWinHdl) = 1) Then
                $L_ThisClass = $L_CurTopWinAra[$i][1]
;~              If MapExists( $G_PinnedMap, $L_CurWinHdl ) Then         ; If we're supposed to leave this win where it is
;~                  ContinueLoop
;~              EndIf
;~              _Add2Map( $L_CurSpclMap, $L_CurWinHdl, $L_ThisClass )
;~              If @error <> 0 Then
;~                  FileWriteLine( $G_RptFileHdl, "ERROR - _Add2Map( #2 = " & Hex($L_CurWinHdl) * " ) Failed -- Aborting")
;~                  MsgBox($MB_OK, $C_Title, "ERROR - _Add2Map( #2 = " & Hex($L_CurWinHdl) * " ) Failed -- Aborting")
;~                  Exit 6
;~              EndIf
            EndIf
        Next
;   -   -   -   -   -   -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -
        For $i = 1 To $L_NumCurWins         ; Now that we have the NEW Cur Map built, we can compare it to Previous Map
            $L_CurWinHdl = $L_CurTopWinAra[$i][0]
            If ($L_CurWinHdl <> 0) And (IsHWnd($L_CurWinHdl) = 1) Then
                If WinExists( $L_CurWinHdl ) <> 1 Then          ; This window may have been closed by this time
                    ContinueLoop
                EndIf
                If MapExists( $G_PinnedMap, $L_CurWinHdl ) Then         ; If we're supposed to leave this win where it is
                    ContinueLoop
                EndIf
                $L_ThisClass = $L_CurTopWinAra[$i][1]
                If Not MapExists( $G_PrevWinMap, $L_CurWinHdl ) Then    ; If this window wasn't in the prev list, i'ts new
                    $L_Stat = _AnalyzeNewWin( $G_PrevWinMap, $L_CurWinHdl, $L_ThisClass )
                    If $L_Stat <> 0 Then            ; If this window is new & has been added to the table of wins to be moved
                        $L_ThisClass = $G_PrevWinMap[$L_CurWinHdl]
                        MapRemove( $G_CurWinMap, $L_CurWinHdl )     ; Ensure that this win doesn't look like a fresh one
                        MapRemove( $G_PrevWinMap, $L_CurWinHdl )    ; ... either past or present
                    EndIf
                EndIf
            EndIf
        Next
    EndIf
;
;  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =  =
;
    If $L_MoveTblCount >= 0 Then                        ; IF we need to move at least 1 win (valid indices/count start at 0)
        $L_Stat = _RelocateWins( )
        $L_MoveTblCount = -1
    EndIf
;;~ <snip>    <snip>    <snip>
    Sleep( 1000 )
WEnd
Exit 12
;
;===================================================================================================================
;
;   This function was kindly contributed by jchd
;   See: https://www.autoitscript.com/forum/topic/204187-please-review-my-usage-of-scripting-dictionaries/?do=findComment&comment=1467804
;
Func _Add2Map(ByRef $map, $key, $value)
    If MapExists($map, $key) Then       ; same as If $m[$key] = Null Then
        If IsMap($map[$key]) Then
            MapAppend($map[$key], $value)
        Else
            Local $m2[]     ; an empty map
            $m2[1] = $map[$key]
            $m2[2] = $value
            $map[$key] = $m2
        EndIf
    Else
        $map[$key] = $value
    EndIf
EndFunc   ;==>_Add2Map
;
;===================================================================================================================
;
;   _AnalyzeNewWin -- Analyze a newly discovered window to see if it should be moved to the other monitor.
;
;   Return code: 0 = Ignore this window.
;                1 = This is an intruding window entirely on a blocked monitor, so it must be moved
;                2 = This is an intruding window partially on a blocked monitor, so it might need to be moved
;               -1 = An error occurred during processing
;
Func _AnalyzeNewWin( ByRef $arg_Map, $arg_WinHdl, $arg_Class )
;~ <snip>    <snip>    <snip>
Return 2
EndFunc   ;==>_AnalyzeNewWin
;
;===================================================================================================================
;
;   We now know that there's at least one window (normal or popup) that needs to be relocated to a
;   different monitor.
;
;   Currently, there's no need to distinguish between the two window types (normal or popup), but
;   there might need to be in the future, so I've decided to prepare for this eventuality.
;
Func _RelocateWins()
    Const $Lfc_MoveFull = 1, $Lfc_MovePartial = 2
;~ <snip>    <snip>    <snip>
    Return 1
EndFunc   ;==>_RelocateWins
;
;= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
;
Func _AddIfUnseenWin( ByRef $arg_Map, ByRef $arg_MoveTblWinInfoRow )
;
;   This function determines if we've seen this window before, and if NOT, then it
;   gets added to the appropriate Map.
;
;~ <snip>    <snip>    <snip>
    Return 1
EndFunc   ;==>_AddIfUnseenWin
;
;= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
;
Func _AddPinMenuItem( $arg_ExtWinHdl )
    Local $Lf_Stat, $Lf_ExtPID, $Lf_ExtSysMenuHdl, $Lf_ExtPinMenuHdl, $Lf_PinMenuItemIndex, $Lf_PinMenuItemID
    Local $Lf_InPinnedMap, $Lf_PinMenuItemExists, $Lf_SysMsgCodeMin, $Lf_SysMsgCodeMax
    If MapExists( $G_PinnedMap, $arg_ExtWinHdl ) Then
        $Lf_InPinnedMap = True
    Else
        $Lf_InPinnedMap = False
    EndIf
    $Lf_ExtPID = WinGetProcess( $arg_ExtWinHdl )
    $Lf_ExtSysMenuHdl = _GUICtrlMenu_GetSystemMenu( $arg_ExtWinHdl, False )
    $Lf_PinMenuItemIndex = _GUICtrlMenu_FindItem( $Lf_ExtSysMenuHdl, "Pinned", True )
    If $Lf_PinMenuItemIndex = -1 Then
        $Lf_PinMenuItemExists = False
    Else
        $Lf_PinMenuItemExists = True
        $Lf_PinMenuItemID = _GUICtrlMenu_GetItemID( $Lf_ExtSysMenuHdl, $Lf_PinMenuItemIndex, True )
    EndIf
    If Not $Lf_PinMenuItemExists Then
        If $Lf_InPinnedMap Then
            MapRemove( $G_PinnedMap, $arg_ExtWinHdl )
            Return SetError( 0, 0, 2 )
        EndIf
        $Lf_PinMenuItemIndex = _GUICtrlMenu_AddMenuItem( $Lf_ExtSysMenuHdl, "Pinned", 47 )
        If $Lf_PinMenuItemIndex = -1 Then
            Return SetError( 17226, 0, 0 )
        EndIf
        $Lf_PinMenuItemID = _GUICtrlMenu_GetItemID( $Lf_ExtSysMenuHdl, $Lf_PinMenuItemIndex, True )
        _Add2Map( $G_PinnedMap, $arg_ExtWinHdl, $Lf_PinMenuItemID)
        $Lf_ExtPinMenuHdl = GUICtrlGetHandle( $Lf_PinMenuItemID )
        $Lf_Stat = _SetupExtWinMsgRegs( $arg_ExtWinHdl, $Lf_ExtPinMenuHdl )
        Return SetError( @error, 0, $Lf_Stat )
    EndIf
    Return SetError( 0, 0, 1 )
EndFunc
Func __PinMenuCmdHandler()
    Local $Lf_ControlID, $Lf_SendingWinHdl, $Lf_ControlHdl, $Lf_ExtSysMenuHdl
    Local $Lf_Enabled, $Lf_Highlighted
    $Lf_ControlID = @GUI_CtrlId
    $Lf_SendingWinHdl = @GUI_WinHandle
    $Lf_ControlHdl = @GUI_CtrlHandle
    $Lf_ExtSysMenuHdl = _GUICtrlMenu_GetSystemMenu( $Lf_SendingWinHdl, False )
    $Lf_Enabled = _GUICtrlMenu_GetItemDisabled( $Lf_ExtSysMenuHdl, $Lf_ControlID, False )
    $Lf_Highlighted = _GUICtrlMenu_GetItemHighlighted( $Lf_ExtSysMenuHdl, $Lf_ControlID, False )
    If $Lf_Enabled Or $Lf_Highlighted Then
        _GUICtrlMenu_SetItemEnabled( $Lf_ExtSysMenuHdl, $Lf_ControlID, False, False )
        _GUICtrlMenu_SetItemHighlighted( $Lf_ExtSysMenuHdl, $Lf_ControlID, False, False )
        _GUICtrlMenu_EnableMenuItem( $Lf_ExtSysMenuHdl, $Lf_ControlID, 2, False )
        If MapExists( $G_PinnedMap, $Lf_ExtSysMenuHdl) Then
            MapRemove( $G_PinnedMap, $Lf_ExtSysMenuHdl )
        EndIf
    Else
        _GUICtrlMenu_SetItemEnabled( $Lf_ExtSysMenuHdl, $Lf_ControlID, True, False )
        _GUICtrlMenu_SetItemHighlighted( $Lf_ExtSysMenuHdl, $Lf_ControlID, True, False )
        _GUICtrlMenu_EnableMenuItem( $Lf_ExtSysMenuHdl, $Lf_ControlID, 0, False )
        _GUICtrlMenu_DrawMenuBar( $Lf_ExtSysMenuHdl )
        _Add2Map( $G_PinnedMap, $Lf_ExtSysMenuHdl, $Lf_ControlID )
    EndIf
    _GUICtrlMenu_DrawMenuBar( $Lf_ExtSysMenuHdl )
    Return
EndFunc
;
;= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
;
Func _IsMonitorBlocked( ByRef $arg_MoveTblWinInfoRow )
;
;   This func determines if the passed window is currently presented on a "blocked" monitor
;
;   Returns: $C_OnOpenMon    (=1) If the window in the passed Move Table Window Info Row is on an Open monitor
;            $C_OnBlockedMon (=2) If ... on Blocked mon
;            $C_SpansMon     (=3) If the window spans across both open and blocked mon
;
;~ <snip>    <snip>    <snip>
EndFunc   ;==>_IsMonitorBlocked
;
;   =   =   =   =   =   =   =   =   =   =   =   =   =   =   =   =   =   =   =
;
Func _SetupExtWinMsgRegs( $arg_ExtWinHdl, $arg_ExtControlHdl )
    Local $Lf_Stat
    For $i = 0 To $G_SysWinMsgCodesRegAraCount -1
        If $G_SysWinMsgCodesRegAra[$i][0] = $C_UseExtWinHdl Then
            $Lf_Stat = GUIRegisterMsg20( $arg_ExtWinHdl, $G_SysWinMsgCodesRegAra[$i][1], $G_SysWinMsgCodesRegAra[$i][2] )
        Else
            $Lf_Stat = GUIRegisterMsg20( $arg_ExtControlHdl, $G_SysWinMsgCodesRegAra[$i][1], $G_SysWinMsgCodesRegAra[$i][2] )
        EndIf
        If $Lf_Stat <> 1 Then
            Return SetError( @error, 0, 1 )
        EndIf
    Next
    Return SetError( 0, 0, 0 )
EndFunc
;
;   =   =   =   =   =   =   =   =   =   =   =   =   =   =   =   =   =   =   =
;
Func __ExtCloseCmdHandler( $arg_hWnd, $arg_Msg, $arg_wParam, $arg_lParam )
  #forceref $arg_hWnd, $arg_Msg, $arg_wParam, $arg_lParam
    Return $GUI_RUNDEFMSG
EndFunc
;
;   -   -   -   -   -   -   -   -   -   -   -   -   -   -   -   -   -   -   -
;
Func __ExtMoveWinHandler( $arg_hWnd, $arg_Msg, $arg_wParam, $arg_lParam )
    #forceref $arg_hWnd, $arg_Msg, $arg_wParam, $arg_lParam
    FileWriteLine( $G_RptFileHdl, "In __ExtMoveWinHandler()" )
    Return $GUI_RUNDEFMSG
EndFunc
;
;   -   -   -   -   -   -   -   -   -   -   -   -   -   -   -   -   -   -   -
;
Func __ExtMovIngWinHandler( $arg_hWnd, $arg_Msg, $arg_wParam, $arg_lParam )
    #forceref $arg_hWnd, $arg_Msg, $arg_wParam, $arg_lParam
    FileWriteLine( $G_RptFileHdl, "In __ExtMovIngWinHandler()" )
    Return $GUI_RUNDEFMSG
EndFunc
;
;   -   -   -   -   -   -   -   -   -   -   -   -   -   -   -   -   -   -   -
;
Func __ExtMenuCmdHandler( $arg_hWnd, $arg_Msg, $arg_wParam, $arg_lParam )
    #forceref $arg_hWnd, $arg_Msg, $arg_wParam, $arg_lParam
    FileWriteLine( $G_RptFileHdl, "In __ExtMenuCmdHandler()" )
    Return $GUI_RUNDEFMSG
EndFunc
;
;   -   -   -   -   -   -   -   -   -   -   -   -   -   -   -   -   -   -   -
;
Func __ExtMenuSelectHandler( $arg_hWnd, $arg_Msg, $arg_wParam, $arg_lParam )
    #forceref $arg_hWnd, $arg_Msg, $arg_wParam, $arg_lParam
    FileWriteLine( $G_RptFileHdl, "In __ExtMenuSelectHandler()" )
    Return $GUI_RUNDEFMSG
EndFunc

 

Edited by Mbee
typos
Link to post
Share on other sites

The message handling techniques that you mention in the first section can only be used in your own applications where you write the code yourself. These techniques are used in programming areas that can generally be termed as user interface development (UI development). The techniques can only be used when you write the code yourself.

There are generally two techniques for interacting with external applications: automation and hooking techniques. The hooking technique assumes that the associated message handler function is implemented in a dll-file. However, dll-files cannot be created using the AutoIt language. So the only real option is the automation techniques.

An example where the automation technique is used to add an additional menu item to the Notepad context menu and then detect that the menu item is clicked is found here. May be this method can also be used for the window system menu.

Link to post
Share on other sites

Thank you, LarsJ!

The example you pointed me to certainly is simple and straightforward, but it seems to me that I simply must have some kind of event handler, That's because there may be multiple windows (though typically only a few and no more than an a handful), yet I have other processing that must be done at the "same" time, so that I really shouldn't use wait loops watching these other arbitrary applications/windows.

I have a question that the example doesn't seem to answer for me: Are the automation techniques you refer to those found in the Help file under "WinAPIEx -> System Reference -> Automation Management"? They look quite powerful, and I would enjoy learning how to employ them in this case and in the future...

ETA: If that is what you are referring to, one of the things I want to be able to do is to hook into a window moved event (its fine to wait until the window is in the new position). Is that possible with those functions?

As long as I specify my own thread in the $iThreadId parameter (and never zero, which would once again mandate using my own custom DLL), the _WinAPI_SetWindowsHookEx() function looks like it can do what I want, with the possible exception of window moved. What do you think?

Edited by Mbee
Added another question
Link to post
Share on other sites
20 hours ago, Mbee said:

The example you pointed me to certainly is simple and straightforward, but it seems to me that I simply must have some kind of event handler, That's because there may be multiple windows (though typically only a few and no more than an a handful), yet I have other processing that must be done at the "same" time, so that I really shouldn't use wait loops watching these other arbitrary applications/windows.

Yes, you need an event handler. This event handler can best be implemented as a UI Automation event handler that monitors your system menus. Only when you have verified that it's a correct system menu you start the code to detect which menu item has been clicked.

But there is definitely a time-critical aspect to this method: In any case, do you have time to detect which menu item was clicked before the menu is closed? 

UI Automation event handlers are executed in Microsoft dll-files and therefore run as true compiled code. They are very efficient and can monitor hundreds of windows probably without any measurable performance impact on the PC.

 

The automation techniques I am referring to are quite general: Classic and UIA automation code.

 

20 hours ago, Mbee said:

As long as I specify my own thread in the $iThreadId parameter (and never zero, which would once again mandate using my own custom DLL), the _WinAPI_SetWindowsHookEx() function looks like it can do what I want, with the possible exception of window moved. What do you think?

If you mean that you can hook into an external application e.g. Notepad without a dll-file, then I really want to see the code.

This AutoIt example shows how to hook into external applications using dll-files.

Link to post
Share on other sites

Thanks again, LarsJ

Allow me to reply to the second part first... I certainly didn't mean to suggest I knew how to do this or that I knew it would work! Please understand that this whole area is entirely new to me and I have no Windows-OS experience with this type of thing. Furthermore, I'm kinda stupid, so I beg your indulgence and patience.  

My error was in (apparently) misunderstanding the help file related to the $pProc parameter of the _WinAPI_SetWindowsHookEx() function, which reads: "$pProc: Pointer to the hook procedure. If the $iThreadId parameter is zero or specifies the identifier of a thread created by a different process, the $pProc parameter must point to a hook procedure in a DLL. Otherwise, $pProc can point to a hook procedure in the code associated with the current process". I understood that to mean that as long as the $iThreadId parameter was neither zero nor represented a different process than my own, the hook procedure could indeed exist in my own code and not in a DLL. I still don't understand why that's not true, but when I coded up a test, the call to _WinAPI_SetWindowsHookEx() would always return "Invalid hook procedure".

My misunderstanding must lie in the jargon used in the Help file for that function, e.q., Is my thread considered to be "created by a different process"? Different relative to what? And does "current process" mean my own script, as a straightforward reading would suggest? Or does it mean the third-party process I'm trying to interact with? You see, I'm still quite confused. But I also see that I was on the wrong track anyway, so I'll drop that line of research.

- - - -

Returning to the first half of your reply: Yes, as the author of the astonishingly powerful UI Automation UDFs, I knew that was one of two possible interpretations. Clearly, I picked the wrong one, and I apologize for my errant guess. I will now commence working with your UI Automation UDFs.

Thanks

 

Link to post
Share on other sites

Wise @LarsJ

I'm very intimidated by the complexity and learning curve of using the UI Automation UDFs, so before I spend more time trying to scale this rather high wall, there's a critical question I need to ask. Given that I will have no idea which external applications and their associated windows I will have to interact with, are the UI Automation UDFs still a reasonable solution for me? I ask because -- in my admitted ignorance- -  it appears to me that to use the UDFs I will have to have advance knowledge of whatever arbitrary application/window I might ever encounter, which is obviously impossible.

Or am I mistaken, and the UDFs can control any arbitrary application and widow?

Because I am also pursuing the idea of creating a custom DLL using either VB.NET or VB 6.0, or if those don't work, in Visual C++. I have a licensed copy of Visual Studio 2017 Enterprise as well as VB 6.0 (which I managed -- with help -- to install under 64-bit Windows 10). I am quite unfamiliar with these tools and the IDE, but this path might nevertheless be the easiest to travel. I would be very grateful for your advice and explanations for that advice.

Thanks!

 

Link to post
Share on other sites

By using the DLL recommended by Larsj, I was able to intercept quite gracefully the clicks on self-made menu for outside applications.  Made my test on Notepad and was able to grab the clicks on whatever deep the menu was from the main menu.  The script requires a great deal of clean-up (as I needed to understand the whole shebang) but it is working nicely as far as I can tell.  I will post something later on in the near future, but this DLL is alright for usage if someone cares to experiment.

Link to post
Share on other sites

After more intensive tests, here are my latest findings.  By hooking to the WH_MSGFILTER, it is working alright since it is very easy to monitor all menu messages thru MSGF_MENU code.

BUT there is a major collateral issue.  The performance of the whole system (computer) is terribly impacted with the interception by the DLL of all messages.  For each message, the DLL generates 4-5 additional messages and all applications are hit badly. As far as I can tell, this solution is not viable, unless the DLL is rewritten specifically to a menu only interception.

Link to post
Share on other sites
On 2/8/2021 at 10:34 AM, Nine said:

After more intensive tests, here are my latest findings.  By hooking to the WH_MSGFILTER, it is working alright since it is very easy to monitor all menu messages thru MSGF_MENU code.

BUT there is a major collateral issue.  The performance of the whole system (computer) is terribly impacted with the interception by the DLL of all messages.  For each message, the DLL generates 4-5 additional messages and all applications are hit badly. As far as I can tell, this solution is not viable, unless the DLL is rewritten specifically to a menu only interception.

I am extremely grateful that you joined this thread, @Nine !

I understand them both technically, but I don't want to misinterpret your second post. Would I be correct in considering that to be an effective confirmation that I shouldn't employ the suggestion made by @LarsJ to use the UIAutomation technology to solve my problem with arbitrary GUI applications?  That virtually my only alternative is to create my own DLL to handle the WinAPI callbacks?

Thanks again for your most welcome help!

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.

  • Similar Content

    • By sandokanfirst
      Quick description: I'm building an application for a friend who wants to quit the use of Picasa but is reluctant because of the customised order of his pictures. The only way I know how to retain the custom order is to export all folders (and/or albums) managed by Picasa.
      I suppose the quick question is whether the way to trap messages for a window and/or control of your own making (GUI) is fundamentally different from trapping it for windows in a third party GUI. If that makes sense.
      My wizard style application has grown steadily but mainly slowly I found out that almost none of the parts of Picasa are recognisable Windows controls. (according to Au3Info and several other detection tools I've tried) So far, I've managed to work around that by reading and writing values to the registry w/ regard to export size, export location and quality.
      However. the final step appears to be the most difficult for me. What I tried to do is for the user to indicate two folders by double clicking on them. (Those two folders represent the range of folders that have to be exported). On this forum I found a contribution by Rasim to subclass a Window and replace the Windows procedure by a new one; see https://www.autoitscript.com/forum/topic/76841-detect-double-click-on-edit-box/ Here is his code to detect a double click in an edit control:
      $hGUI = GUICreate("Test GUI", 300, 200) $edit = GUICtrlCreateEdit("", 10, 10, 280, 180) $wProcHandle = DllCallbackRegister("_WindowProc", "ptr", "hwnd;uint;wparam;lparam") $wProcOld = _WinAPI_SetWindowLong(GUICtrlGetHandle($edit), $GWL_WNDPROC, DllCallbackGetPtr($wProcHandle)) GUISetState() Do Until GUIGetMsg() = $GUI_EVENT_CLOSE GUIDelete($hGui) DllCallbackFree($wProcHandle) Func _WindowProc($hWnd, $Msg, $wParam, $lParam) Switch $hWnd Case GUICtrlGetHandle($edit) Switch $Msg Case $WM_LBUTTONDBLCLK ConsoleWrite("-> Left mouse double click" & @LF) Return 0 EndSwitch EndSwitch Local $aRet = DllCall("user32.dll", "int", "CallWindowProc", "ptr", $wProcOld, _ "hwnd", $hWnd, "uint", $Msg, "wparam", $wParam, "lparam", $lParam) Return $aRet[0] EndFunc The(/a) fundamental difference between this code and my program is that I try to do the same with Picasa and that that is not a GUI created in AutoIt. So instead of GUICtrlGetHandle($edit), I call the above function with the Picasa window handle. However, it doesn't work. Neither does Notepad if I pass its handle. I'm sure I express myself rather clumsily, but is it imperative to create and address your own GUI if you want to detect a double click in a third party program? Why wouldn't the above code work when automating a non AutoIt GUI?
      Cheers,
      Sando
      PS (Edit): I'm sure there are smarter ways to achieve what I want in Picasa, but I'm also really interested in AutoIts working. I was a Delphi developer in the past, and although i see quite a few similarities, especially between Koda and the IDE, I've also come to realise how much we Delphi developers have been shielded from the intricacies of Windows and its frameworks. I find AutoIt fascinating, and incredibly useful.
    • By littleboy62
      Hello.
       
      For a project of a tool, I have to launch a program.
      This one: Display Properties control (run desk.cpl)
      I have to resize it, so we can see what's necessary.

      I'm getting there.
      The problem is that there is a drop-down menu: so I want to disable it.
      How to do this?

      I already did a test with the notepad. By deactivating the "close" button.
      Here is the code:
       
      Run("Notepad.exe") WinWait("[CLASS:Notepad]") $Hwnd = WinGetHandle("[CLASS:Notepad]") $menu = DllCall("user32.dll","hwnd","GetSystemMenu","hwnd", $Hwnd, "int",0) DllCall("user32.dll", "int", "RemoveMenu", "hwnd", $menu[0] , "int", 0xF060, "int", 0x0);SC_CLOSE MsgBox(0,'',1) $menu = DllCall("user32.dll","hwnd","GetSystemMenu","hwnd", $Hwnd, "int",1) DllCall("user32.dll", "int", "RemoveMenu", "hwnd", $menu[0] , "int", 0xF060, "int", 0x0);SC_CLOSE
      I would like to do the same, but with the drop-down menu (with DllCall).
      Is it possible to disable / block (or other) the drop-down menu of a program?

      Thank you for your answers.
      Sincerely,
      Thomas.
    • By FrancescoDiMuro
      Good morning everyone

      I'm trying to build, and, first of all, understand, how a Virtual ListView works.
      By now, I've already downloaded all the samples from Virtual List View samples provided by @LarsJ, but I still don't understand few things.
      What I understood by now, is that the Virtual ListView doesn't need to be filled by object like Items, because it use directly the source to display the data.
      I've already understood that the $LVN_ODCACHEHINT Notification Code does provide the range of the items to be displayed, but I don't understand how to fill $iFrom and $iTo which are in the $NMLVCACHEHINT, or who fill them.
      And, "finally", I understood that the $LVN_GETDISPINFO is used to fill the ListView with "items".
      Can someone please explain to me how do these Virtual ListView controls work?
      Thanks in advance.


      Best Regards.

       
    • By Monica
      Hi guys!! I'm going crazy... please help me -.-
      First, open the attached screenshot. Second: I'd like to grab info from external window. The program is "Metatrader4", a tool for trading and charts analysis.
      The function _GUICtrlListView_GetItemText() return an empty string! I tried to change the index $index (the second parameter) into Number($index), as recommended by other similar posts in this forum, but this did not solve my problem 
      Well, why I'm going mad? Because in my mind an UDF approach works great or it does not work at all; instead, in my code something works and something else does not. I really don't understand why... and you?
      THXX!!!!  
      $handle=ControlGetHandle("Expert - "&$expertname,"",1383);List: Variable|Value For $i=0 To _GUICtrlListView_GetItemCount($handle)-1;WORKS!!! $temp=_GUICtrlListView_GetItemText($handle,0+$i);RETURN EMPTY STRING :-( MsgBox(0, "", $temp) $temp = _GUICtrlListView_GetItemSelected($handle,$i);WORKS!!! MsgBox(0, "", $temp) Next  

    • By dejhost
      Hi everybody,
      I have troubles using the function "GUISetCursor" properly.
      My script generates a small GUI and opens an image in IrfanView. If the user hits the button "$p_coord" on the GUI, the GUI shall be hidden, IrfanView shall become active and the mousepointer shall turn into a cross (as long hovering over IrfanView). Once clicked into IrfanView, the cursor shall turn into a nordinary mousepointer again.
      Unfortunately, the cross doesn't appear until the user clicks into IrfanView manually. Then the mousepointer changes for the duration of the click into the cross. Can anybody tell why this is happening?
      Here is the relevant code:
      GUISetState(@SW_SHOW, $hGUI) ;the autoit gui is hidden ; Loop until the user exits: While 1 Switch GUIGetMsg() Case $GUI_EVENT_CLOSE ExitLoop Case $p_coord WinSetState("Bicon Coordinator","",@SW_HIDE) ;the autoit gui is hidden $Handle = WinActivate ( "IrfanView") ; IrfanView is activated GUISetCursor (15,1,$Handle) ;Cursor should be turned into a cross - this is what fails :-( While 1 If _IsPressed ("01", $hDLL) Then ;Once a mousclick by the user is performed... GUISetCursor (2) EndIf EndSwitch WEnd  
      dejhost
       
       
       
       
       
×
×
  • Create New...