Jump to content
pixelsearch

[Solved] WM_CONTEXTMENU

Recommended Posts

Hi everybody :)

I don't know if what follows is accurate, but here is what I just tested :

1) WM_CONTEXTMENU is a message that seems always sent by a GUI, as soon as you right-click inside the GUI, no matter you created a contextual menu or not (you should have created one but this is for test purpose)
The following script shows this behavior, while displaying in the Console some useful informations :

#include <GUIConstantsEx.au3>
#include <MsgBoxConstants.au3>
#include <StaticConstants.au3>
#include <WindowsConstants.au3>

If ProcessExists("SciTE.exe") = 0 Then
    MsgBox($MB_TOPMOST, "SciTE", _
        "Please run from SciTE for ConsoleWrite to display")
EndIf

$hGUI = GUICreate("", 500, 200)
WinSetTitle($hGui, "", "GUI Handle : " & $hGUI & " - right-click inside GUI")

$idControl = GUICtrlCreateLabel("", _
    20, 20, 200, 40, BitOr($SS_CENTERIMAGE, $SS_CENTER))
GUICtrlSetBkColor(-1, 0xFFA060) ; orange

$hControl = GUICtrlGetHandle($idControl)
GUICtrlSetData($idControl, "Label handle : " & $hControl)

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

While 1
    Switch GUIGetMsg()
        Case $GUI_EVENT_CLOSE
            ExitLoop
    EndSwitch
WEnd

GUIDelete($hGUI)

;=================================================
Func WM_CONTEXTMENU($hwnd, $iMsg, $wParam, $lParam)
    #forceref $iMsg

    Local Static $iCounter = 0
    $iCounter += 1

    Local $iXPos = BitAND($lParam, 0xFFFF) ; X pos of cursor when right-click (screen coords)
    Local $iYPos = BitShift($lParam, 16)   ; Y pos of the cursor

    ConsoleWrite("WM_CONTEXTMENU #" & $iCounter & _
        "   $hwnd = "   & $hwnd & "   $wParam = " &  $wParam & _
        "   $iXPos = " & $iXPos & "   $iYPos = "  & $iYPos & @crlf)

    Return $GUI_RUNDEFMSG
EndFunc

2) Be careful if "Case $GUI_EVENT_SECONDARYDOWN" is found in your While... Wend loop, because it seems to always be triggered before WM_CONTEXTMENU
Also, it may happen that code within "Case $GUI_EVENT_SECONDARYDOWN" will not be executed completely before WM_CONTEXTMENU is triggered, here is an example :

#include <GUIConstantsEx.au3>
#include <MsgBoxConstants.au3>
#include <WindowsConstants.au3>

If ProcessExists("SciTE.exe") = 0 Then
    MsgBox($MB_TOPMOST, "SciTE", _
        "Please run from SciTE for ConsoleWrite to display")
EndIf

Global $iFlag = False

$hGUI = GUICreate("Right-click inside GUI", 500, 200)

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

While 1
    Switch GUIGetMsg()
        Case $GUI_EVENT_CLOSE
            ExitLoop

        Case $GUI_EVENT_SECONDARYDOWN
            ConsoleWrite("$GUI_EVENT_SECONDARYDOWN BEGIN" & @crlf)
            $iFlag = False
            Sleep(1000) ; <===================================== (change it to 10)
            $iFlag = True
            ConsoleWrite("$GUI_EVENT_SECONDARYDOWN END" & @crlf)

    EndSwitch
WEnd

GUIDelete($hGUI)

;=================================================
Func WM_CONTEXTMENU($hwnd, $iMsg, $wParam, $lParam)
    #forceref $hwnd, $iMsg, $wParam, $lParam

    Local Static $iCounter = 0
    $iCounter += 1

    ConsoleWrite("WM_CONTEXTMENU #" & $iCounter & _
        "   $iFlag = " & $iFlag & @crlf)

    Return $GUI_RUNDEFMSG
EndFunc

Console display with Sleep(1000) in the script above

$GUI_EVENT_SECONDARYDOWN BEGIN
WM_CONTEXTMENU #1   $iFlag = False
$GUI_EVENT_SECONDARYDOWN END

 If you change the line to Sleep(10), then Console should display :

$GUI_EVENT_SECONDARYDOWN BEGIN
$GUI_EVENT_SECONDARYDOWN END
WM_CONTEXTMENU #1   $iFlag = True

