AutoHotkey Homepage AutoHotkey Community
Let's help each other out
 
 FAQFAQ   SearchSearch   MemberlistMemberlist   RegisterRegister 
 ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

SetWinTimer Function (Triggers Variable or Function)

 
Post new topic   Reply to topic    AutoHotkey Community Forum Index -> Scripts & Functions
View previous topic :: View next topic  
Author Message
dmatch



Joined: 15 Oct 2007
Posts: 113

PostPosted: Sun Feb 03, 2008 1:28 am    Post subject: SetWinTimer Function (Triggers Variable or Function) Reply with quote

Edit: 2/04/2008 Corrected bug where all timers get shut off when first one killed.
Edit: 2/04/2008 Replaced WM_TIMER handler with Windows TimerProc callback function.
Edit: 2/06/2008 Added abiltity to use a callback function as a target.

Hi, ALL

Having read of a request in "Ask For Help" for creating a timer on the fly, I wrote this function that uses no globals or labels to run Windows timers (more than 1 at a time) using DllCalls to Windows SetTimer function. Instead of calling labels the function sets a caller's BYREF variable to true or calls a user callback function each time the timer fires. Here is a prototype call for the function:
Code:
SetWinTimer(hGui,TimerID,TimeInMiliseconds,TimerFired,true ;or false)

hGui is the handle of Gui window.
TimerID is an integer 0x1000 or larger.
TimeInMiliseconds is the duration for the timer in miliseconds.
TimerFired is:

1)The callers BYREF Global or Static variable that will be set true when the timer fires. This can be a dynamic variable so in that sense this is a dynamic timer function.

OR

2)A string containing the name of a function to call when the timer fires. This function must accept 5 parameters. See comments in the function script for more info. The last parameter in the call to SetWinTimer must be 1 to invoke the target function version of the function.

To turn off a timer call SetWinTimer same as above but with a time of 0. To kill all timers call as above with a window Gui handle (hGui) of 0.

Returns 0 for failure and >0 for success (Timer ID). Calls to kill individual timers return 0 when no timers left to kill and 1 if a timer(s) is still running.

This function does not use OnMessage to temporarily trap WM_TIMER messages so should not interfere with other timers in your script.

I don't know if this will solve the "Ask For Help" poster's problem but thought I would share it here since I messed around with it for a couple of hours (Edit: now even longer).

The following is a script to demonstrate use of the function. Save it in a folder of your choice such as C:\Program Files\AutoHotKey. Later in this post you will see code for the function itself. Save it as SetWinTimer.ahk in the same folder as this demo script and uncomment the line #include SetWinTimer.ahk in the demo script... OR save the function's code in your user library as SetWinTimer.ahk.

Please read the comments in the demo and function scripts for more iinformation.
Code:

;******************************************************
; Demonstration of SetWinTimer Function using a variable
; target and a function target.
;
; Starts 2 repeating timers set to 4 seconds and allows
; them to run 3 times each. Each time a timer fires
; a variable is set to true (1) or a function is called.
;
; SetWinTimer.ahk file should be in the same folder as
; this script or in the AHK user library folder.
;
; 2/4/2008
; dmatch @ AutoHotKey forum
;******************************************************

;#include SetWinTimer.shk ;comment/uncomment as needed

CoordMode,ToolTip,Screen
Gui,Add,Button,vStartTimers1 gStartTimers1,Demo Timers With Target Variables
Gui,Add,Button,vStartTimers2 gStartTimers2,Demo Timers With Target Function
Gui,Show,w200 h100, Timer Demo
WinActivate, Timer Demo
WinWaitActive, Timer Demo
hGui:=WinExist() ;Get window handle for use in SetWinTimer
NextTimerID:=0x1000 ;Seed for timer values
return

