06/10/2025
En el desarrollo de aplicaciones para Windows, a menudo nos encontramos con la necesidad de ejecutar una porción de código de manera periódica. Ya sea para actualizar la interfaz de usuario, realizar comprobaciones en segundo plano o crear animaciones, los temporizadores son una herramienta fundamental. La API de Win32 nos proporciona una función robusta y versátil para esta tarea: SetTimer. A través de esta guía, exploraremos en profundidad cómo funciona, sus diferentes modos de uso y cómo gestionarla correctamente para crear aplicaciones eficientes y estables.

¿Qué es la API SetTimer?
La función SetTimer es una API del sistema operativo Windows que permite a un desarrollador crear un temporizador que genera eventos a intervalos regulares. Una vez que se inicia un temporizador, el sistema operativo se encarga de notificar a nuestra aplicación cada vez que transcurre el intervalo de tiempo especificado. Esta notificación puede llegar en forma de un mensaje de Windows o mediante la llamada a una función específica que definamos. Esta flexibilidad la convierte en una herramienta indispensable en la caja de herramientas de cualquier programador de Win32.
La firma (o prototipo) de esta función es la siguiente:
UINT_PTR SetTimer( HWND hWnd, UINT_PTR nIDEvent, UINT uElapse, TIMERPROC lpTimerFunc );Para utilizarla correctamente, es crucial entender cada uno de sus parámetros:
- hWnd: Es un manejador (handle) a la ventana que estará asociada con el temporizador. Todos los eventos del temporizador se dirigirán a esta ventana. Si este parámetro es
NULL, el temporizador no estará asociado a ninguna ventana, lo cual tiene implicaciones en cómo se manejan los otros parámetros. - nIDEvent: Un valor entero distinto de cero que sirve como identificador único para el temporizador. Si se crea un temporizador asociado a una ventana (
hWndno esNULL), este ID debe ser único para esa ventana. SihWndesNULL, este parámetro es ignorado. - uElapse: Es el corazón del temporizador. Especifica el intervalo de tiempo, en milisegundos, entre cada evento del temporizador. Por ejemplo, un valor de 1000 hará que el evento se dispare cada segundo. Es importante notar que Windows tiene límites; si el valor es menor que
USER_TIMER_MINIMUM(10 ms), se ajustará a este mínimo. Si es mayor queUSER_TIMER_MAXIMUM, se ajustará al máximo. - lpTimerFunc: Este parámetro opcional determina cómo nuestra aplicación será notificada. Si es
NULL, el sistema enviará un mensajeWM_TIMERa la cola de mensajes de la ventana especificada enhWnd. Si, por el contrario, proporcionamos un puntero a una función (una función de tipoTIMERPROC), el sistema llamará a esa función directamente cada vez que el temporizador se dispare.
Si la función SetTimer tiene éxito, devuelve un valor entero que identifica al nuevo temporizador. Si falla, devuelve cero. Este valor de retorno es importante, especialmente cuando necesitamos destruir el temporizador más tarde.
Dos Caminos para un Mismo Fin: WM_TIMER vs. TimerProc
Como mencionamos, SetTimer nos ofrece dos mecanismos distintos para manejar los eventos del temporizador. La elección entre uno y otro dependerá de la arquitectura de nuestra aplicación y de la tarea específica que deba realizar el temporizador.

Método 1: El Mensaje WM_TIMER
Este es el enfoque más común, especialmente en aplicaciones basadas en una interfaz gráfica tradicional. Al dejar el parámetro lpTimerFunc como NULL, le indicamos al sistema que, cada vez que el temporizador se active, debe poner un mensaje WM_TIMER en la cola de mensajes de nuestra aplicación. Luego, en el procedimiento de ventana (WndProc), podemos capturar este mensaje y ejecutar el código correspondiente.
Ventajas:
- Integración natural con el bucle de mensajes de una aplicación GUI.
- El código del temporizador se mantiene centralizado dentro del
WndProc. - Simple de implementar para tareas relacionadas directamente con la actualización de una ventana.
Método 2: La Función Callback TimerProc
Al proporcionar una función de devolución de llamada (callback), el sistema llamará a esta función directamente cada vez que el temporizador se dispare. Esta función debe tener una firma específica, conocida como TIMERPROC.
Ventajas:
- El código está más encapsulado y separado de la lógica del
WndProc, lo que puede mejorar la legibilidad. - No depende directamente de la cola de mensajes, aunque el hilo que creó el temporizador debe seguir despachando mensajes para que el callback sea invocado.
- Útil para tareas que no están intrínsecamente ligadas a una ventana específica.
Tabla Comparativa
| Característica | WM_TIMER | Callback TimerProc |
|---|---|---|
| Implementación | lpTimerFunc es NULL. Se maneja con un case WM_TIMER: en el WndProc. | Se proporciona un puntero a una función con la firma VOID CALLBACK TimerProc(...). |
| Dependencia de Ventana | Requiere un hWnd válido, ya que el mensaje se envía a esa ventana. | Puede funcionar sin un hWnd asociado. |
| Organización del Código | La lógica reside dentro del procedimiento de ventana, potencialmente mezclada con otros manejadores de mensajes. | La lógica está contenida en una función separada, promoviendo un código más limpio. |
| Caso de Uso Ideal | Actualizar controles de una ventana, animaciones simples, etc. | Tareas periódicas en segundo plano que no modifican directamente la UI. |
El Ciclo de Vida de un Temporizador
Un temporizador no solo se crea, sino que también se puede modificar y, fundamentalmente, se debe destruir para liberar recursos del sistema.