Which means that one should always have in mind that (global) variables redefined during "Case $GUI_EVENT_SECONDARYDOWN" may not have the same values inside the WM_CONTEXTMENU() function.

Note: this Sleep(1000) or Sleep(10) is just a way of showing that the time (in ms) spent within "Case $GUI_EVENT_SECONDARYDOWN" depends on the code (number of lines, variables definition etc...) found in that part of the script.

3) To prevent this issue, a solution could be to avoid using "Case $GUI_EVENT_SECONDARYDOWN" when WM_CONTEXTMENU is registered in a script, especially if variables are shared between both parts of code.

4) Why do I expose all this ?
* Actually my real script has a contextual menu and a "Case $GUI_EVENT_SECONDARYDOWN"
* WM_CONTEXTMENU is not registered in the real script, so everything works fine !

But what I would like to do now is to prevent sometimes the context menu to be displayed, when the user right-click duration is more than 250ms (which will be processed differently than displaying a context menu)

To do this, I need to register WM_CONTEXTMENU and Return 0 when the user right-click duration is > 250ms (Return 0 means "do not display the context menu")

Also, I won't be able to use TimerInit() => TimerDiff() within "Case $GUI_EVENT_SECONDARYDOWN", the reason is explained in 2)
So I guess I'll have to choose the solution described in 3) which is : avoid using "Case $GUI_EVENT_SECONDARYDOWN" when WM_CONTEXTMENU is registered in a script.

The code moved from "Case $GUI_EVENT_SECONDARYDOWN" to Func WM_CONTEXTMENU() will probably have a different scope.

Please comment if you got ideas & remarks, thanks !

Edited by pixelsearch
Title changed to [Solved]

Share this post


Link to post
Share on other sites

@pixelsearch  

1)  I think it is quite normal that it is call whenever you right-click, because you may want to create a context-menu on the fly within the WM_CONTEXTMENU proc depending on different conditions (one of my scripts actually is doing so)

2) There should not be any form of Sleep within the GUI loop, especially a large one like 1+ sec.  And ideally, you should try to reduce usage of Global as much as possible.  Usage of dummy controls can also help synchronizing events, instead of manipulating multiple global variables in the WINPROC.

If you could make a snippet of the issue you are having with the 250ms delay, we could try to find a solution.

 

Share this post


Link to post
Share on other sites

@Nine: Thanks for your reply :)

4 hours ago, Nine said:

There should not be any form of Sleep within the GUI loop, especially a large one like 1+ sec

I did explain the meaning of this "false Sleep", with this sentence, please let me requote it here :

5 hours ago, pixelsearch said:

Note: this Sleep(1000) or Sleep(10) is just a way of showing that the time (in ms) spent within "Case $GUI_EVENT_SECONDARYDOWN" depends on the code (number of lines, variables definition etc...) found in that part of the script.

As Nine suggested, I'll post now 3 short snippets, without any GDI+ functions, no main GUI, no parent & child GUI etc... just trying to explain the logic of each snippet.

1) First snippet corresponds to an old version of the script, which worked very fine.
During the main While... Wend loop, the duration of a right click determines which action will be done :
* Short right-click => Save an image
* Long right-click ( > 250ms) => Cancel Crop (which was done in a precedent step)
No context menu is associated to the GUI, no message registered. This version worked fine during months :

#include <GUIConstantsEx.au3>
#include <MsgBoxConstants.au3>
#include <WindowsConstants.au3>

$hGUI = GUICreate("Test #1 - Right-click (short then long) inside GUI", 500, 200, _
    -1, -1, -1, $WS_EX_TOPMOST)

GUISetState(@SW_SHOW, $hGUI)

While 1
    Switch GUIGetMsg()
        Case $GUI_EVENT_CLOSE
            ExitLoop

        Case $GUI_EVENT_SECONDARYDOWN
            Local $aPos = GUIGetCursorInfo($hGUI)
            If Not IsArray($aPos) Then ContinueLoop

            Local $hTimer = TimerInit()
            While $aPos[3] = 1 ; Secondary down (1 if pressed, 0 if not pressed)
                Sleep(50) ; better than Sleep(100)
                $aPos = GUIGetCursorInfo($hGUI)
            Wend
            Local $fDiff = TimerDiff($hTimer)

            If $fDiff < 250 Then
                ConsoleWrite("Right-click < 250ms (save image)" & @crlf)
                ; code for save image here (after confirmation)
            Else
                ConsoleWrite("Right-click >= 250ms (cancel crop)" & @crlf)
                ; code for cancel crop here
            EndIf
    EndSwitch
WEnd

GUIDelete($hGUI)

2) Second snippet below corresponds to the actual version of the script, which works without any error since November 6th :
A context menu is associated to the Gui (in fact it's associated to an image control in the real script, no big deal)
A right-click, no matter its duration, displays the context menu where the user can choose to save the image or cancel the crop (plus other context menu options not in this snippet, like Delete, Hide tooltip, Quit preview etc...)
No message is registered (i.e no WM_CONTEXTMENU is registered in this actual version) :

; $iClickStatus discussed here :
; https://www.autoitscript.com/forum/topic/204317-solved-how-to-eat-left-clicks/?do=findComment&comment=1468072

#include <GUIConstantsEx.au3>
#include <MsgBoxConstants.au3>
#include <WindowsConstants.au3>

$hGUI = GUICreate("Test #2 - Right-click inside GUI", 500, 200, _
    -1, -1, -1, $WS_EX_TOPMOST)

Local $idContext_Menu = GUICtrlCreateContextMenu()
Local $idContext_Save = GUICtrlCreateMenuItem("Save", $idContext_Menu)
GUICtrlCreateMenuItem("", $idContext_Menu)
Local $idContext_CancelCrop = GUICtrlCreateMenuItem("Cancel Crop", $idContext_Menu)

GUISetState(@SW_SHOW, $hGUI)

While 1
    Switch GUIGetMsg()
        Case $GUI_EVENT_CLOSE
            ExitLoop

        Case $GUI_EVENT_SECONDARYDOWN
            Local $aPos = GUIGetCursorInfo($hGUI)
            If Not IsArray($aPos) Then
                ; $iClickStatus = 0
                ContinueLoop
            Else
                ; $iClickStatus = 2
            EndIf

        Case $idContext_Save
            ConsoleWrite("Code for Save Image" & @crlf)

        Case $idContext_CancelCrop
            ConsoleWrite("Code for Cancel Crop" & @crlf)
    EndSwitch
WEnd

GUIDelete($hGUI)

3) Third snippet below is just the bad idea discussed in my 1st post : what would happen if we mix the logic of the 2 scripts above ?

* Allow again the user to right-click > 250ms to perform a crop cancel without displaying the context menu
* When user right-click < 250ms then the context menu is displayed
* Remove the "Crop Cancel" item from the context menu (this last point to be discussed as there's no harm to keep it in)

#include <GUIConstantsEx.au3>
#include <MsgBoxConstants.au3>
#include <WindowsConstants.au3>

Global $bContextDisplay = True

$hGUI = GUICreate("Test #3 - Right-click (short then long) inside GUI", 500, 200, _
    -1, -1, -1, $WS_EX_TOPMOST)

Local $idContext_Menu = GUICtrlCreateContextMenu()
Local $idContext_Save = GUICtrlCreateMenuItem("Save", $idContext_Menu)

GUISetState(@SW_SHOW, $hGUI)

GUIRegisterMsg($WM_CONTEXTMENU, "WM_CONTEXTMENU")

While 1
    Switch GUIGetMsg()
        Case $GUI_EVENT_CLOSE
            ExitLoop

        Case $GUI_EVENT_SECONDARYDOWN
            $bContextDisplay = True

            Local $aPos = GUIGetCursorInfo($hGUI)
            If Not IsArray($aPos) Then ContinueLoop

            Local $hTimer = TimerInit()
            While $aPos[3] = 1 ; Secondary down (1 if pressed, 0 if not pressed)
                Sleep(50) ; better than Sleep(100)
                $aPos = GUIGetCursorInfo($hGUI)
            Wend

            Local $fDiff = TimerDiff($hTimer)
            If $fDiff > 250 Then ; Cancel crop
                $bContextDisplay = False
                ConsoleWrite("Code for Cancel Crop" & @crlf)
            EndIf

        Case $idContext_Save
            ConsoleWrite("Code for Save Image" & @crlf)

    EndSwitch
WEnd

GUIDelete($hGUI)

;=================================================
Func WM_CONTEXTMENU($hwnd, $iMsg, $wParam, $lParam)
    #forceref $hwnd, $iMsg, $wParam, $lParam

    Local Static $iCounter = 0
    $iCounter += 1
    ConsoleWrite("WM_CONTEXTMENU #" & $iCounter & @crlf)

    If $bContextDisplay = False Then Return 0 ; prevents the Context menu to appear
    Return $GUI_RUNDEFMSG
EndFunc

As you will notice when you run this silly 3rd script, when the user right-clicks > 250ms, the variable $bContextDisplay never equals to False before WM_CONTEXTMENU()  is triggered . In fact, no matter how long the user right-click lasts, the context menu is always displayed, very bad scripting !

Tests show that Case $GUI_EVENT_PRIMARYDOWN is immediately triggered by a right-click, but its inside loop While $aPos[3] = 1 ... Wend will be done "in 2 times" :
* After about 60-70ms, Autoit will stop executing it and will trigger WM_CONTEXTMENU() while $bContextDisplay is still equal to True, which will display the context menu (while the user is still right-clicking) : everything is going wrong !

* Then, after WM_CONTEXTMENU() has ended, the loop While $aPos[3] = 1 ... Wend will terminate and now only $bContextDisplay = False, which is much too late as WM_CONTEXTMENU() has already been executed.

Moving the While $aPos[3] = 1 ... Wend from "Case $GUI_EVENT_PRIMARYDOWN" to "Func WM_CONTEXTMENU" would be a terrible idea too, because we know that a registered message shouldn't contain any "waiting" code, especially the user could right-click during more than 250ms

imho there is no need to try to improve anything. Version #2 above is perfect now, let's forget the "long" right-clicks and the registration of WM_CONTEXTMENU() when "Case $GUI_EVENT_PRIMARYDOWN" is present in a script. Canceling the crop in version #2 is just a click away after the context menu is always displayed.

I guess I have some strange thoughts sometimes (lol) but I was a bit surprised, when I started this topic, to notice that not only Case $GUI_EVENT_PRIMARYDOWN was triggered first, then followed by the registered WM_CONTEXTMENU (Nine explained why in his answer) but also to notice that after a bunch of ms (60-70), WM_CONTEXTMENU was triggered when "Case $GUI_EVENT_PRIMARYDOWN" hadn't time enough to set the variable $bContextDisplay to False.

Thanks for reading :)

Share this post


Link to post
Share on other sites

That seems to be working fine.  As I said there shouldn't be any form of Sleep (even a small loop within the GUI loop can be calculated as a sleep).  So by removing the sleep and creating a pseudo-loop outside the Switch, I think I got the result you were looking for :

#include <GUIConstantsEx.au3>
#include <MsgBoxConstants.au3>
#include <WindowsConstants.au3>

Global $bContextDisplay = False

$hGUI = GUICreate("Test #3 - Right-click (short then long) inside GUI", 500, 200, _
     -1, -1, -1, $WS_EX_TOPMOST)

Local $idContext_Menu = GUICtrlCreateContextMenu()
Local $idContext_Save = GUICtrlCreateMenuItem("Save", $idContext_Menu)

GUISetState(@SW_SHOW, $hGUI)

GUIRegisterMsg($WM_CONTEXTMENU, "WM_CONTEXTMENU")

Local $hTimer, $aPos, $fDiff

While 1
  Switch GUIGetMsg()
    Case $GUI_EVENT_CLOSE
      ExitLoop

    Case $GUI_EVENT_SECONDARYDOWN
      ConsoleWrite("secondary" & @CRLF)
      $bContextDisplay = True
      $hTimer = TimerInit()
    Case $GUI_EVENT_SECONDARYUP
      $bContextDisplay = False
    Case $idContext_Save
      ConsoleWrite("Code for Save Image" & @CRLF)

  EndSwitch

  If $bContextDisplay Then
    $aPos = GUIGetCursorInfo($hGUI)
    If Not IsArray($aPos) Then
      $bContextDisplay = False
      ContinueLoop
    EndIf
    If $aPos[3] = 1 Then
      $fDiff = TimerDiff($hTimer)
      ConsoleWrite("timer diff " & $fDiff & @CRLF)
      If $fDiff > 250 Then   ; Cancel crop
        $bContextDisplay = False
        ConsoleWrite("Code for Cancel Crop" & @CRLF)
      EndIf
    EndIf
  EndIf
WEnd

GUIDelete($hGUI)

