Share via

Using SetTimer API

Anonymous
2012-09-26T21:02:00+00:00

Hi,

I'd like to ask for some advice on the SetTimer API please.

I am using the following code:

Public Declare Function SetTimer Lib "user32" (ByVal hwnd As Long, ByVal nIDEvent As Long, _

    ByVal uElapse As Long, ByVal lpTimerFunc As Long) As Long

Public Declare Function KillTimer Lib "user32" (ByVal hwnd As Long, ByVal nIDEvent As Long) As Long

Public Declare Function IsClipboardFormatAvailable Lib "user32" (ByVal wFormat As Long) As Long

Public bPasteTextAvailable As Boolean

Public  lngTimerID As Long

Public bInTimer As Boolean

Sub StartOnTime()

    If bInTimer Then

        lngTimerID = KillTimer(0, lngTimerID)

        If lngTimerID = 0 Then

            Debug.Print "Error : Timer Not Stopped at " & Now

            Exit Sub

        End If

        bInTimer = False       

    Else

        lngTimerID = SetTimer(0, 0, 2000, AddressOf CheckClipboard)

        If lngTimerID = 0 Then

            Debug.Print "Error : Timer Not Generated at " & Now

            Exit Sub

        End If

        bInTimer = True

    End If

End Sub

Sub KillOnTime()

    lngTimerID = KillTimer(0, lngTimerID)

    bInTimer = False

End Sub

Sub CheckClipboard()

    If IsClipboardFormatAvailable(1) = 0 Then

        bPasteTextAvailable = False

    Else

        bPasteTextAvailable = True

    End If

    rib.InvalidateControl ("btnPasteText")    'Invalidates a CustomUI button on the ribbon; the GetEnabled call back uses                                                                           'bPasteTextAvailable to determine enabled status of the button.

End Sub

  1. Are there any drawbacks to having a timer running a process continually, say every 2-3 seconds in a PowerPoint add-in?
  2. What are the consequences of not killing the timer with KillTimer, for example on closing PowerPoint? / will the timer be automatically killed when I close PowerPoint?
  3. Are there any tips for ensuring the time is always running?
  4. What if anything would cause the timer to stop running/crash?
  5. Is there a way to see if there are any timers running?  Say, for example I loose the value of the lngTimerID and/or bInTimer... can I look for a running timer? and recover the timer's ID?
  6. What happens if other code is running when the timer is supposed to run?  Will it run after the other code has finished?
  7. What about if PowerPoint is busy saving a large file, or has it's Options window open?

Thanks in advance for any advice you can offer.

Cheers

Rich

Microsoft 365 and Office | PowerPoint | For home | Windows

Locked Question. This question was migrated from the Microsoft Support Community. You can vote on whether it's helpful, but you can't add comments or replies or follow the question.

0 comments No comments
Answer accepted by question author
  1. Andreas Killer 144.1K Reputation points Volunteer Moderator
    2012-10-04T10:12:33+00:00

    Try to compare if

      Private Declare Function GetForegroundWindow Lib "user32" () As Long

    is the same handle before you call myRibbon.InvalidateControl("buttonName")

    (GetActiveWindow retrieves the window handle to the active window associated with the thread that calls the function. That is not the active window in the OS.)

    Andreas.

    0 comments No comments
Answer accepted by question author
  1. Andreas Killer 144.1K Reputation points Volunteer Moderator
    2012-09-27T10:59:40+00:00

    At first: Your code is wrong, please do as follows:

    Copy the example below into a normal module

    Open the immediate window

    Execute sub StartTimer

    Look a few seconds into the immediate window to see whats happen.

    Execute sub CallTimer a few times to see whats happen.

    Execute sub StopTimer

    Also refers to the MSDN article for more information about SetTimer:

    http://msdn.microsoft.com/en-us/library/windows/desktop/ms644906%28v=vs.85%29.aspx

    Now to your questions:

    1. IMHO no, except that your code need some time to execute.
    2. No / yes. Windows terminates the timer if the thread that creates the timer is closed.
    3. The timer is called automatically until you call KillTimer (or you compile your project during

    development).

    1. Doesn't matter... for the timer handling.
    2. AFAIK no.
    3. If the timer code is called it might be possible that other code or events are interrupted temporary.
    4. No sure, but IMHO the code should run.

    Andreas.

    Option Explicit

    Private hWnd As Long

    Private Const WM_TIMER = &H113

    Private Declare Function SetTimer Lib "user32" ( _

        ByVal hWnd As Long, ByVal nIDEvent As Long, ByVal uElapse As Long, _

        ByVal lpTimerFunc As Long) As Long

    Private Declare Function KillTimer Lib "user32" ( _

        ByVal hWnd As Long, ByVal nIDEvent As Long) As Long

    Private Declare Function GetForegroundWindow Lib "user32" () As Long

    Private Function TimerFunc(ByVal hWnd As Long, ByVal wMsg As Long, _

        ByVal nIDEvent As Long, ByVal dwTime As Long) As Long

      'Called from the windows timer?

      If wMsg = WM_TIMER Then

        Debug.Print "Windows " & Now & ": " & hWnd & "," & nIDEvent

      Else

        Debug.Print "OurSelf " & Now & ": " & hWnd & "," & wMsg & "," & nIDEvent

      End If

    End Function

    Sub StartTimer()

      hWnd = GetForegroundWindow

      SetTimer hWnd, 1, 1000, AddressOf TimerFunc

      SetTimer hWnd, 2, 1500, AddressOf TimerFunc

    End Sub

    Sub CallTimer()

      TimerFunc 123456, 0, 3, Now

    End Sub

    Sub StopTimer()

      KillTimer hWnd, 1

      KillTimer hWnd, 2

    End Sub

    0 comments No comments