StartTimers1: ;run variable target version
   DemoText=Please Wait....`n
   ToolTip,,,,1
   GuiControl,Disable,StartTimers1
   GuiControl,Disable,StartTimers2
   Timer1Fired:=0 ;These are boolean indicators that timers have fired
   Timer2Fired:=0
   TimeInMiliseconds:=3000 ;Duration in miliseconds (3 seconds)
   Loop,2
   {
       NextTimerID++
       TimerID%A_Index%:=NextTimerID
       result:=SetWinTimer(hGui,TimerID%A_Index%,TimeInMiliseconds,Timer%A_Index%Fired)
       DemoText=%DemoText%TickCount=%A_TickCount% SetWinTimer(hGui`,%NextTimerID%`,%TimeInMiliseconds%`,Timer%A_Index%Fired) `;Started Timer%A_Index%`n
       ToolTip,%DemoText%,0,0,1
       if(A_Index=1)
           Sleep 1000 ;keep the timers out of sync for demo
   }
   GoSub CheckTimers
   DemoText=%DemoText%`nDone!
return

;Check if timer target variables are set
CheckTimers:
   Count:=0
   Loop
   {
      Loop 2
      {
          If(Timer%A_Index%Fired){
              count++
              Timer%A_Index%Fired:=false
              DemoText=%DemoText%`nTickCount=%A_TickCount% Timer %A_Index% Fired: Total Firings=%count%
              ToolTip,%DemoText%,0,0,1
          }
      } 
      if(count>=6){
         break
      }
   }
   NextTimerID:=0x1000
   ;Kill timers
   ;To kill an individual timer set it's duration to 0
   DemoText=%DemoText%`n
   Loop 2
   {
       TimerID:=TimerID%A_Index%
       SetWinTimer(hGui,TimerID%A_Index%,0,_:="") ;kill the timer
       DemoText=%DemoText%`nTickCount=%A_TickCount% SetWinTimer(hGui`,%TimerID%`,0`,_:="") `;kill the timer
       ToolTip,%DemoText%,0,0,1
   }
   DemoText=%DemoText%`nTickCount=%A_TickCount% Done!
   ToolTip,%DemoText%,0,0,1
   ;we could have done this instead
   ;SetWinTimer(0,0,0,_:="") ;kill all timers by setting hGui=0
   GuiControl,Enable,StartTimers1
   GuiControl,Enable,StartTimers2
return
StartTimers2: ;function target version
    StartTimersFromFunction(hGui)
return
StartTimersFromFunction(hGui)
{
   GuiControl,Disable,StartTimers1
   GuiControl,Disable,StartTimers2
   TimeInMiliseconds:=3000 ;Duration in miliseconds (3 seconds)
   Loop 2
   {
       ;use TimerID=0 feature to get next timer ID (0x1001 and above)
       TimerID:=SetWinTimer(hGui,0,TimeInMiliseconds,_:="TimerFired",true)
       ;At this point SetWinTimer has sent our TimerFired
       ;function the new timer ID with use of UserParam1=true
       ;to indicate to TimerFired that a new timer was added.
       ;This avoids use of globals.
       if(A_Index=1)
           Sleep 1000 ;keep the timers out of sync for demo
   }
   return
}
;Function Called when timer fires
;You can use more than 1 function
;Or combine multiple Timer IDs as in this case
TimerFired(ID,UserParam1,UserParam2,UserParam3,UserParam4)
{
    ;SetWinTimer always sends UserParams of 0 when timer fires
    ;When a timer is added it sends Timer ID and UserParam1=1
    ;So that the callback process can do housekeeping.
   Static count,Timers,x,y,DemoText,ClearDemoText
   if(ClearDemoText){
       DemoText:=""
       ClearDemoText:=false
   }
    if(UserParam1=1){ ;UserParam1=1 indicates addition of new timer
DemoText=%DemoText%TickCount=%A_TickCount% TimerID:=SetWinTimer(hGui`,0`,TimeInMiliseconds`,_:="TimerFired"`,true) `;TimerID=%ID%`n
DemoText=%DemoText%TickCount=%A_TickCount% called TimerFired(%ID%`,1`,0`,0`,0) `;Registered Timer with target function`n
       ToolTip,%DemoText%,0,0,1
       if(Timers)
           Timers .=","
       Timers .=ID+0
        ToolTip,%DemoText%,0,0,1
       return 1
   }
   count++
   Loop,Parse,Timers,CSV
   {
       if(ID=A_LoopField){
DemoText=%DemoText%`nTickCount=%A_TickCount% AHK called TimerFired(%A_LoopField%`,0`,0`,0`,0) - Timer %A_Index% Fired: Total Firings=%count%
           ToolTip,%DemoText%,0,0,1
       }
   }
    if(count>=6){
          SetWinTimer(0,0,0,_:="") ;kill all timers
DemoText=%DemoText%`n`nTickCount=%A_TickCount% SetWinTimer(0`,0`,0`,_:="") `;killed all timers
        ToolTip,%DemoText%`nTickCount=%A_TickCount% Done!,0,0,1
        count:=0
        Timers:=""
        ClearDemoText:=true
        GuiControl,Enable,StartTimers1
        GuiControl,Enable,StartTimers2
        return 0
    }
    return 1
}

GuiEscape:
GuiClose:
   ;Kill all timers in case of early exit
   SetWinTimer(0,0,0,_:="")
ExitApp
return



Here is the script code for the SetWinTimer Function:
Code:

;************************ SetWinTimer ***********************
; Starts/Kills 1 or more timers using DllCalls to
; Windows SetTimer/KillTimer functions. Uses a TimerProc
; Windows callback function so does not interfere with
; timer (WM_TIMER) messages that are being handled with
; the AHK OnMessage function or AHK SetTimer command.
;
; Creates no global variables and requires no labels.
;
; There are 2 ways to use the function:
; 1) With Variable Targets
;    A caller's BYREF variable is set to true when the timer fires.
;        This will require periodic polling of the variable
;        to determine if the timer has fired (variable=true=1)
;
;    SetWinTimer(hGui,ID,Duration,TargetVar:=0)
;
; 2) With Function Targets
;    A caller's function(s) is called when timer(s) fires.
;        This is probably more useful than the above.
;
;    MyTimerCallback(ID,UserParam1,UserParam2,UserParam3,UserParam4)
;
;        This user named function is called whenever a timer fires and
;        whenever a new timer is added.
;
;        The caller's function must accept 5 parameters,
;        the Timer ID (1st param) and 4 int user parameters.
;        You are not required to use the user parameters.
;        However, if you ignore UserParam1 the timer will
;        appear to fire immediately. See below for how to use
;        UserParam1. You may make the 4 UserParams
;        optional. The function is expected to return an int.
;
;        When a new timer is added with
;
;    SetWinTimer(hGui,ID,Duration,_:="MyTimerCallback",IsFunction:=1)
;
;        the callback function is called immediately with UserParam1
;        set to 1 and the Timer ID in ID. This allows the caller to
;        use static variables for tracking timers instead of globals
;        or labels.
;
;        Whenever a timer fires and the function is called, Timer ID
;        will be in ID and all user parameters will contain 0.
;
; A negative (-) duration creates a single shot timer.
; To kill an individual timer set its duration to 0.
; To kill all timers call with a Gui window handle of 0.
;
; Returns 0 for failure
;     or >0 Timer ID for successful addition of timer
;     or 1 for successful timer kill (0 when no timers to kill)
;
;     If there is attempt to access an unknown timer a MsgBox
;     will appear encouraging the user to quit AHK.
;
; Save this as SetWinTimer.ahk in your AutoHotKey user library or
; save it in the same directory as your script and use
; #include SetWinTimer.ahk at thr first of your script.
;
; AutoHotKey version tested: 1.0.47.02
; Revisions:
; 2/4/2008 Replaced WM_Message with TimerProc callback
; 2/6/2008 Added callback function(s) as target for timers
; Author: dmatch @ AutoHotKey forum 2/3/2008
;**********************************************************************
SetWinTimer(GuiHandle   ;Window's Handle of Gui or 0 to kill all timers.
,ID                     ;Your Timer ID (use 0x1000 and higher).
                        ;Or 0 to use default (last Timer ID + 1).
                        ;Default is 0x1001 when no timers running.
,Duration               ;Timer duration in miliseconds (msec.)
                        ;or 0 to kill the timer (1000 msec.=1 sec.)
,BYREF Target           ;Variable to make true (1) on timeout
                        ;OR string containing function name to call.
,IsFunction=0)          ;If = 1 Target is used as a function name.
{
   Static LastTimerID:=0x1000,TimerProcAddr,Functions
   if(!GuiHandle){ ;Kill all timers
       result:=SetWinTimer_TimerProc(0,0,0,0)
       LastTimerID:=0x1000
       ;Free all memory used for callback addresses
       Loop,Parse,Functions,CSV
           if A_LoopField is integer
               DllCall("GlobalFree",uint,A_LoopField)
       Functions:=""
       return 1
   }
   if(!Duration){ ;Kill the individual timer
      result:=SetWinTimer_TimerProc(-1*GuiHandle,0,ID,2) ;2 indicates to kill
      if(!result){ ;no timers left
          LastTimerID:=0x1000
      }
      return result
   }
   if(ID=0){
       ID:=LastTimerID+1
   }
   LastTimerID:=ID
   If(Duration<0){
       ID:=-1*ID ;Indicates to SetWinTimer_TimerProc a single-shot timer
   }
   ;Introduce new timer Procs
   if(IsFunction){ ;Register the callback function if needed
       ;See if we already have this function
       if(Pos:=InStr(Functions,Target)){
           DupFunction:=true
           pos:=InStr(Functions,",",0,pos)
           pos++
           pos2:=InStr(Functions,",",0,pos)
           if(!pos2)
               pos2:=StrLen(Functions)+1
           ;Get address we used previously
           TargetAddr:=SubStr(Functions,pos,pos2-pos)
       }
       if(!DupFunction){
           ;Register a new callback target function
           TargetAddr:=RegisterCallback(Target,"",5,0)
           if(TargetAddr){
               if(Functions)
                   Functions .=","
               ;add target and address to function list
               Functions .=Functions . Target . "," . TargetAddr+0
           }
           else
               return 0
       }
       SetWinTimer_TimerProc(-1*GuiHandle,TargetAddr,ID,1)
   }
   Else{
       SetWinTimer_TimerProc(-1*GuiHandle,&Target,ID,0)
   }
   if(!TimerProcAddr)
       TimerProcAddr:=RegisterCallback("SetWinTimer_TimerProc", "", 4, 0)
   ;Start the Timer
   result:=DllCall("SetTimer",uint,GuiHandle,uint,ID,uint,Duration,uint,TimerProcAddr)
   return result
}
;*********************************************************
; Timer callback procedure set in SetWinTimer above.
;*********************************************************

SetWinTimer_TimerProc(hWnd,msg,ID,SysTime)
{
   Static TimerIDs
   KillIt:=false
   Result:=0
   HaveError:=0
_SetWinTimer_ReturnOnError_:
   if(hWnd<0){ ;Was internal call from SetWinTimer to introduce/kill timer
      if ID is not integer ;check if contains legit TimerID
         return result
      hGui:=-1*hWnd
      if(SysTime<2){ ;Used to indicate to add a Timer and if is function
         if(TimerIDs)
             TimerIDs .=","
         TimerIDs .=ID+0 . "," . hGui+0 . "," . SysTime+0 . "," . msg+0
         ;We now have a comma delimted string
         ;with "TimerID,hGui,IsFunction,Address".....
         ;send ID to callback process with UserParam1=true
         result:=DllCall(msg+0,int,ID,int,1,int,0,int,0,int,0,int)
         return abs(ID) ; - IDs can indicate single-shot timer
      }
      Else{ ; 2 indicates to kill the Timer
         KillIt:=true ;Set switch to Kill the timer (below)
      }
   }
   Else If(!Hwnd){ ;kill all timers
      Loop,Parse,TimerIDs,CSV
      {
          If(Mod(A_Index,4)=1){
              TimerID:=abs(A_LoopField) ;can be -
          }
          Else If(Mod(A_Index,4)=2){
              hWnd:=A_LoopField
              result:=DllCall("KillTimer",uint,hWnd,uint,TimerID,uint)
          }
      }
      TimerIDs:=""
      if(HaveError){ ;HaveError set at end of function
          MsgBox,4,SetWinTimer Error,Attempt to access an unknown timer: TimerID=%ID%`nIt is recommended that you exit.`n`nExit AHK?
          IfMsgBox yes
              ExitApp
      }
      return 0
   }
   if(!TimerIDs) ;No Timers running
      return 0
   ;Comes here when called from windows when timer counts down (fires)
   ;OR when we call internally to kill a timer
   ID:=ID+0 ;make sure it's handled as an integer
   FoundIt:=false
   IsFunction:=false
   KillItLater:=false ;Indicator to kill a single-shot timer
   Loop,Parse,TimerIDs,CSV ;Parse the TimerIDs string
   {
      if(Mod(A_Index,4)=1){ ;Check TimerID
         if(ID=abs(A_LoopField)){
            FoundIt:=true
            TimerID:=abs(A_LoopField) ;can be -
            if(A_LoopField<0)
               KillItLater:=true
         }
      }
      Else If(FoundIt and Mod(A_Index,4)=2){ ;get window handle
         hWnd:=A_LoopField+0
      }
      Else if(FoundIt and Mod(A_Index,4)=3){ ;See if it is a function
         if(A_LoopField>0)
            IsFunction:=true
      }
      Else if(FoundIt and Mod(A_Index,4)=0){
         ;Set user's Timer Target Variable to true or call function
         ;A_LoopField contains address of the variable or function
         if(!KillIt){
             if(!IsFunction){
                 NumPut(asc("1"),A_LoopField+0,"char")
                 result:=1
             }
             Else{ ;Call the address registered with RegisterCallback.
                 ;User's callback process must accept 5 params
                 ;The first must be the TimerID
                 ;The other 4 are user params for use in internal calls
                 ;to the callback function.
                 ;The user's callback is expected to return an integer.
                 result:=DllCall(A_LoopField+0,uint,ID+0,int,0,int,0,int,0,int,0,int)
             }
             if(!KillItLater)
                 return result
         }
         If(KillIt or KillItLater){ ; Kill the timer
            if(!DllCall("KillTimer",uint,hWnd,uint,TimerID)){
                HaveError:=1
                GoTo _SetWinTimer_ReturnOnError_
            }
            ;remove Timer info from string
            pos:=InStr(TimerIDs,ID)
            StartPos:=pos
            Loop,4
               pos:=InStr(TimerIDs,",",0,pos+1)
               if(!pos) ;reached end of string
                   TimerIDs:=""
               Else
                   TimerIDs:=SubStr(TimerIDs,1,StartPos-1) . SubStr(TimerIDs,pos+1)
            if(!TimerIDs){
               return 0 ;indicates to SetWinTimer that no timers are left
            }
            return 1
         }
      }
   }
   ;Shouldn't get to here so must be error
   hWnd:=0
   HaveError:=1
   GoTo _SetWinTimer_ReturnOnError_
}