;=================================================
Func WM_CONTEXTMENU($hwnd, $iMsg, $wParam, $lParam)
  #forceref $hwnd, $iMsg, $wParam, $lParam

  Local Static $iCounter = 0
  $iCounter += 1
  ConsoleWrite("WM_CONTEXTMENU #" & $iCounter & @CRLF)

  If Not $bContextDisplay Then Return 0   ; prevents the Context menu to appear
  Return $GUI_RUNDEFMSG
EndFunc   ;==>WM_CONTEXTMENU

 

Share this post


Link to post
Share on other sites

Thanks Nine, very interesting !
Especially I spent some time too this morning working on $GUI_EVENT_SECONDARYUP, leading me to nowhere, because I didn't create a pseudo-loop outside the Switch (like you did).

I modified below a couple of lines in your script to solve an issue that may happen :

#include <GUIConstantsEx.au3>
#include <MsgBoxConstants.au3>
#include <WindowsConstants.au3>

Global $bContextDisplay = False

$hGUI = GUICreate("Test #4 - Right-click (short then long) inside GUI", 500, 200, _
     -1, -1, -1, $WS_EX_TOPMOST)

Local $idContext_Menu = GUICtrlCreateContextMenu()
Local $idContext_Save = GUICtrlCreateMenuItem("Save", $idContext_Menu)

GUISetState(@SW_SHOW, $hGUI)

GUIRegisterMsg($WM_CONTEXTMENU, "WM_CONTEXTMENU")

Local $hTimer, $aPos, $fDiff

While 1
  Switch GUIGetMsg()
    Case $GUI_EVENT_CLOSE
      ExitLoop

    Case $GUI_EVENT_SECONDARYDOWN
      ConsoleWrite("--------------" & @CRLF & "secondary down" & @CRLF)
      $bContextDisplay = True
      $hTimer = TimerInit()

    Case $GUI_EVENT_SECONDARYUP
      ConsoleWrite("secondary up" & @CRLF)
      $bContextDisplay = False

    Case $idContext_Save
      ConsoleWrite("Code for Save Image" & @CRLF)

  EndSwitch

  If $bContextDisplay Then
    $aPos = GUIGetCursorInfo($hGUI)
    If Not IsArray($aPos) Then
      $bContextDisplay = False
      ContinueLoop
    EndIf
    If $aPos[3] = 1 Then
      $fDiff = TimerDiff($hTimer)
      ; ConsoleWrite("timer diff " & $fDiff & @CRLF)

      ; If $fDiff > 250 Then  ; Cancel crop
      If $fDiff > 250 And $bContextDisplay Then  ; Cancel crop <=================

        $bContextDisplay = False
        ConsoleWrite("Code for Cancel Crop" & @CRLF)
      EndIf
    EndIf
  EndIf

WEnd

GUIDelete($hGUI)

;=================================================
Func WM_CONTEXTMENU($hwnd, $iMsg, $wParam, $lParam)
  #forceref $hwnd, $iMsg, $wParam, $lParam

  Local Static $iCounter = 0
  $iCounter += 1

  If Not $bContextDisplay Then
    ConsoleWrite("WM_CONTEXTMENU #" & $iCounter & " (not displayed)" & @CRLF)
    Return 0   ; prevents the Context menu to appear
  Else
    ConsoleWrite("WM_CONTEXTMENU #" & $iCounter & " (visible)" & @CRLF)
    $bContextDisplay = False ; <=================
    Return $GUI_RUNDEFMSG
  EndIf
EndFunc   ;==>WM_CONTEXTMENU

Let's forget the additional informative ConsoleWrite(s), here is what I added/modified :

; If $fDiff > 250 Then  ; Cancel crop
If $fDiff > 250 And $bContextDisplay Then  ; Cancel crop
...
$bContextDisplay = False ; added in Func WM_CONTEXTMENU()

If we don't do that (i.e running your script "as-is") then a short right-click would correctly display the context menu :

--------------
secondary down
WM_CONTEXTMENU #1 (visible)

Now if the user doesn't choose any option in the menu but right-clicks again (shortly) in the GUI, the context menu will reappear but...

--------------
secondary down
WM_CONTEXTMENU #1 (visible)
Code for Cancel Crop <=============
secondary up
--------------
secondary down
WM_CONTEXTMENU #2 (visible)

Code for Cancel Crop was executed when it shouldn't have been at all.

Which leads to another question : where should this "Code for Cancel Crop" be placed in the script ? In fact, could it be placed somewhere else ?

Because when you run your original script and right-click > 250ms then the Console displays what follows, while the right mouse button is still pressed :

