Jump to content

_GUICtrlMenu_TrackPopupMenu() and $iNotify


Info
 Share

Recommended Posts

How do I get the menu item that the user selected if $iNotify is set to 1 for "Do not send notification messages"?

If you do not want a notification to be sent then you would set $iNotify to 1 or 3. But you would need to use 3 rather than 1 because then the id of the menu item is returned by the function.

Serial port communications UDF Includes functions for binary transmission and reception.printing UDF Useful for graphs, forms, labels, reports etc.Add User Call Tips to SciTE for functions in UDFs not included with AutoIt and for your own scripts.Functions with parameters in OnEvent mode and for Hot Keys One function replaces GuiSetOnEvent, GuiCtrlSetOnEvent and HotKeySet.UDF IsConnected2 for notification of status of connected state of many urls or IPs, without slowing the script.
Link to comment
Share on other sites

Perhaps this isn't what I'm looking for. Here's my code

Switch _GUICtrlMenu_TrackPopupMenu($hMenu, $Listview, -1, -1, 1, 1, 1)
    Case 1
        _Func1()
    Case 2
        _Func2()
    Case 3
        _Func3()
EndSwitch

I create this PopupMenu when the user right clicks a Listview control.

The problem is that the listview gets stuck until one of those functions (_Func1/2/3) finishes executing.

What can I do?

Link to comment
Share on other sites

Perhaps this isn't what I'm looking for. Here's my code

Switch _GUICtrlMenu_TrackPopupMenu($hMenu, $Listview, -1, -1, 1, 1, 1)
    Case 1
        _Func1()
    Case 2
        _Func2()
    Case 3
        _Func3()
EndSwitch

I create this PopupMenu when the user right clicks a Listview control.

The problem is that the listview gets stuck until one of those functions (_Func1/2/3) finishes executing.

What can I do?

Are you running _GUICtrlMenu_TrackPopupMenu() in WM_NOTIFY or in WM_CONTEXTMENU?

Read notes for GuiRegisterMsg() about not using blocking functions in a message handler

You need to return from _GUICtrlMenu_TrackPopupMenu() and run functions from your main loop or on-event mode

Here are two methods of implementing a contextmenu on a listview that return to the message loop after item selection and use hit testing.

Contextmenu positioning on a control is all about hit testing.

You need hit testing so the contextmenu does not popup over non-item elements of the listview.

(header, blank areas, checkbox, icon)

I posted an example in this thread of a contextmenu for a listview that includes hit testing

and the ability to have a customized contextmenu per listview group or item.

There are several ways to use a contextmenu.

You can use the native contextmenu to run functions on event or from loop or use the UDF version or mix the UDFcommands

with the native menu as we do here with _GUICtrlMenu_TrackPopupMenu()

For the returned selected menu item you can use the messages WM_COMMAND or WM_MENUCOMMAND to get notifications

from _GUICtrlMenu_TrackPopupMenu() or use the direct returned selected menu id from _GUICtrlMenu_TrackPopupMenu() in WM_CONTEXTMENU

This is why the sometimes suggested method of sending a WM_COMMAND message with a menu item index to an external

program to run one of its menu functions without having to show the menu and do a menu mouseclick will not work, because a

contextmenu can be implemented using a method other than WM_COMMAND.

Here are two examples, one uses hit testing in the WM_NOTIFY message handler using the NM_RCLICK notification,

the other does the hit test in WM_CONTEXTMENU

Both examples include optional keyboard Menu AppKey and Shift-F10 support

Edit: verbiage

Edit2: Changed the header size code in 1st example

WM_CONTEXTMENU method

#include <GuiListView.au3>
#include <WindowsConstants.au3>
#include <GuiConstantsEx.au3>
#include <GuiMenu.au3>

Opt('MustDeclareVars', 1)

Global $hLv, $hLVCM

Local $hGui, $msg, $cLv, $cLVCM, $cMenu1, $a, $b, $c
$hGui = GUICreate('ListView ContextMenu', 410, 300, -1, -1)
$cLv = GUICtrlCreateListView('List of Items', 5, 5, 400, 290)
$hLv = GUICtrlGetHandle($cLv) ;get listview handle from control ID
_GUICtrlListView_SetExtendedListViewStyle($cLv, BitOR($LVS_EX_GRIDLINES, $LVS_EX_FULLROWSELECT))
_GUICtrlListView_SetColumnWidth($cLv, 0, 396)

For $i = 1 To 150
    GUICtrlCreateListViewItem('ITEM ' & $i, $cLv)
Next

;Create a dummy parent for the listview contextmenu menu so it will not popup on right click
;unless shown with _GUICtrlMenu_TrackPopupMenu()
$cMenu1 = GUICtrlCreateDummy()
$cLVCM = GUICtrlCreateContextMenu($cMenu1)
$hLVCM = GUICtrlGetHandle(-1)
$a = GUICtrlCreateMenuItem('LV Menu Item 1', $cLVCM)
$b = GUICtrlCreateMenuItem('LV Menu Item 2', $cLVCM)
$c = GUICtrlCreateMenuItem('LV Menu Item 3', $cLVCM)


GUIRegisterMsg($WM_CONTEXTMENU, "_WM_CONTEXTMENU")
GUISetState(@SW_SHOW)


While 1
    $msg = GUIGetMsg()
    Switch $msg
        Case 0
        Case $a
            ConsoleWrite("+ LV Menu Item 1" & @CRLF)
        Case $b
            ConsoleWrite("- LV Menu Item 2" & @CRLF)
        Case $c
            ConsoleWrite("> LV Menu Item 3" & @CRLF)
        Case -3
            GUIDelete($hGui)
            Exit
    EndSwitch
WEnd



Func _WM_CONTEXTMENU($hWnd, $iMsg, $wParam, $lParam)
    #forceref $hWnd, $iMsg, $wParam, $lParam
    Switch $wParam
        Case $hLv ;bypass other contextmenus
            ;If $lParam = -1 Then Return $GUI_RUNDEFMSG ;option: ignore menu appkey/Shift-F10
            Local $iRet, $iX, $iY, $aSel, $aPos, $iIdx, $tpoint, $fSysMenu = False
            If $lParam = -1 Then ;Keyboard: Menu Appkey/Shift-F10
                ;get last selected item (if one or more selected by keyboard))
                $aSel = _GUICtrlListView_GetSelectedIndices($wParam, True)
                If @error Or UBound($aSel) <= 1 Then Return $GUI_RUNDEFMSG
                If $aSel[$aSel[0]] = -1 Then Return $GUI_RUNDEFMSG
                $iIdx = $aSel[$aSel[0]]
                $aPos = _GUICtrlListView_GetItemPosition($wParam, $iIdx)
                $tpoint = DllStructCreate("int X;int Y")
                DllStructSetData($tpoint, "X", $aPos[0])
                DllStructSetData($tpoint, "Y", $aPos[1])
                _WinAPI_ClientToScreen($wParam, $tPoint)
                $iX = DllStructGetData($tpoint, "X")
                $iY = DllStructGetData($tpoint, "Y")
                $fSysMenu = True
            Else ;mouse right click on listview item
                $iX = BitAND($lParam, 0x0000FFFF)
                $iY = BitShift($lParam, 16)
                ;convert Screen to Client coordinates
                $tpoint = DllStructCreate("int X;int Y")
                DllStructSetData($tpoint, "X", $iX)
                DllStructSetData($tpoint, "Y", $iY)
                _WinAPI_ScreenToClient($wParam, $tpoint)
                Local $iYPos1 = DllStructGetData($tPoint, "Y")

                ;WM_NOTIFY NM_RCLICK notification is not sent for right clicks on header,
                ;so when using WM_CONTEXTMENU only, we have to check before hit test if click is on header
                ;by checking if Y position is less than the header height
                Local $tRect = DllStructCreate($tagRECT)
                Local $hHeader = _SendMessage($wParam, $LVM_GETHEADER, 0, 0, 0, "wparam", "lparam", "hwnd")
                $iRet = _SendMessage($hHeader, $HDM_GETITEMRECT, 0, DllStructGetPtr($tRect), 0, "wparam", "ptr")
                If @error Or $iRet < 0 Then Return $GUI_RUNDEFMSG
                Local $iYPos2 = DllStructGetData($tRect, "Bottom")-1
                If $iYPos2 <0 Then Return $GUI_RUNDEFMSG
                If $iYPos1 <= $iYPos2 Then Return $GUI_RUNDEFMSG

                ;Do ListView HitTest
                Local $tTest = DllStructCreate($tagLVHITTESTINFO)
                DllStructSetData($tTest, "X", DllStructGetData($tPoint, "X"))
                DllStructSetData($tTest, "Y", $iYPos1)
                $iRet = _SendMessage($wParam, $LVM_HITTEST, 0, DllStructGetPtr($tTest))
                If @error Or $iRet = -1 Then Return $GUI_RUNDEFMSG ; -1 = not on lv item, otherwise returns index
                Switch DllStructGetData($tTest, "Flags")
                    Case $LVHT_ONITEMICON, $LVHT_ONITEMLABEL, $LVHT_ONITEM ;hit was on item icon or 1st column item or subitem
                        $fSysMenu = True
                EndSwitch
            EndIf

            If $iX >= 0 And $iY >= 0 Then
                ;pop the listview contextmenu
                If $fSysMenu = True And _GUICtrlMenu_IsMenu($hLVCM) = 1 Then
                    _GUICtrlMenu_TrackPopupMenu($hLVCM, $hWnd, $iX, $iY, 1, 1, 1)
                    Return True
                EndIf
            EndIf
    EndSwitch
    Return $GUI_RUNDEFMSG
EndFunc   ;==>_WM_CONTEXTMENU

WM_NOTIFY, WM_CONTEXTMENU method

#include <GuiListView.au3>
#include <WindowsConstants.au3>
#include <GuiConstantsEx.au3>
#include <GuiMenu.au3>

Opt('MustDeclareVars', 1)

Global $hLv, $hLVCM, $fSysMenu = False ;globals required for _WM_NOTIFY() and _WM_CONTEXTMENU()

Local $hGui, $cLv, $msg, $cLVCM, $cMenu1, $a, $b, $c
$hGui = GUICreate('ListView ContextMenu', 410, 300, -1, -1)
$cLv = GUICtrlCreateListView('List of Items', 5, 5, 400, 290)
$hLv = GUICtrlGetHandle($cLv) ;get listview handle from control ID
_GUICtrlListView_SetExtendedListViewStyle($cLv, BitOR($LVS_EX_GRIDLINES, $LVS_EX_FULLROWSELECT))
_GUICtrlListView_SetColumnWidth($cLv, 0, 396)

For $i = 1 To 15
    GUICtrlCreateListViewItem('ITEM ' & $i, $cLv)
Next

;Create a dummy parent for the listview contextmenu menu so it will not popup on right click
;unless shown with _GUICtrlMenu_TrackPopupMenu()
$cMenu1 = GUICtrlCreateDummy()
$cLVCM = GUICtrlCreateContextMenu($cMenu1)
$hLVCM = GUICtrlGetHandle(-1)
$a = GUICtrlCreateMenuItem('LV Menu Item 1', $cLVCM)
$b = GUICtrlCreateMenuItem('LV Menu Item 2', $cLVCM)
$c = GUICtrlCreateMenuItem('LV Menu Item 3', $cLVCM)

GUIRegisterMsg($WM_NOTIFY, "_WM_NOTIFY")
GUIRegisterMsg($WM_CONTEXTMENU, "_WM_CONTEXTMENU")
GUISetState(@SW_SHOW)


While 1
    $msg = GUIGetMsg()
    Switch $msg
        Case 0
        Case $a
            ConsoleWrite("+ LV Menu Item 1" & @CRLF)
        Case $b
            ConsoleWrite("- LV Menu Item 2" & @CRLF)
        Case $c
            ConsoleWrite("> LV Menu Item 3" & @CRLF)
        Case -3
            GUIDelete($hGui)
            Exit
    EndSwitch
WEnd


Func _WM_CONTEXTMENU($hWnd, $iMsg, $wParam, $lParam)
    #forceref $hWnd, $iMsg, $wParam, $lParam
    Switch $wParam
        Case $hLv ;bypass other contextmenus
            ;If $lParam = -1 Then Return $GUI_RUNDEFMSG ;option: ignore menu appkey/Shift-F10
            Local $iRet, $iX, $iY, $aSel, $aPos, $iIdx, $tpoint;, $fSysMenu = False
            If $lParam = -1 Then ;Keyboard: Menu Appkey/Shift-F10
                ;get last selected item (if one or more selected by keyboard))
                $aSel = _GUICtrlListView_GetSelectedIndices($wParam, True)
                If @error Or UBound($aSel) <= 1 Then Return $GUI_RUNDEFMSG
                If $aSel[$aSel[0]] = -1 Then Return $GUI_RUNDEFMSG
                $iIdx = $aSel[$aSel[0]]
                $aPos = _GUICtrlListView_GetItemPosition($wParam, $iIdx)
                $tpoint = DllStructCreate("int X;int Y")
                DllStructSetData($tpoint, "X", $aPos[0])
                DllStructSetData($tpoint, "Y", $aPos[1])
                _WinAPI_ClientToScreen($wParam, $tPoint)
                $iX = DllStructGetData($tpoint, "X")
                $iY = DllStructGetData($tpoint, "Y")
                $fSysMenu = True
            Else ;mouse right click on listview item
                $iX = BitAND($lParam, 0x0000FFFF)
                $iY = BitShift($lParam, 16)
            EndIf

            If $iX >= 0 And $iY >= 0 Then
                ;pop the listview contextmenu
                If $fSysMenu = True And _GUICtrlMenu_IsMenu($hLVCM) = 1 Then
                    _GUICtrlMenu_TrackPopupMenu($hLVCM, $hWnd, $iX, $iY, 1, 1, 1)
                    $fSysMenu = False
                    Return True
                EndIf
            EndIf
    EndSwitch
    Return $GUI_RUNDEFMSG
EndFunc   ;==>_WM_CONTEXTMENU


Func _WM_NOTIFY($hWnd, $Msg, $wParam, $lParam)
    #forceref $hWnd, $Msg, $wParam
    Local $tNMHDR, $hWndFrom, $iCode
    $tNMHDR = DllStructCreate($tagNMHDR, $lParam)
    $hWndFrom = HWnd(DllStructGetData($tNMHDR, "hWndFrom"))
    $iCode = DllStructGetData($tNMHDR, "Code")
    Local $IDFrom = DllStructGetData($tNMHDR, "IDFrom")

    Switch $IDFrom
        Case $cLv
            Switch $iCode
                Case $NM_RCLICK ; determine if right click on an item
                    Local $tInfox = DllStructCreate($tagNMITEMACTIVATE, $lParam)
                    If @error Then Return $GUI_RUNDEFMSG
                    Local $Item = DllStructGetData($tInfox, "Index")
                    If @error Or $Item = -1 Then Return $GUI_RUNDEFMSG
                    Local $tTest = DllStructCreate($tagLVHITTESTINFO)
                    DllStructSetData($tTest, "X", DllStructGetData($tInfox, "X"))
                    DllStructSetData($tTest, "Y", DllStructGetData($tInfox, "Y"))
                    Local $iRet = GUICtrlSendMsg($IDFrom, $LVM_HITTEST, 0, DllStructGetPtr($tTest))
                    If @error Or $iRet = -1 Then Return $GUI_RUNDEFMSG ; -1 = not on lv item, otherwise returns index
                    Switch DllStructGetData($tTest, "Flags")
                        Case $LVHT_ONITEMICON, $LVHT_ONITEMLABEL, $LVHT_ONITEM ;hit was on item icon or 1st column item or subitem
                            $fSysMenu = True
                    EndSwitch
            EndSwitch
    EndSwitch
    Return $GUI_RUNDEFMSG
EndFunc   ;==>_WM_NOTIFY
Edited by rover

I see fascists...

Link to comment
Share on other sites

Thank you but this still does not solve the whole issue.

Although the Listview is not getting stuck anymore, it still waits until a menu item's event function finishes executing before it allows other menu items' functions to execute.

Link to comment
Share on other sites

Thank you but this still does not solve the whole issue.

Although the Listview is not getting stuck anymore, it still waits until a menu item's event function finishes executing before it allows other menu items' functions to execute.

I changed the code in the first example, I use the hit test in WM_NOTIFY method myself and ran into another issue coding the WM_CONTEXTMENU only example that the other method does not have.

Now, on to your comment.

AutoIt is a single threaded language, so each instruction is queued, and run after the previous finishes.

If you click all three menu items they will run sequentially.

The only way around this is to run multiple script processes and use inter process communication.

There are examples on the forum.

There is another method, but it is advanced and limited in what can be run.

This method runs code in another thread.

On the forum is a script that polls internet connectivity without blocking the main loop by running assembly code in another thread.

Although AU3 is not threadsafe, a method has been worked out using assembly code.

I don't know much about it beyond testing a few of the examples of which there are several on the forum.

So multi process communication may be your only way to go.

Edit:

You could use $iNotify = 3 (as Martin pointed out) with TrackPopupMenu and use the returned menu item in a conditional statement to send a message to a co-process to run functions instead of using main loop or on-event mode for functions in current process.

Edited by rover

I see fascists...

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...