I don't have any idea how well it might function with more and more timers running or the accuracy that it will attain, so if it breaks something consider yourself forewarned. Wink

Thanks to lexiKos for the nifty way to use ToolTip to demonstrate things like this. I was tired of using MsgBox with all the beeping.

I haven't came up with a need for this yet so this was an academic exercise that I hope might be of some practical use to someone.

dmatch


Last edited by dmatch on Wed Feb 06, 2008 6:54 pm; edited 5 times in total
Back to top
View user's profile Send private message
Lexikos



Joined: 17 Oct 2006
Posts: 2739
Location: Australia, Qld

PostPosted: Sun Feb 03, 2008 4:42 am    Post subject: Re: SetWinTimer Function (Uses No labels/No Globals) Reply with quote

Would I be correct in assuming you must poll for timer completion (i.e. repeatedly check whether a timer has fired?) This more or less defeats the purpose of having timers, since you could just as well "count down" in a loop.
Quote:
I wrote this function that uses no globals or labels
True, but it does globally reserve the function name "WM_TIMER", and prevent any other script's from detecting WM_TIMER while it is running...

Interesting solution, nonetheless.
Back to top
View user's profile Send private message
dmatch



Joined: 15 Oct 2007
Posts: 113

PostPosted: Mon Feb 04, 2008 6:06 pm    Post subject: Re: SetWinTimer Function (Uses No labels/No Globals) Reply with quote

