How to filter Timer ID for WM_TIMER event messages

Windows specific questions.
Tonigau
Posts: 36
Joined: Feb 25, 2021 20:19

How to filter Timer ID for WM_TIMER event messages

Post by Tonigau »

I can set a Timer & capture WM_TIMER messages but I am getting unwanted messages from a system timer (Scrollbars of an EditBox within Window_Main when mouseover)
To prove this I can disable SetTimer & I see event messages only for mouseover Scrollbars.

I don't know how to filter just my Timer 1 event messages ( I have tried lots, & referencing various C code examples & get either error or it not works)

Here is a snippet that captures (all) WM_TIMER event messages...

Code: Select all

SetTimer(Window_Main, 1, 1000, 0 )  ' Set 1 second timer, ID = 1

Do
    WaitEvent(Window_Main, msg)

    If msg.message = WM_TIMER Then GoDoSomething

Loop Until Window_Event_Close(Window_Main, msg)
There is a good resource here...
https://flylib.com/books/en/4.348.1.65/1/
[but OnTimer is probably a MFC thing]
adeyblue
Posts: 300
Joined: Nov 07, 2019 20:08

Re: How to filter Timer ID for WM_TIMER event messages

Post by adeyblue »

Timers are uniquely identified by the combination of window and id.

So in your example you'd need to check for
if msg.message = WM_TIMER AndAlso msg.hwnd = Window_Main AndAlso msg.wParam = 1 Then GoDoSomething
Tonigau
Posts: 36
Joined: Feb 25, 2021 20:19

Re: How to filter Timer ID for WM_TIMER event messages

Post by Tonigau »

Thank you, it works perfectly.
deltarho[1859]
Posts: 4313
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: How to filter Timer ID for WM_TIMER event messages

Post by deltarho[1859] »

I have not tried this – haven't got Lothar's library.

Instead of using

Code: Select all

SetTimer(Window_Main, 1, 1000, 0 )  ' Set 1 second timer, ID = 1
it seems to me that using

Code: Select all

Dim As UINT_PTR nIDEvent
nIDEvent = SetTimer(Window_Main, 0, 1000, 0 )  ' Set 1 second timer
should give us an unused ID so that there is no chance of it colliding with any other timer.

We would then use 'SetTimer(Window_Main, nIDEvent, 1000, 0 )'

KillTimer is not used, but if it were, then we would use 'KillTimer Window_Main, nIDEvent'

We can then use adeyblue's

Code: Select all

if msg.message = WM_TIMER AndAlso msg.hwnd = Window_Main AndAlso msg.wParam = nIDEvent Then GoDoSomething

Of course, I could be misunderstanding the SetTimer function at Microsoft. It won't be the first time. :)

Anyway, give a whirl – I'd like to know whether I am talking gibberish or not. :lol:

Edit: I used *nIDEvent and should have used nIDEvent. I used the idea on some of my code, and it gave an ID = 1, but I wasn't expecting a collision.
Last edited by deltarho[1859] on Oct 21, 2023 8:59, edited 1 time in total.
deltarho[1859]
Posts: 4313
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: How to filter Timer ID for WM_TIMER event messages

Post by deltarho[1859] »

Yours truly wrote:We would then use 'SetTimer(Window_Main, nIDEvent, 1000, 0 )'
Not required.
Edit: I used *nIDEvent and should have used nIDEvent.
nIDEvent is an integer and not a pointer; as quoted by Microsoft.
deltarho[1859]
Posts: 4313
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: How to filter Timer ID for WM_TIMER event messages

Post by deltarho[1859] »

Tonigau wrote:It looks like the timer timeout state is interfered by the WM_MOUSMOVE ?
You should still have a problem. See Moving the mouse blocks WM_TIMER and WM_PAINT

Have a look at timeSetEvent. I use it here in the code block CPULoad2.bas.

timeSetEvent uses a separate thread of execution and does not get involved with message queues.
deltarho[1859]
Posts: 4313
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: How to filter Timer ID for WM_TIMER event messages

Post by deltarho[1859] »

Yours truly wrote:Anyway, give a whirl – I'd like to know whether I am talking gibberish or not.
Looks like I am talking gibberish.

I tried this (I am using the SDK)

Code: Select all