11 additional answers

Sort by: Most helpful
  1. Anonymous
    2012-09-28T14:04:22+00:00
    1. IMHO yes. All the arguments are pushed onto the call stack, I don't know what's happen if you use a sub, but theoretically this would cause memory leaks.

    OK, thanks.  Makes no real difference to me to include the arguments.

    1. For 2007 and above try

    Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" ( _

        ByVal lpClassName As String, ByVal lpWindowName As String) As Long

    hWnd = FindWindow(vbNullString, Application.Caption)

    This appears to work perfectly.  Thanks.

    1. Well, the MSDN article says that the existing timer is replaced by the new timer, but a short test with WinXP shows that this is not true.

    On my PC the new timer is ignored and when I kill the timer and try to etablish a new timer with different timings, the timings from the last timer are used.

    OK... but I think what I am asking is: is there any harm in repeatedly calling StartTimer?  That way, if for some reasone the timer is stopped, it will get started again each time the user clicks a certain button or something...

    1. No chance.

    The problem is that code of the current instance is running, so the only way to have the intellisense and the timer running is to use a different application (windows thread) for the timer.

    But PP is a Multi-Use application type, so there is no way to create 2 instances of that application.

    Oh well, if you don't ask you never get...

    Many thanks.

    Cheers

    Rich

    0 comments No comments
  2. Andreas Killer 144.1K Reputation points Volunteer Moderator
    2012-09-28T11:41:51+00:00
    1. IMHO yes. All the arguments are pushed onto the call stack, I don't know what's happen if you use a sub, but theoretically this would cause memory leaks.
    2. For 2007 and above try

    Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" ( _

        ByVal lpClassName As String, ByVal lpWindowName As String) As Long

    hWnd = FindWindow(vbNullString, Application.Caption)

    1. Well, the MSDN article says that the existing timer is replaced by the new timer, but a short test with WinXP shows that this is not true.

    On my PC the new timer is ignored and when I kill the timer and try to etablish a new timer with different timings, the timings from the last timer are used.

    1. No chance.

    The problem is that code of the current instance is running, so the only way to have the intellisense and the timer running is to use a different application (windows thread) for the timer.

    But PP is a Multi-Use application type, so there is no way to create 2 instances of that application.

    Andreas.

    0 comments No comments
  3. Anonymous
    2012-09-28T10:44:24+00:00

    Thanks Andreas, but I have a few questions;

    1. I’m a bit confused by the various arguments being used in the Timer function... are all the arguments required?, even if I don’t want to do anything with them in the TimerFunc?
    2. Also, I notice you use hWnd = GetForegroundWindow, what if the application (PowerPoint in this case) is not in the foreground?  Is there a way to get the hWnd of the Application/add-in?  It is possible to switch windows while PowerPoint is starting up, so then ppt wouldn’t be the active window and the timer doesn’t work.
    3. Please can you tell me; if the code for StartTimer is:

    Sub StartTimer()

    hWnd = GetForegroundWindow

    SetTimer hWnd, 1, 1000, AddressOf TimerFunc

    End Sub

    If I run StartTimer twice, will it replace the existing timer or make additional ones? (I’m thinking that because nIDEvent is always 1, it should replace the existing timer if it exists, or start a new one if it doesn’t, providing hWnd hasn’t changed.  What I’m not clear on is whether there is an hWnd value attached to the Application, or the add-in, or is it attached to individual presentations?)

    1. Final question (challenge!), if I open the VBE while the timer is running all intellisense dropdown’s are killed by the timer.  Is there an easy way to pause/disable the timer when the VBE is opened?  (I know I’m pushing my luck here!).

    Cheers

    Rich

    0 comments No comments