lexiKos wrote:
Would I be correct in assuming you must poll for timer completion (i.e. repeatedly check whether a timer has fired?) This more or less defeats the purpose of having timers, since you could just as well "count down" in a loop.
Quote:
I wrote this function that uses no globals or labels
True, but it does globally reserve the function name "WM_TIMER", and prevent any other script's from detecting WM_TIMER while it is running...

Interesting solution, nonetheless.
Yes the loop (polling) is needed to check timer fired status, but there could be other things going on in this loop depending on what a person wanted to do. It doesn't have to be dedicated to just the timers. For instance, the loop could call a function and check the timer fired variable there too. Anyway, the polling loop is not unlike a main window message loop in a Windows Application and many things could be done from it.

I agree it has limited function.

Edited to Add: I think I will get rid of the WM_TIMER message handler. That shouldn't be too hard to do.

dmatch
Back to top
View user's profile Send private message
dmatch



Joined: 15 Oct 2007
Posts: 113

PostPosted: Wed Feb 06, 2008 6:46 pm    Post subject: Updated First Post Reply with quote

First post was updated with new code that does not trap WM_TIMER messages and will also call a function(s) when a timer(s) fires.

This makes it really global-less.

dmatch
Back to top
View user's profile Send private message
basi



Joined: 28 May 2007
Posts: 4

PostPosted: Sat Feb 09, 2008 9:39 pm    Post subject: Reply with quote

Thank you dMatch

this will come in handy

-Basi
Back to top
View user's profile Send private message
Display posts from previous:   
Post new topic   Reply to topic    AutoHotkey Community Forum Index -> Scripts & Functions All times are GMT
Page 1 of 1

 
Jump to:  
You can post new topics in this forum
You can reply to topics in this forum


Powered by phpBB © 2001, 2005 phpBB Group