--------------
secondary down
Code for Cancel Crop

And just for the record, when you now release the right mouse button (after 250ms), then it's interesting to see (at last !) the "WM_CONTEXTMENU #1 (not displayed)" in the Console :

--------------
secondary down
Code for Cancel Crop
WM_CONTEXTMENU #1 (not displayed)
secondary up

Here again, the "Code for Cancel Crop" shouldn't have been executed so soon, it should be executed only after the user releases the right mouse button Though It may not be a major issue for the user to have his "backed up original uncropped" pic reappear while he is still pressing RMB (I hope it will not be an issue), I'm just wondering if we could avoid it and execute the "Code for Cancel Crop" only after RMB is released (as in the 1st snippet in my 2nd post above, which corresponds to the 1st version of the script, when there was no context menu at all)

Thanks a lot, Nine, as you already offered a big help teaching me (and probably other users) to "avoid any form of 'Sleep' within the GUI loop".

I knew we had to avoid it absolutely within registered messages functions, but I didn't know it had to be avoided too inside the GUI loop :)

Edited by pixelsearch
typo

Share this post


Link to post
Share on other sites

Glad I could help.  And you are absolutely right about negating $bContextDisplay in WM_CONTEXTMENU.  I was also able to replicate the issue you found.  Good catch. But I don't think it is necessary to confirm $bContextDisplay in this statement as it has already been checked few lines before.

If $fDiff > 250 And $bContextDisplay Then  ; Cancel crop

It would mean that this block on code could be interrupted in some way.  Which I do not see how with this script.

Share this post


Link to post
Share on other sites

My mind was so filled with the script that I forgot the 'like' button :)

So, negating $bContextDisplay in WM_CONTEXTMENU was a good thing. I wonder why I modified the other line you mentioned. I'll let you know... in case I ever remember the reason !

Share this post


Link to post
Share on other sites

Hi Nine :)
Could you please have a look at the following ?

Rule #1 - $GUI_EVENT_SECONDARYDOWN doesn't trigger WM_CONTEXTMENU

Rule #2 -  WM_CONTEXTMENU is triggered as soon as the user physically releases RMB, never before.

Rule #3 - $GUI_EVENT_SECONDARYUP is triggered after WM_CONTEXTMENU has ended, never before.

Based on these 3 personal rules, I modified the script, trying a new approach :

#include <GUIConstantsEx.au3>
#include <MsgBoxConstants.au3>
#include <WindowsConstants.au3>

Global $hTimer = 0

$hGUI = GUICreate("Test #5 - Right-click (short then long) inside GUI", 500, 200, _
     -1, -1, -1, $WS_EX_TOPMOST)

Local $idContext_Menu = GUICtrlCreateContextMenu()
Local $idContext_Save = GUICtrlCreateMenuItem("Save", $idContext_Menu)

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

While 1
    Switch GUIGetMsg()
        Case $GUI_EVENT_CLOSE
            ExitLoop

        Case $GUI_EVENT_SECONDARYDOWN
            ConsoleWrite("--------------" & @CRLF & "secondary down" & @CRLF)
            $hTimer = TimerInit()

        Case $GUI_EVENT_SECONDARYUP
            ConsoleWrite("secondary up" & @CRLF)
            $hTimer = 0 ; peace of mind

        Case $idContext_Save
            ConsoleWrite("Code for Save Image" & @CRLF)
    EndSwitch

WEnd

GUIDelete($hGUI)

;=================================================
Func WM_CONTEXTMENU($hwnd, $iMsg, $wParam, $lParam)
    #forceref $hwnd, $iMsg, $wParam, $lParam

    Local Static $iCounter = 0
    $iCounter += 1

    If $hTimer Then
        Local $fDiff = TimerDiff($hTimer)
        $hTimer = 0
        If $fDiff > 250 Then
            ConsoleWrite("WM_CONTEXTMENU #" & $iCounter & " (not displayed)" & @CRLF)
            Return 0 ; prevents the context menu to appear
        EndIf
    EndIf

    ConsoleWrite("WM_CONTEXTMENU #" & $iCounter & " (visible)" & @CRLF)
    Return $GUI_RUNDEFMSG

EndFunc   ;==>WM_CONTEXTMENU

The script above simply shows the difference between a quick right-click (which displays a context menu) and a longer one > 250ms (which doesn't display it)

#include <GUIConstantsEx.au3>
#include <MsgBoxConstants.au3>
#include <WindowsConstants.au3>

Global $hTimer = 0, $idDummy_CancelCrop

$hGUI = GUICreate("Test #6 - Right-click (short then long) inside GUI", 500, 200, _
     -1, -1, -1, $WS_EX_TOPMOST)

$idDummy_CancelCrop = GUICtrlCreateDummy()

Local $idContext_Menu = GUICtrlCreateContextMenu()
Local $idContext_Save = GUICtrlCreateMenuItem("Save", $idContext_Menu)

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

While 1
    Switch GUIGetMsg()
        Case $GUI_EVENT_CLOSE
            ExitLoop

        Case $GUI_EVENT_SECONDARYDOWN
            ConsoleWrite("--------------" & @CRLF & "secondary down" & @CRLF)
            $hTimer = TimerInit()

        Case $GUI_EVENT_SECONDARYUP
            ConsoleWrite("secondary up" & @CRLF)
            $hTimer = 0 ; peace of mind

        Case $idContext_Save
            ConsoleWrite("Code for Save Image" & @CRLF)

        Case $idDummy_CancelCrop
            ConsoleWrite("Code for Cancel Crop" & @CRLF)
    EndSwitch

WEnd

GUIDelete($hGUI)

;=================================================
Func WM_CONTEXTMENU($hwnd, $iMsg, $wParam, $lParam)
    #forceref $hwnd, $iMsg, $wParam, $lParam

    Local Static $iCounter = 0
    $iCounter += 1

    If $hTimer Then
        Local $fDiff = TimerDiff($hTimer)
        $hTimer = 0
        If $fDiff > 250 Then
            ConsoleWrite("WM_CONTEXTMENU #" & $iCounter & " (not displayed)" & @CRLF)
            GUICtrlSendToDummy($idDummy_CancelCrop)
            Return 0 ; prevents the context menu to appear
        EndIf
    EndIf

    ConsoleWrite("WM_CONTEXTMENU #" & $iCounter & " (visible)" & @CRLF)
    Return $GUI_RUNDEFMSG

EndFunc   ;==>WM_CONTEXTMENU

Script above : after a dummy control has been added, we can trigger this dummy control directly from WM_CONTEXTMENU.
The goal was to execute the "Code for Cancel Crop" only after the user released RMB, but never while RMB is still pressed.

Please advise if you think something is wrong with this logic or could be improved,
Thanks !

Share this post


Link to post
Share on other sites

Good observations. Your latest code seems optimal.  Usage of dummy is a good choice.

If $hTimer Then

is probably not necessary, but it doesn't hurt :thumbsup:

Share this post


Link to post
Share on other sites

Thanks Nine !

2 hours ago, Nine said:
If $hTimer Then

is probably not necessary, but it doesn't hurt :thumbsup:

This line is related to Rule #4 that I didn't mention in the precedent post because... it's a looong rule, compared to the 3 other short ones :P

Rule #4 - right clicks inside an already displayed context menu (what a strange idea !) will trigger WM_CONTEXTMENU as many times as right clicks were pressed. When finally the user chooses or not an option in the context menu (closing the context menu) then will follow :
=> 1 $GUI_EVENT_SECONDARYUP (the "regular" one)
=> many couples of $GUI_EVENT_SECONDARYDOWN / $GUI_EVENT_SECONDARYUP (as many as stacked right clicks)

* With line "If $hTimer Then" and 2 additionals right clicks inside the displayed context menu. Result looks correct :

--------------
secondary down
WM_CONTEXTMENU #1 (visible)
WM_CONTEXTMENU #2 (visible)
WM_CONTEXTMENU #3 (visible)
secondary up
--------------
secondary down
secondary up
--------------
secondary down
secondary up
Code for Save Image

* Without line "If $hTimer Then" , result looks a bit messy :

--------------
secondary down
WM_CONTEXTMENU #1 (visible)
WM_CONTEXTMENU #2 (not displayed)
WM_CONTEXTMENU #3 (not displayed)
secondary up
--------------
secondary down
secondary up
Code for Cancel Crop
--------------
secondary down
secondary up
Code for Cancel Crop
Code for Save Image

Anyway, I guess it won't be that simple in the real script, because of right clicks inside Main gui, then into parent & child Gui's etc...
When it will happen, then $hwnd, $wParam and $lParam will heavily be used to know where we came from... even if we absolutely don't know where we're going to !

Thanks again for your help & advices.

Share this post


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