Case WM_CREATE
  SetTimer( hDlg, IDC_Timer, 100, Null)
  nID1 = SetTimer( hDlg, Null, 250, Null)
  nID2 = SetTimer( hDlg, Null, 500, Null)
  Print nID1,nID2
  KillTimer hDlg, nID1
  KillTimer hDlg, nID2 
and got

Code: Select all

1         1
I was hoping to get

Code: Select all

1         2
:o

Anyway, Tonigau, see if timeSetEvent works for you. You may find the 'jitter' you mentioned disappears.
deltarho[1859]
Posts: 4313
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: How to filter Timer ID for WM_TIMER event messages

Post by deltarho[1859] »

Yours truly wrote:Looks like I am talking gibberish.
Nope. I misunderstood the docs. The code above is wrong. I should have used NULL for SetTimer's first parameter.

Correct code:

Code: Select all

Case WM_CREATE
  SetTimer( hDlg, IDC_Timer, 100, Null ) '  As used in my original code
  nID1 = SetTimer( Null, 0, 100, Null )
  nID2 = SetTimer( Null, 0, 500, Null )
  Print nID1, nID2
  KillTimer Null, nID1
  KillTimer Null, nID2
and now get

Code: Select all

1254      1253
Running the code again I get

Code: Select all

32125     32124
Answers on a postcard to - if you know why nID1 > nID2. :)

If SetTimer's first parameter is valid and not Null the value returned is the same as the second parameter. I thought that the Windows crypto function docs are obfuscated maintaining cryptographic principles. :lol:

Correcting my first post we could use:
Dim As UINT_PTR timerID
timerID = SetTimer( Null, 0, 1000, 0 ) ' Set 1 second timer.

adeyblue's code should now read:

Code: Select all

if msg.message = WM_TIMER AndAlso msg.hwnd = Window_Main AndAlso msg.wParam = timerID Then GoDoSomething
Phew :!:
adeyblue
Posts: 300
Joined: Nov 07, 2019 20:08

Re: How to filter Timer ID for WM_TIMER event messages

Post by adeyblue »

deltarho[1859] wrote: Oct 21, 2023 23:18

Code: Select all

32125     32124
Answers on a postcard to - if you know why nID1 > nID2. :)
Global timers are 'allocated' in reverse order.

Code: Select all

// in win32k(full).sys - InternalSetTimer function
// cTimerId is the last created global timer id.
// pwnd is pretty much the HWND

        if (pwnd == NULL) {

            WORD timerIdInitial = cTimerId;

            /*
             * Pick a unique, unused timer ID.
             */
            do {

                if (--cTimerId <= TIMERID_MIN)
                    cTimerId = TIMERID_MAX;

                if (cTimerId == timerIdInitial) {

                    /*
                     * Flat out of timers bud.
                     */
                    HMFreeObject(ptmr);
                    return 0;
                }

            } while (FindTimer(NULL, cTimerId, flags, FALSE) != NULL);

            ptmr->nID = (UINT)cTimerId;

        } else {
            ptmr->nID = nIDEvent;
        }
deltarho[1859]
Posts: 4313
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: How to filter Timer ID for WM_TIMER event messages

Post by deltarho[1859] »

@adeyblue

I got your postcard. :) We cannot have a more definitive answer than that. Thank you.

Since the desktop is a window with a handle of zero, then using NULL for SetTimer's first parameter is effectively creating a desktop timer, so referring to it as a global timer is a good description. Windows will broadcast WM_TIMER rather than sending it to a specific window, necessitating your additional tests.

However, I think that 'AndAlso msg.hwnd = Window_Main' is surplus to requirements by virtue of

SetTimer(Window_Main, 0, 1000, 0 ) '<4th>

Do 'Main loop to check WinAPI event states:

WaitEvent(Window_Main, msg)

in Tonigau's first code block here.

Thanks again.
deltarho[1859]
Posts: 4313
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: How to filter Timer ID for WM_TIMER event messages

Post by deltarho[1859] »

Out of curiosity I looked at a fair number of posts in both the PowerBASIC and FreeBASIC archives to see if wParam was being checked when WM_TIMER was used. A surprising number of posts did not check.

If a global timer is running, then other running applications which process WM_TIMER but do not check wParam will process WM_TIMER messages which have nothing to do with it. I have never checked wParam.

My SetCompilerSwitchesII, which ships with WinFBE, uses WM_TIMER. It simply checks, every 100ms, whether WinFBE is running, has been minimized or has been restored and responds accordingly if true. No damage then could occur if it received a WM_TIMER which had nothing to do with it. We could have a situation where SetCompilerSwitchesII checked WinFBE very much more often than 100ms; 10ms I believe. This would be avoided by checking WM_TIMER's wParam.

I cannot think offhand if any damage could occur if an application processed a WM_TIMER message that had nothing to do with it. If true, then we would in trouble. Again, this would be avoided by checking WM_TIMER's wParam. WM_TIMER mentions wParam, but does not advise us that it is best practice that it should be checked.

My advice then is to always check wParam when using WM_TIMER.

I will update SetCompilerSwitchesII. :) Edit: Done
adeyblue
Posts: 300
Joined: Nov 07, 2019 20:08

Re: How to filter Timer ID for WM_TIMER event messages

Post by adeyblue »

They're global timers as in they don't belong to any specific window, not global in the sense everybody gets a message about them.
wm_timer messages are only sent to the thread that called SetTimer, whether that's SetTimer(NULL, ...) or SetTimer(hwnd, ...)
And since the hwnd has to have been created by the same thread that calls SetTimer(hwnd, ...), only your app can set timers for your app.

What other apps can do (or at least could, I'm not bootng 11 to find out) is stop any SetTimer(NULL, ...) timers you have set, as there are no checks on killing those.

Code: Select all

'' stop all global timers
'' wouldn't recommend you ever do, or even run, this
For i as Ulong = &h100 to &h7fff
    KillTimer(NULL, i)
Next
deltarho[1859]
Posts: 4313
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: How to filter Timer ID for WM_TIMER event messages

Post by deltarho[1859] »

adeyblue wrote:not global in the sense everybody gets a message about them.
I didn't mean every 'Tom, @#$& and Harry' only those using SetTimer.

We have a similar scenario to timeBeginPeriod. Before Windows 10, version 2004, it affected a global Windows setting. Since then, it only affects other running applications which use timeBeginPeriod.

An analogy: One hundred people have a radio, but only twenty have them turned on. A message is broadcast, but only twenty people will hear it.
deltarho[1859]
Posts: 4313
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: How to filter Timer ID for WM_TIMER event messages

Post by deltarho[1859] »

I am now in trouble.

In Function WndProc, among other declarations, 'Static timerID As UINT_PTR' is used.

In WndPrc we have:

Code: Select all

Select Case uMsg
 
  Case WM_CREATE
    timerID = SetTimer( Null, 0, 2000, Null )
    Print timerID
 
  Case WM_TIMER
    Print "In WM_TIMER"

I am getting a different timerID each time the code is run, but a WM_TIMER message is not being sent. That is I am not getting "In WM_TIMER". The return value is nonzero and therefore successful – but clearly is not.

However, if we use 'timerID = SetTimer( hWnd, 1, 2000, Null )' then that works; timerID = 1.

Microsoft specifically writes, "If the call is not intended to replace an existing timer, nIDEvent should be 0 if the hWnd is NULL."

The SetTimer docs are badly written.

"If the function succeeds and the hWnd parameter is not NULL, then the return value is a nonzero integer. An application can pass the value of the nIDEvent parameter to the KillTimer function to destroy the timer."

In other words, the return value is nIDEvent. Why refer to it as 'a nonzero integer' when it is nIDEvent?

Not only badly written but, it seems to me, we cannot create a 'global timer'.

So keep well away from using hWnd as Null. If we only create one timer, we don't need to check wParam. We do if we use more than one timer as in:

Code: Select all

SetTimer ( hWnd, 1, 100, Null )
SetTimer( hWnd, 2, 1000, Null )
We now need to test wParam in WM_TIMER.

What a shambles, Microsoft. :x
deltarho[1859]
Posts: 4313
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: How to filter Timer ID for WM_TIMER event messages

Post by deltarho[1859] »

I did a Google search on 'SetTimer(Null,'

If we do not specify a hWnd, then we have to create a callback function. The SetTimer docs neglect to mention this. Using 'SetTimer(Null,' will not see a WM_TIMER sent. The reason? Wndows does not know which window to send WM_TIMER to. OK, that makes sense, but by neglecting to tell us a callback function is needed in the SetTimer docs when hWnd is Null leads us to make erroneous assumptions as to what is going on.

The SetTimer docs really are a shambles.
Post Reply