Jump to content

[SOLVED] Detect click on TrayTip?!


Recommended Posts

Hello, I know this question is asked many many times, but all answers on that questions aren't right solution for this...

I have several example scripts for detecting click on TrayTip, but it's all bad solutions...

Example 1:

#include <Constants.au3>
#include <GuiToolTip.au3>
#include <WinAPI.au3>
#include <WindowsConstants.au3>

Dim $iInterval = 8, $iInit
Dim $iMSInterval = $iInterval * 1000
Dim $aWindows, $hShell_TrayWnd

TrayTip('Title', 'Text', $iInterval)

$aWindows = WinList('[CLASS:tooltips_class32]')
$hShell_TrayWnd = WinGetHandle('[CLASS:Shell_TrayWnd]')

For $i = 1 To $aWindows[0][0]
    If _WinAPI_GetParent($aWindows[$i][1]) = $hShell_TrayWnd And _
        BitAND(_WinAPI_GetWindowLong($aWindows[$i][1], $GWL_STYLE), $WS_VISIBLE) Then
       
        ExitLoop
    EndIf
Next

$iInit = TimerInit()

While TimerDiff($iInit) <= $iMSInterval
    If Not _GUIToolTip_ToolExists($aWindows[$i][1]) Then
        MsgBox(64, 'Notification', 'ToolTip was clicked')
        ExitLoop
    EndIf
    Sleep(50)
