Implementing System Timers

Introduction

Welcome to System Events and Integration. We are at the very beginning of this course, which is a great place to start with something practical. In this first lesson, we will build a window that updates itself once per second by using a system timer. The key idea is simple: we want periodic work to happen without freezing the application.

By the end of this lesson, we will understand how to start a timer with SetTimer, receive WM_TIMER messages, update a label dynamically, and stop the timer cleanly when the window closes. This pattern is very common in event-driven desktop programs.

Why System Timers Matter

In WinAPI, the window does not run code in a constant loop that checks the clock again and again. Instead, Windows sends messages to our program, and our program reacts to them. A system timer fits nicely into that model: we ask Windows to notify our window every so often, then we handle that notification like any other message.

This matters because we want the interface to stay responsive. If we used Sleep inside the main thread, the window would pause and stop reacting to input. A timer avoids that problem; Windows delivers WM_TIMER through the normal message system. For a simple counter, an interval of 10001000 milliseconds, which is 11 second, is exactly what we need.

Preparing The Program Structure

Before we start the timer itself, we need a few building blocks: Unicode support for wide strings, numeric IDs for the timer and label, and a window procedure to receive messages. We also keep a counter that survives across message calls because the timer will arrive many times during the life of the window.

C++
#include <windows.h>
#include <wchar.h>

#define ID_TIMER_CLOCK 1
#define ID_LABEL_TIME  101

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
    static int secondsElapsed = 0;

    switch (uMsg) {
        // We will add message handling here
    }

    return DefWindowProcW(hwnd, uMsg, wParam, lParam);
}

This structure should look familiar. We begin by defining unique IDs for both the timer and the label to keep our controls organized. Within the window procedure, we declare secondsElapsed as a static variable; this ensures the counter persists across multiple function calls as the timer fires. Finally, DefWindowProcW handles any messages we don't explicitly process, ensuring our window maintains its standard behavior.

Creating The Label And Starting The Timer

When the window is first created, Windows sends WM_CREATE. This is the right moment to build child controls and start services the window needs. Here, we create a static text label that shows the elapsed seconds, then we start a timer that will notify the same window every second.

C++
case WM_CREATE: {
    // Create a label to display the timer status
    CreateWindowW(
        L"STATIC", L"Seconds: 0",
        WS_VISIBLE | WS_CHILD,
        20, 20, 150, 20,
        hwnd, (HMENU)ID_LABEL_TIME, NULL, NULL
    );

    // Start a timer that fires every 1000 ms
    SetTimer(hwnd, ID_TIMER_CLOCK, 1000, NULL);
    return 0;
}

The first call creates a child control of class STATIC; it appears inside our main window because hwnd is passed as the parent. The label starts with the text Seconds: 0. Then SetTimer registers a timer with ID ID_TIMER_CLOCK and an interval of 1000 milliseconds. Because the last argument is NULL, we are not using a callback function; instead, Windows will send WM_TIMER messages to this window procedure.

Handling Each Timer Tick

Once the timer is active, Windows periodically sends WM_TIMER to our window. This is where the visible update happens. We first make sure the message belongs to our timer, then we increase the counter, build a new wide string, find the label, and replace its text.

C++
case WM_TIMER: {
    // Make sure this is our timer
    if (wParam == ID_TIMER_CLOCK) {
        secondsElapsed++;

        wchar_t buffer[32];
        swprintf(buffer, 32, L"Seconds: %d", secondsElapsed);

        HWND hLabel = GetDlgItem(hwnd, ID_LABEL_TIME);
        SetWindowTextW(hLabel, buffer);
    }
    return 0;
}

Cleaning Up Resources

When a window is closed, it is important to stop any active timers. While Windows cleans up some resources automatically, explicitly calling KillTimer is a best practice. This ensures that no more timer messages are sent once the window starts the destruction process.

C++
case WM_DESTROY: {
    // Stop the timer to free resources
    KillTimer(hwnd, ID_TIMER_CLOCK);
    PostQuitMessage(0);
    return 0;
}

By handling WM_DESTROY, we stop the clock using its unique ID and then call PostQuitMessage to tell the application's message loop to terminate. This cycle of SetTimer in WM_CREATE and KillTimer in WM_DESTROY represents the complete lifecycle of a system timer.

Conclusion and Next Steps

We have now established a reliable pattern for periodic interface updates in WinAPI. By initializing our label and starting the timer during WM_CREATE, updating the display inside WM_TIMER, and ensuring a clean shutdown with KillTimer during WM_DESTROY, we’ve built an application that remains responsive while performing background work.

In the upcoming practice section, you will transition from theory to implementation. You’ll have the opportunity to apply these patterns hands-on, solidifying your understanding of how system events drive modern desktop software.

Sign up

Join the 1M+ learners on CodeSignal

Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal