Jump to content

Problem with ContextMenu


Sunaj
 Share

Recommended Posts

Hi,

Basically: the submenu entry in bottom of the context menu acts erroneously and not so when on top - I think that there may be a problem with GUICtrlCreateMenu, discovered it while fiddling about with menutips. Please scroll down to the part of the code where the context menu is initialized (or search for "!PROBLEM DESCRIPTION!") to see detailed description of problem. If run/debugged in SciTE: a consolewrite has been added to provide details when moving mouse over menuitems. Could of course also be a problem with the menutip system but it seems more and more unlikely the more I look at it (which is where this posting comes in :whistle:

Cheers,

Sunaj

#include <GUIConstants.au3>
#include <Array.au3>

; ################
; MENU TIP RELATED
; ################

Global Const $WM_MENUSELECT = 0x011F, $WM_TIMER = 0x0113, $WM_ENTERIDLE = 0x0121, $WM_QUERYENDSESSION = 0x0011
Global Const $WM_COPYDATA = 0x4A, $WM_COPY = 0x0301, $WM_CHANGECBCHAIN = 0x030D, $WM_DRAWCLIPBOARD = 0x0308
Global $hWnd_NEXT_IN_CLIP_CHAIN, $bInChain  ;flags to indicate app is in the notification chain.

Global $USE_TOOLTIP = True
Local $aCtrlArray[1], $aCtrlMsgArray[1], $ControlID
Global $defaultstatus = "Ready"
Global $status
Global $MenuItemId
Global $IDLECounter = 0 ; When 5 is reached tooltip is closed
Global $EVENT ; for msg loop related to menutip
Global $TIMERENABLED = False ; Flag set when SetTimer api is called
Global $TIP_TIMER ; Timestamp holder for tooltip visiblilty
Global Const $TIP_TIMER_ID = 999 ; Timer id for SetTimer api
Global Const $TIPSHOW = 1 ; Event
Global Const $TIPVISIBLE = 2 ; Event
Global Const $TIP_TTL = 3333 ; how long to show tooltip
Global Const $TIP_DELAY = 333 ; how long to wait to show tooltip
Global Const $MSG_INTERVAL = 33 ; Interval Windows will use to send

; ####################
; WINDOW MESSAGE HOOKS
; ####################

GUIRegisterMsg($WM_MENUSELECT, "MenuTipHandler") ; menu tip, change on move between menu items
GUIRegisterMsg($WM_ENTERIDLE, "MenuTipHandler") ; to make context menues disappear when moving pointer off menu without closing it
GUIRegisterMsg($WM_TIMER, "TimerCallBack") ; also menu tip related
GUISetOnEvent($GUI_EVENT_CLOSE, "exit_program") ; exit on standard stuff


$h_GUI = GUICreate("My GUI Context Menu", 300, 200)

$contextmenu = GUICtrlCreateContextMenu()

$button = GUICtrlCreateButton("OK", 100, 100, 70, 20)
$buttoncontext = GUICtrlCreateContextMenu($button)
$buttonitem = GUICtrlCreateMenuitem("About button", $buttoncontext)


$fileitem1 = GUICtrlCreateMenuitem("Open1", $contextmenu)
_AddCtrl($fileitem1, "test1")
$fileitem2 = GUICtrlCreateMenuitem("Open2", $contextmenu)
_AddCtrl($fileitem2, "test2")
$fileitem3 = GUICtrlCreateMenuitem("Open3", $contextmenu)
_AddCtrl($fileitem3, "test3")
$fileitem4 = GUICtrlCreateMenuitem("Open4", $contextmenu)
_AddCtrl($fileitem4, "test4")
$fileitem5 = GUICtrlCreateMenuitem("Open5", $contextmenu)
_AddCtrl($fileitem5, "test5")
$saveitem = GUICtrlCreateMenuitem("Save", $contextmenu)
GUICtrlCreateMenuitem("", $contextmenu)     ; separator
$infoitem = GUICtrlCreateMenuitem("Info", $contextmenu)
$newsubmenu = GUICtrlCreateMenu("new", $contextmenu) ; (!PROBLEM DESCRIPTION!) if you run this in scite you can see in the console that this menu is given the same internal id (the wparam) as the "Open2" menuitem.. this does NOT happen if the submenu is moved to the top of the menu, maybe suggesting a possible problem with GUICtrlCreateMenu (since this kinda ghosting does not happen with GUICtrlCreateMenuitem...?)
$textitem = GUICtrlCreateMenuitem("text", $newsubmenu)

GUISetState()

; Run the GUI until the dialog is closed
While 1
    $msg = GUIGetMsg()
    If $msg = $GUI_EVENT_CLOSE Then ExitLoop
WEnd

Func exit_program()
    Exit
EndFunc   ;==>exit_program


; Show a menu in a given GUI window which belongs to a given GUI ctrl
Func ShowMenu($hWnd, $CtrlID, $nContextID)
    Local $hMenu = GUICtrlGetHandle($nContextID)
    $arPos = ControlGetPos($hWnd, "", $CtrlID)
    Local $x = $arPos[0]
    Local $y = $arPos[1] + $arPos[3]
    ClientToScreen($hWnd, $x, $y)
    TrackPopupMenu($hWnd, $hMenu, $x, $y)
EndFunc   ;==>ShowMenu


; Convert the client (GUI) coordinates to screen (desktop) coordinates
Func ClientToScreen($hWnd, ByRef $x, ByRef $y)
    Local $stPoint = DllStructCreate("int;int")
    
    DllStructSetData($stPoint, 1, $x)
    DllStructSetData($stPoint, 2, $y)

    DllCall("user32.dll", "int", "ClientToScreen", "hwnd", $hWnd, "ptr", DllStructGetPtr($stPoint))
    
    $x = DllStructGetData($stPoint, 1)
    $y = DllStructGetData($stPoint, 2)
    ; release Struct not really needed as it is a local
    $stPoint = 0
EndFunc   ;==>ClientToScreen


; Show at the given coordinates (x, y) the popup menu (hMenu) which belongs to a given GUI window (hWnd)
Func TrackPopupMenu($hWnd, $hMenu, $x, $y)
    DllCall("user32.dll", "int", "TrackPopupMenuEx", "hwnd", $hMenu, "int", 0, "int", $x, "int", $y, "hwnd", $hWnd, "ptr", 0)
EndFunc   ;==>TrackPopupMenu

Func EventLoop()
    Select
        Case $EVENT = $TIPSHOW
            If $IDLECounter < 11 Then ; this if/then fixes 'quick move over' problem with context menu (not truely on the global menu)
                If $TIP_TIMER Then
                    If ((TimerDiff($TIP_TIMER) >= $TIP_DELAY) And ($MenuItemId > 0)) Then ShowMenuTip()
                EndIf
            EndIf
        Case $EVENT = $TIPVISIBLE
            If TimerDiff($TIP_TIMER) >= $TIP_TTL + $TIP_DELAY Then VoidMenuTip()
    EndSelect
EndFunc   ;==>EventLoop

Func _AddCtrl($ControlID, $ControlMsg)
    _ArrayAdd($aCtrlArray, $ControlID)
    _ArrayAdd($aCtrlMsgArray, $ControlMsg)
EndFunc   ;==>_AddCtrl

Func ShowMenuTip()
    For $x = 0 To UBound($aCtrlArray) - 1
        If $MenuItemId = ($aCtrlArray[$x]) Then
            ToolTip($aCtrlMsgArray[$x])
            $EVENT = $TIPVISIBLE
            $IDLECounter = 0
            ExitLoop
        EndIf
    Next
EndFunc   ;==>ShowMenuTip

Func VoidMenuTip()
    ToolTip("")
    $TIP_TIMER = 0
EndFunc   ;==>VoidMenuTip

Func StartTimer($hWndGUI, $TimerId, $Interval)
    If $TIMERENABLED = True Then StopTimer($hWndGUI, $TimerId)
    $retval = DllCall("User32.dll", "int", "SetTimer", "hwnd", $hWndGUI, "int", $TimerId, "int", $Interval, "int", 0)
    $TIMERENABLED = True
EndFunc   ;==>StartTimer

Func StopTimer($hWndGUI, $TimerId)
    $retval = DllCall("User32.dll", "int", "KillTimer", "hwnd", $hWndGUI, "int", $TimerId)
    $TIMERENABLED = False
EndFunc   ;==>StopTimer

Func TimerCallBack($hWndGUI, $MsgID, $WParam, $LParam)
    Local $TimerId = BitAND($WParam, 0xFFFF)
    If $TimerId = $TIP_TIMER_ID Then EventLoop()
    Return $GUI_RUNDEFMSG
EndFunc   ;==>TimerCallBack

Func MenuTipHandler($hWndGUI, $MsgID, $WParam, $LParam)
    $IDLECounter += 1
    If $IDLECounter > 4 Then ;make context tip disappear on move away
        ToolTip("")
    EndIf
    Local $id = BitAND($WParam, 0xFFFF)
    If $MsgID = $WM_MENUSELECT Then
        If $USE_TOOLTIP Then
            StartTimer($h_GUI, $TIP_TIMER_ID, $MSG_INTERVAL)
            $MenuItemId = $id
            ToolTip("")
            $IDLECounter = 0
            If $MenuItemId > 0 Then
                ConsoleWrite("$WParam: " & $WParam & "$LParam: " & $LParam & @CRLF)
                $TIP_TIMER = TimerInit()
                $EVENT = $TIPSHOW
            EndIf
        EndIf
    EndIf
    Return $GUI_RUNDEFMSG
EndFunc   ;==>MenuTipHandler
Link to comment
Share on other sites

Not a problem with GUICtrlCreateMenu() - a problem with getting the ID - a menuitem is not the same like a menu!

From MSDN: wParam...If the selected item opens a drop-down menu or submenu, this parameter contains the index of the drop-down menu or submenu in the main menu, and the lParam parameter contains the handle to the main (clicked) menu...

#include <GUIConstants.au3>
#include <Array.au3>

; ################
; MENU TIP RELATED
; ################

Global Const $WM_MENUSELECT = 0x011F, $WM_TIMER = 0x0113, $WM_ENTERIDLE = 0x0121, $WM_QUERYENDSESSION = 0x0011
Global Const $WM_COPYDATA = 0x4A, $WM_COPY = 0x0301, $WM_CHANGECBCHAIN = 0x030D, $WM_DRAWCLIPBOARD = 0x0308
Global $hWnd_NEXT_IN_CLIP_CHAIN, $bInChain  ;flags to indicate app is in the notification chain.

Global $USE_TOOLTIP = True
Local $aCtrlArray[1], $aCtrlMsgArray[1], $ControlID
Global $defaultstatus = "Ready"
Global $status
Global $MenuItemId
Global $IDLECounter = 0 ; When 5 is reached tooltip is closed
Global $EVENT ; for msg loop related to menutip
Global $TIMERENABLED = False ; Flag set when SetTimer api is called
Global $TIP_TIMER ; Timestamp holder for tooltip visiblilty
Global Const $TIP_TIMER_ID = 999 ; Timer id for SetTimer api
Global Const $TIPSHOW = 1 ; Event
Global Const $TIPVISIBLE = 2 ; Event
Global Const $TIP_TTL = 3333 ; how long to show tooltip
Global Const $TIP_DELAY = 333 ; how long to wait to show tooltip
Global Const $MSG_INTERVAL = 33 ; Interval Windows will use to send
Global Const $MF_POPUP = 0x00000010 ; Drop-down menu or submenu
Global Const $MIIM_ID = 0x00000002 ; Menu item ID

; ####################
; WINDOW MESSAGE HOOKS
; ####################

GUIRegisterMsg($WM_MENUSELECT, "MenuTipHandler") ; menu tip, change on move between menu items
GUIRegisterMsg($WM_ENTERIDLE, "MenuTipHandler") ; to make context menues disappear when moving pointer off menu without closing it
GUIRegisterMsg($WM_TIMER, "TimerCallBack") ; also menu tip related
GUISetOnEvent($GUI_EVENT_CLOSE, "exit_program") ; exit on standard stuff


$h_GUI = GUICreate("My GUI Context Menu", 300, 200)

$contextmenu = GUICtrlCreateContextMenu()

$button = GUICtrlCreateButton("OK", 100, 100, 70, 20)
$buttoncontext = GUICtrlCreateContextMenu($button)
$buttonitem = GUICtrlCreateMenuitem("About button", $buttoncontext)


$fileitem1 = GUICtrlCreateMenuitem("Open1", $contextmenu)
_AddCtrl($fileitem1, "test1")
$fileitem2 = GUICtrlCreateMenuitem("Open2", $contextmenu)
_AddCtrl($fileitem2, "test2")
$fileitem3 = GUICtrlCreateMenuitem("Open3", $contextmenu)
_AddCtrl($fileitem3, "test3")
$fileitem4 = GUICtrlCreateMenuitem("Open4", $contextmenu)
_AddCtrl($fileitem4, "test4")
$fileitem5 = GUICtrlCreateMenuitem("Open5", $contextmenu)
_AddCtrl($fileitem5, "test5")
$saveitem = GUICtrlCreateMenuitem("Save", $contextmenu)
_AddCtrl($saveitem, "saveitem")
GUICtrlCreateMenuitem("", $contextmenu)     ; separator
$infoitem = GUICtrlCreateMenuitem("Info", $contextmenu)
_AddCtrl($infoitem, "infoitem")
$newsubmenu = GUICtrlCreateMenu("new", $contextmenu) ; (!PROBLEM DESCRIPTION!) if you run this in scite you can see in the console that this menu is given the same internal id (the wparam) as the "Open2" menuitem.. this does NOT happen if the submenu is moved to the top of the menu, maybe suggesting a possible problem with GUICtrlCreateMenu (since this kinda ghosting does not happen with GUICtrlCreateMenuitem...?)
_AddCtrl($newsubmenu, "newsubmenu")
$textitem = GUICtrlCreateMenuitem("text", $newsubmenu)
_AddCtrl($textitem, "textitem")

GUISetState()

; Run the GUI until the dialog is closed
While 1
    $msg = GUIGetMsg()
    If $msg = $GUI_EVENT_CLOSE Then ExitLoop
WEnd

Func exit_program()
    Exit
EndFunc   ;==>exit_program


; Show a menu in a given GUI window which belongs to a given GUI ctrl
Func ShowMenu($hWnd, $CtrlID, $nContextID)
    Local $hMenu = GUICtrlGetHandle($nContextID)
    $arPos = ControlGetPos($hWnd, "", $CtrlID)
    Local $x = $arPos[0]
    Local $y = $arPos[1] + $arPos[3]
    ClientToScreen($hWnd, $x, $y)
    TrackPopupMenu($hWnd, $hMenu, $x, $y)
EndFunc   ;==>ShowMenu


; Convert the client (GUI) coordinates to screen (desktop) coordinates
Func ClientToScreen($hWnd, ByRef $x, ByRef $y)
    Local $stPoint = DllStructCreate("int;int")
   
    DllStructSetData($stPoint, 1, $x)
    DllStructSetData($stPoint, 2, $y)

    DllCall("user32.dll", "int", "ClientToScreen", "hwnd", $hWnd, "ptr", DllStructGetPtr($stPoint))
   
    $x = DllStructGetData($stPoint, 1)
    $y = DllStructGetData($stPoint, 2)
    ; release Struct not really needed as it is a local
    $stPoint = 0
EndFunc   ;==>ClientToScreen


; Show at the given coordinates (x, y) the popup menu (hMenu) which belongs to a given GUI window (hWnd)
Func TrackPopupMenu($hWnd, $hMenu, $x, $y)
    DllCall("user32.dll", "int", "TrackPopupMenuEx", "hwnd", $hMenu, "int", 0, "int", $x, "int", $y, "hwnd", $hWnd, "ptr", 0)
EndFunc   ;==>TrackPopupMenu

Func EventLoop()
    Select
        Case $EVENT = $TIPSHOW
            If $IDLECounter < 11 Then ; this if/then fixes 'quick move over' problem with context menu (not truely on the global menu)
                If $TIP_TIMER Then
                    If ((TimerDiff($TIP_TIMER) >= $TIP_DELAY) And ($MenuItemId > 0)) Then ShowMenuTip()
                EndIf
            EndIf
        Case $EVENT = $TIPVISIBLE
            If TimerDiff($TIP_TIMER) >= $TIP_TTL + $TIP_DELAY Then VoidMenuTip()
    EndSelect
EndFunc   ;==>EventLoop

Func _AddCtrl($ControlID, $ControlMsg)
    _ArrayAdd($aCtrlArray, $ControlID)
    _ArrayAdd($aCtrlMsgArray, $ControlMsg)
EndFunc   ;==>_AddCtrl

Func ShowMenuTip()
    For $x = 0 To UBound($aCtrlArray) - 1
        If $MenuItemId = ($aCtrlArray[$x]) Then
            ToolTip($aCtrlMsgArray[$x])
            $EVENT = $TIPVISIBLE
            $IDLECounter = 0
            ExitLoop
        EndIf
    Next
EndFunc   ;==>ShowMenuTip

Func VoidMenuTip()
    ToolTip("")
    $TIP_TIMER = 0
EndFunc   ;==>VoidMenuTip

Func StartTimer($hWndGUI, $TimerId, $Interval)
    If $TIMERENABLED = True Then StopTimer($hWndGUI, $TimerId)
    $retval = DllCall("User32.dll", "int", "SetTimer", "hwnd", $hWndGUI, "int", $TimerId, "int", $Interval, "int", 0)
    $TIMERENABLED = True
EndFunc   ;==>StartTimer

Func StopTimer($hWndGUI, $TimerId)
    $retval = DllCall("User32.dll", "int", "KillTimer", "hwnd", $hWndGUI, "int", $TimerId)
    $TIMERENABLED = False
EndFunc   ;==>StopTimer

Func TimerCallBack($hWndGUI, $MsgID, $WParam, $LParam)
    Local $TimerId = BitAND($WParam, 0xFFFF)
    If $TimerId = $TIP_TIMER_ID Then EventLoop()
    Return $GUI_RUNDEFMSG
EndFunc   ;==>TimerCallBack

Func MenuTipHandler($hWndGUI, $MsgID, $WParam, $LParam)
    $IDLECounter += 1
    If $IDLECounter > 4 Then ;make context tip disappear on move away
        ToolTip("")
    EndIf
    Local $id = BitAND($WParam, 0xFFFF)
    
    If $MsgID = $WM_MENUSELECT Then
        Local $Flags = BitAnd(BitShift($wParam, 16), 0xFFFF)
        
        If BitAnd($Flags, $MF_POPUP) Then
            Local $stMII = DllStructCreate("uint;uint;uint;uint;uint;hwnd;hwnd;hwnd;ptr;ptr;uint;hwnd")
            DllStructSetData($stMII, 1, DllStructGetSize($stMII))
            DllStructSetData($stMII, 2, $MIIM_ID)
            If GetMenuItemInfoA($LParam, $id, TRUE, DllStructGetPtr($stMII)) Then $id = DllStructGetData($stMII, 5)
        EndIf
    
        If $USE_TOOLTIP Then
            StartTimer($h_GUI, $TIP_TIMER_ID, $MSG_INTERVAL)
            $MenuItemId = $id
            ToolTip("")
            $IDLECounter = 0
            If $MenuItemId > 0 Then
                ConsoleWrite("$WParam: " & $WParam & "$LParam: " & $LParam & @CRLF)
                $TIP_TIMER = TimerInit()
                $EVENT = $TIPSHOW
            EndIf
        EndIf
    EndIf
    Return $GUI_RUNDEFMSG
EndFunc   ;==>MenuTipHandler


Func GetMenuItemInfoA($hMenu, $nItem, $bByPosition, $pMII)
    Local $bResult = DllCall("user32.dll", "int", "GetMenuItemInfo", _
                                                    "hwnd", $hMenu, _
                                                    "int", $nItem, _
                                                    "int", $bByPosition, _
                                                    "ptr", $pMII)
    Return $bResult[0]
EndFunc

Greets

Holger

Edited by Holger
Link to comment
Share on other sites

Update: sorry i had not seen your superb work on the code - simply took it for granted that you just replied in text. Everything works as it should - thanks again! :whistle:

Ok, thanks for the clarification Holger. Do you have an idea/explanation as to the fact that the submenu is given a unique id if placed at the top of the menu and not so if its placed at the bottom - and, if yes, how do think I could get the submenu to have a unique id when placed at the bottom (is it possible to change the id..?)? Thanks for your help here, I realize it takes time to look into this subject!

Greets,

Sunaj

Not a problem with GUICtrlCreateMenu() - a problem with getting the ID - a menuitem is not the same like a menu!

From MSDN: wParam...If the selected item opens a drop-down menu or submenu, this parameter contains the index of the drop-down menu or submenu in the main menu, and the lParam parameter contains the handle to the main (clicked) menu...

Edited by Sunaj
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...