Creación y Modificación
La creación se realiza con la primera llamada a SetTimer. Una característica interesante es que SetTimer también sirve para modificar un temporizador existente. Si se llama a SetTimer con el mismo hWnd y nIDEvent de un temporizador ya activo, el sistema no crea uno nuevo. En su lugar, reemplaza el temporizador anterior. El temporizador se reinicia, lo que significa que el nuevo intervalo de tiempo (uElapse) comenzará a contar desde ese momento, ignorando el valor anterior.
Destrucción: La Función KillTimer
Cuando un temporizador ya no es necesario, es crucial destruirlo utilizando la función KillTimer. No hacerlo puede llevar a fugas de recursos y a comportamientos inesperados, especialmente si la ventana asociada se destruye pero el temporizador sigue activo.
La firma de KillTimer es mucho más simple:
BOOL KillTimer( HWND hWnd, UINT_PTR uIDEvent );- hWnd: El manejador de la ventana asociada al temporizador que se quiere destruir. Debe ser el mismo que se usó al crearlo.
- uIDEvent: El identificador del temporizador que se quiere destruir.
La función devuelve un valor distinto de cero si tiene éxito y cero si falla. Un lugar común para llamar a KillTimer es en el manejador del mensaje WM_DESTROY, asegurando que todos los temporizadores asociados a una ventana se limpien cuando esta se cierra.

Ejemplo Práctico en C++
A continuación, se muestra un programa de ejemplo que crea una ventana y utiliza un temporizador para actualizar su título con la hora actual cada segundo. El código está preparado para usar tanto el método WM_TIMER como el callback TimerProc, controlado por la macro USE_TIMER_PROC.
#include <windows.h> #include <time.h> // Descomenta la siguiente línea para usar el método de callback // #define USE_TIMER_PROC #define MY_TIMER_ID 100 #ifdef USE_TIMER_PROC // Función callback para el temporizador VOID CALLBACK MyTimerProc(HWND hWnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime) { time_t rawtime; struct tm* timeinfo; char buffer[80]; if (idEvent == MY_TIMER_ID) { time(&rawtime); timeinfo = localtime(&rawtime); strftime(buffer, 80, "Hora actual: %I:%M:%S %p", timeinfo); SetWindowText(hWnd, buffer); } } #endif // Procedimiento de ventana principal LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { WNDCLASS wc = {0}; wc.lpfnWndProc = WndProc; wc.hInstance = hInstance; wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); wc.lpszClassName = "TimerDemoClass"; RegisterClass(&wc); HWND hWnd = CreateWindow("TimerDemoClass", "Demo de SetTimer/KillTimer", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 400, 200, NULL, NULL, hInstance, NULL); ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); // Bucle de mensajes MSG msg; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return (int)msg.wParam; } LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_CREATE: // Iniciar el temporizador al crear la ventana #ifdef USE_TIMER_PROC SetTimer(hWnd, MY_TIMER_ID, 1000, MyTimerProc); #else SetTimer(hWnd, MY_TIMER_ID, 1000, NULL); #endif break; #ifndef USE_TIMER_PROC case WM_TIMER: if (wParam == MY_TIMER_ID) { time_t rawtime; struct tm* timeinfo; char buffer[80]; time(&rawtime); timeinfo = localtime(&rawtime); strftime(buffer, 80, "Hora actual: %I:%M:%S %p", timeinfo); SetWindowText(hWnd, buffer); } break; #endif case WM_DESTROY: // Es crucial destruir el temporizador al cerrar la ventana KillTimer(hWnd, MY_TIMER_ID); PostQuitMessage(0); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } return 0; } Preguntas Frecuentes (FAQ)
- ¿Qué pasa si llamo a `SetTimer` para un temporizador que ya existe?
- El temporizador existente es reemplazado por el nuevo. El contador de tiempo se reinicia con el nuevo valor de `uElapse`. Esto es útil para cambiar el intervalo de un temporizador dinámicamente.
- ¿Puedo tener múltiples temporizadores en una misma ventana?
- Sí. Puedes crear varios temporizadores para una misma ventana, siempre y cuando utilices un `nIDEvent` (identificador) diferente para cada uno. Esto te permite gestionar diferentes eventos periódicos con distintas frecuencias.
- ¿Es `SetTimer` preciso al milisegundo?
- No. La precisión de `SetTimer` no está garantizada. El valor `uElapse` es un tiempo de espera mínimo. El sistema enviará la notificación tan pronto como sea posible después de que transcurra el intervalo, pero puede haber retrasos debido a la carga del sistema u otras tareas de mayor prioridad. No debe usarse para aplicaciones de tiempo real que requieran alta precisión.
- ¿Es obligatorio destruir los temporizadores con `KillTimer`?
- Absolutamente. No destruir un temporizador es una fuente común de errores y fugas de recursos. Siempre debes llamar a `KillTimer` cuando el temporizador ya no sea necesario, típicamente en el manejador de `WM_DESTROY` para temporizadores asociados a ventanas.
Conclusión
La función SetTimer, junto con su contraparte KillTimer, proporciona un mecanismo potente y flexible para la gestión de eventos periódicos en aplicaciones Windows. Comprender sus dos modos de operación —mediante mensajes WM_TIMER o funciones callback TimerProc— nos permite elegir la estrategia más adecuada para cada situación. Al gestionar correctamente el ciclo de vida de los temporizadores, aseguramos que nuestras aplicaciones sean robustas, eficientes y libres de fugas de recursos, dominando así uno de los aspectos más importantes de la programación asíncrona en el entorno Win32.
Si quieres conocer otros artículos parecidos a Guía Completa para Usar SetTimer en Windows puedes visitar la categoría Juegos.