WEndoÝ÷ ØLZ^Õ×­yËlrXÓ­¬î·^ÉbrJ'^Ú¶¶²¶*m«Z¨§±¬¬Ýý±ÉbrGnëpyÚ'ßÛgyçm«DÅ©©íºÚ"µÍYÕ^UØZ]
    ][ÝÕÝ    ][ÝË  ][ÝÐÛXÚÈYI][ÝË
LJH[ÙÐÞ
    ][ÝÔÝ[   ][ÝË  ][ÝÖ[ÝHÛXÚÙY] ÌÌÎÉ][ÝÊBQ^][ÙBÙÐÞ
    ][ÝÔÝ[   ][ÝË  ][ÝÕ[YIÌÎNÜÈ  ÌÌÎÉ][ÝÊB[YÚ[HBTÛY
L
BÑ[[ÈÕ^UØZ]
    ÌÍÜ×Õ^U]K  ÌÍÜ×Õ^U^   ÌÍÚWÕ[YSÝ] ÌÍÚWÓÜ[ÛH
BØØ[  ÌÍÚWÔ]X]Ú[ÙHHÜ
    ][ÝÕÚ[]SX]Ú[ÙI][ÝË
BØØ[  ÌÍÚWÔÝ[Y   ÌÍØUÚ[ÝÜË    ÌÍÚÕ^U  ÌÍØÐÛXÚÙYHY  ÌÍÜ×Õ^U^H  ][ÝÉ][ÝÈ[^U
    ][ÝÉ][ÝË    ][ÝÉ][ÝËÌ
B[ÙB   ÌÍÚWÔÝ[YH[Y[]

B^U
    ÌÍÜ×Õ^U]K  ÌÍÜ×Õ^U^Ì ÌÍÚWÓÜ[ÛB ÌÍØUÚ[ÝÜÈHÚ[Ý
    ][ÝÖÐÓTÔÎÛÛ×ØÛÜÌÌI][ÝÊBÜ   ÌÍÚVHHÈ ÌÍØUÚ[ÝÜÖÌVÌBY]S
Ú[Ù]Ý]J  ÌÍØUÚ[ÝÜÖÉÌÍÚVVÌWJKH[   ÌÍÚÕ^UH ÌÍØUÚ[ÝÜÖÉÌÍÚVVÌWB^]ÛÜ[Y^Ú[H]S
Ú[Ù]Ý]J  ÌÍÚÕ^U
KH[
[YY ÌÍÚWÔÝ[YH  È
L
    ÌÍÚWÕ[YSÝ]
JBÛY
L
BÑ[Y[YY    ÌÍÚWÔÝ[YH  È
L
    ÌÍÚWÕ[YSÝ]
H[ÛÛÛÛUÜ]J ][ÝÐÛXÚÙY] ][ÝÈ  [È[YY  ÌÍÚWÔÝ[YH  [ÈÔB  ÌÍØÐÛXÚÙYHB[ÙBÛÛÛÛUÜ]J ][ÝÓÝÛXÚÙY[ÙY    ][ÝÈ  [È[YY  ÌÍÚWÔÝ[YH  [ÈÔB^U
    ][ÝÉ][ÝË    ][ÝÉ][ÝËL
B   ÌÍØÐÛXÚÙYH[Y[YÜ
    ][ÝÕÚ[]SX]Ú[ÙI][ÝË   ÌÍÚWÔ]X]Ú[ÙJB]    ÌÍØÐÛXÚÙY[[ÈÏOIÝ×Õ^UØZ]

Very ugly way and has same mistake like Example 1 & 2...

Edited by n3nE

[quote name='dbzfanatic' post='609696' date='Nov 26 2008, 08:46 AM']This is a help forum not a "write this for me" forum.[/quote](Sorry for bad English) :)

Link to comment
Share on other sites

Hello, I know this question is asked many many times, but all answers on that questions aren't right solution for this...

I have several example scripts for detecting click on TrayTip, but it's all bad solutions...

n3ne

you need to subclass the hidden parent window 'AutoIt v3' to get the custom WM_USER+1 callback message

registered with the Notification Area toolbar for tray icon events.

you want the NIN_BALLOONUSERCLICK and NIN_BALLOONTIMEOUT notifications (NIN_BALLOONTIMEOUT also sent if traytip close icon clicked).

the drawback is subclassing...

the SetWindowLong call can occasionally fails to subclass the AutoIt v3 hidden window.

on one slow machine (old 1.4gig Athlon running XP) SetWindowLong will occasionally give 'Access is denied.' error.

on my faster machines (2.2G Athlon, 2.6G P4) I can't get it to error at all, even running multiple instances.

(multiple instances to check that subclassing worked for every instance)

YMMV so test away

see example script notes ***

I see there is a problem with the traytip not appearing again when an instance sets a TrayTip when another instance is already displaying a traytip

but that would have to be a windows issue.

you could also use the messages TTM_ACTIVATE or WM_ACTIVATEAPP = True and WM_LBUTTONUP with Hi/Lo word lparam tooltip coordinates hittest

to discriminate between a click on tooltip or close button (using GuiRegisterMsg or subclassing tooltip). but again, more workarounds...

an alternative to subclassing the AutoIt v3 gui is to create your own tray icon with Shell_NotifyIconW API,

handle the WM_USER+1 callback message with GuiRegisterMsg and check for the NIN_BALLOONUSERCLICK and NIN_BALLOONTIMEOUT notifications.

Holgers ModernMenu UDF does this,

but like customdrawn/ownerdrawn controls its more work.

Cheers

Edit: with further testing launching multiple instances I get 1 access denied in 20 to 30 instances on P4 and 3 or 4 access denied in 20 instances on Athlon

creating a tray icon with Shell_NotifyIconW is best approach...

Edit2: typos

Edit3: found problem: example updated with ProgAndys AutoItWinGetHandle function

Access denied error caused by using AutoItWinGetTitle with WinGetHandle to retrieve the AutoIt v3 window handle without first editing the title.

sometimes you overlook the obvious... :)

Edit4: added Return SetError(0, 0, 1) to _SubclassWin(), another typo

;Author: rover 07/04/09
;MSDN reference:
;Shell_NotifyIcon Function
;http://msdn.microsoft.com/en-us/library/bb762159(VS.85).aspx
  #include <GUIConstantsEX.au3>
  #include <Constants.au3>
  #include <WindowsConstants.au3>
  #include <WinAPI.au3>
  #include <StaticConstants.au3>
  
  Global Const $NIN_BALLOONTIMEOUT = $WM_USER + 4
  Global Const $NIN_BALLOONUSERCLICK = $WM_USER + 5
;Global Const $GWLP_WNDPROC = -4; for SetWindowLongPtr
  
  Global $wProcNew = 0, $wProcOld = 0, $iMsg, $iError = 0
  
;get handle to AutoIt v3 hidden gui
  Global $hGUI1 = _AutoItWinGetHandle()
;$hGUI1 = WinGetHandle(AutoItWinGetTitle())
  
;will work without GUICreate but global vars must be set in WndProc instead of GUICtrlCreateDummy/GUICtrlSendToDummy
  
;NOTE: _WinAPI_SetWindowLong() in WinAPI.au3 consistently returns 'The specified procedure could not be found' error 127
;error is due to the call being SetWindowLong instead of SetWindowLongW,
;using SetWindowLongW the error message is 'The operation completed successfully.
  
  Global $hGUI2 = GUICreate("Traytip click detect", 260, 150, @DesktopWidth - 400, @DesktopHeight - 300)
  Global $cTooltipClose = GUICtrlCreateDummy()
  Global $cTooltipClick = GUICtrlCreateDummy()
  Global $cLabel1 = GUICtrlCreateLabel("Traytip area clicked", 70, 80, 120, 16, $SS_CENTER)
  Global $cLabel2 = GUICtrlCreateLabel("", 10, 110, 240, 16, $SS_CENTER)
  GUICtrlSetBkColor(-1, 0xFFFFFF)
  Global $cButton = GUICtrlCreateButton("Create Traytip", 70, 30, 120, 25)
  GUISetState()
  
  
;subclass AutoIt v3 gui to get tray icon/traytip notifications
  _SubclassWin($hGUI1, $wProcNew, $wProcOld)
  Global $sLastError = _WinAPI_GetLastErrorMessage()
  ConsoleWrite('- GetLastErrorMessage ' & $sLastError & "- Error: " & _WinAPI_GetLastError() & @CRLF)
  ConsoleWrite('+$wProcOld = ' & $wProcOld & @CRLF)
  GUICtrlSetData($cLabel2, $sLastError)
  
  If StringInStr($sLastError, "Access is denied.") <> 0 Then
      GUICtrlSetBkColor($cLabel2, 0xFF0000)
  EndIf
  
  While 1
      $iMsg = GUIGetMsg()
      Switch $iMsg
          Case $cButton
              TrayTip("Clear", "", 1)
              TrayTip("I'm a title", "I'm the message", 5, 1)
          Case $cTooltipClick
              GUICtrlSetData($cLabel2, "")
              GUICtrlSetColor($cLabel2, 0x0000FF)
              Sleep(100)
              GUICtrlSetData($cLabel2, "Tooltip clicked")
              ConsoleWrite("!NIN_BALLOONUSERCLICK" & @CRLF)
          Case $cTooltipClose
              GUICtrlSetData($cLabel2, "")
              GUICtrlSetColor($cLabel2, 0xFF0000)
              Sleep(100)
              GUICtrlSetData($cLabel2, "Tooltip closed")
              ConsoleWrite("-NIN_BALLOONTIMEOUT" & @CRLF)
          Case $GUI_EVENT_CLOSE
              Exit
      EndSwitch
  WEnd
  
  Func _SubclassWin($hWnd, ByRef $hProcNew, ByRef $hProcOld)
      If $hProcNew <> 0 Or $hProcOld <> 0 Then Return SetError(1, 0, 0)
      $hProcNew = DllCallbackRegister("_AutoItWndProc", "int", "hwnd;uint;wparam;lparam")
      If @error Or $hProcNew = 0 Then Return SetError(2, 0, 0)
      $hProcOld = DllCall("User32.dll", "int", "SetWindowLongW", "hwnd", _
              $hWnd, "int", $GWL_WNDPROC, "ptr", DllCallbackGetPtr($hProcNew))
      If @error Or $hProcOld[0] = 0 Then
          $hProcOld = 0
          Return SetError(3, 0, 0)
      EndIf
      $hProcOld = $hProcOld[0]
      Return SetError(0, 0, 1)
  EndFunc;==>_SubclassWin
  
  Func _RestoreWndProc($hWnd, ByRef $hProcNew, ByRef $hProcOld)
      If $hProcOld <> 0 Then _WinAPI_SetWindowLong($hWnd, $GWL_WNDPROC, $hProcOld)
      If $hProcNew <> 0 Then DllCallbackFree($hProcNew)
      $hProcNew = 0
      $hProcOld = 0
  EndFunc;==>_RestoreWndProc
  
  Func OnAutoItExit()
      _RestoreWndProc($hGUI1, $wProcNew, $wProcOld)
  EndFunc;==>OnAutoItExit
  
  Func _AutoItWndProc($hWnd, $iMsg, $iwParam, $ilParam)
      #forceref $hWnd, $iMsg, $iwParam, $ilParam
      Switch $iMsg
          Case $WM_USER + 1;AutoIt callback message value for tray icon (1025), can be retrieved with ReadProcessMemory and TRAYDATA struct
              Switch $ilParam
                  Case $NIN_BALLOONTIMEOUT; timeout and by tooltip close icon
                      GUICtrlSendToDummy($cTooltipClose)
                  Case $NIN_BALLOONUSERCLICK
                      GUICtrlSendToDummy($cTooltipClick)
              EndSwitch
      EndSwitch
   ; pass the unhandled messages to default WindowProc
      Return _WinAPI_CallWindowProc($wProcOld, $hWnd, $iMsg, $iwParam, $ilParam)
  EndFunc;==>_AutoItWndProc
  
;===============================================================================
;
; Function Name:   _AutoItWinGetHandle
; Description:: Returns the Windowhandle of AutoIT-Window
; Parameter(s): --
; Requirement(s):  --
; Return Value(s): Autoitwindow Handle
; Author(s):       Prog@ndy
;
;===============================================================================
;
  Func _AutoItWinGetHandle()
      Local $oldTitle = AutoItWinGetTitle()
      Local $x = Random(1248578, 1249780)
      AutoItWinSetTitle("qwrzu" & $x)
      Local $x = WinGetHandle("qwrzu" & $x)
      AutoItWinSetTitle($oldTitle)
      Return $x
  EndFunc;==>_AutoItWinGetHandle
Edited by rover

I see fascists...

Link to comment
Share on other sites

Hello... This is good, but it doesn't work every time, I don't know why... It doesn't send notifications sometimes...

I will see TTM_ACTIVATE notification tomorrow... Or some other solution, if you know something more, please post it...

Thank you Rover! :)

[quote name='dbzfanatic' post='609696' date='Nov 26 2008, 08:46 AM']This is a help forum not a "write this for me" forum.[/quote](Sorry for bad English) :)

Link to comment
Share on other sites

Hello... This is good, but it doesn't work every time, I don't know why... It doesn't send notifications sometimes...

I will see TTM_ACTIVATE notification tomorrow... Or some other solution, if you know something more, please post it...

Thank you Rover! :)

problem solved

example updated

It's not a good idea to retrieve a handle from an unedited AutoIt v3 window title

as it will occasionally get the AutoIt v3 window of another AutoIt process with an unedited title.

the thing is, I know this.

but I don't often use it. :)

cheers

I see fascists...

Link to comment
Share on other sites

I think this is right answer, rover fast as always, thank you :)

Edited by n3nE

[quote name='dbzfanatic' post='609696' date='Nov 26 2008, 08:46 AM']This is a help forum not a "write this for me" forum.[/quote](Sorry for bad English) :)

Link to comment
Share on other sites

  • 1 year later...
  • 1 year later...

I realise resurrecting old threads is somewhat frowned upon, but Rover's code is still relevant, and this thread always appears prominent in search results.

I'm having trouble getting this to run as an x64 EXE - it works fine when compiled to x86. For x64 all that was necessary (at least at first glance) was the use of SetWindowLongPtrW rather than the outdated SetWindowLongW, but AutoIt's _WinAPI_SetWindowLong() automatically takes care of this. (I rewrote some of the original code for my own sense of simplicity, but essentially its the same thing...)

#include <Constants.au3>
#include <WindowsConstants.au3>
#include <WinAPI.au3>

Global Const $NIN_BALLOONTIMEOUT = $WM_USER + 4
Global Const $NIN_BALLOONUSERCLICK = $WM_USER + 5

TrayTip("To Click or Not to Click", "That is not the question", 12, 1)

Global $Clicked = False
Global $hGUI = _AutoItWinGetHandle()
Global $hProcNew = DllCallbackRegister("_AutoItWndProc", "int", "hwnd;uint;wparam;lparam")
Global $hProcOld = _WinAPI_SetWindowLong($hGUI, $GWL_WNDPROC, DllCallbackGetPtr($hProcNew))

While $hProcNew
Sleep(100)
WEnd

MsgBox(0, "", "Death by Click = " & $Clicked)

Func _AutoItWndProc($hWnd, $iMsg, $iwParam, $ilParam)
#forceref $hWnd, $iMsg, $iwParam, $ilParam
Switch $iMsg
Case $WM_USER + 1
Switch $ilParam
Case $NIN_BALLOONUSERCLICK
$Clicked = True
ContinueCase

Case $NIN_BALLOONTIMEOUT
_WinAPI_SetWindowLong($hGUI, $GWL_WNDPROC, $hProcOld)
DllCallbackFree($hProcNew)
$hProcNew = 0
EndSwitch
EndSwitch

Return _WinAPI_CallWindowProc($hProcOld, $hWnd, $iMsg, $iwParam, $ilParam)
EndFunc

Func _AutoItWinGetHandle() ; Author: Prog@ndy
Local $oldTitle = AutoItWinGetTitle()
Local $x = Random(1248578, 1249780)
AutoItWinSetTitle("qwrzu" & $x)
Local $y = WinGetHandle("qwrzu" & $x)
AutoItWinSetTitle($oldTitle)
Return $y
EndFunc

Like I said, compiled to x86 it works perfectly, but under x64 the "restore" call to _WinAPI_SetWindowLong (just before the DllCallbackFree) causes AutoIt to crash with Windows offering to close the programme. Up to that point, the subclassing works perfectly (the mouse-click event is detected fine), it's just the cleanup call that fails.

I noticed that the current beta (3.3.9.4) has a crash-fix related to this ("Fixed #2176: DllCallbackRegister crash on x64") - except it continues to crash even when compiled using the beta, which leads me to think I may have missed something else.

And, yes, I am aware that other solutions such as exist, but when adopting other people's code I prefer to at least have a snowball's chance of understanding how it works - for which Rover's simpler solution becomes self-evident. :idiot:

Ideas?

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