Introduction
In this article, I show a method for building a simple event-based framework for Windows GUI applications based on a technique I outlined in a previous article: Event Driven Programming using Template Specialization.
This event driven framework uses template function specializations, which allow the user of an event-driven library to write event handlers, without having to explicitly register them with some kind of call-back system. This is an attractive alternative to typical event-driven libraries which use function pointers or require overloading of virtual functions.
Responding to Windows Events
In order to respond to Windows events using the "winevent.hpp" library, we include the header and write our event handlers. The only requirement is that they have the following signature:
template<>
LRESULT OnMsg<WM_xxx>(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
}
The WM_xxx is the Windows message code that you want to respond to. It is common to call the function DefWindowProc() from within a custom handler.
Nothing else is required from the programmer, this function will be called when the application receives the given Windows message. Registration occurs automatically using some template magic.
Sample Application
I have included a sample application which demonstrates the event-driven framework. This program is a minimal Windows application that upon a key-press, sends 100,000 WM_TIMER messages to itself. It then writes to the screen how long this process took in msec.
#include <windows.h>
#include "winevent.hpp"
#include <time.h>
#include <stdio.h>
int nInt = 0;
int nStart = 0;
int nEnd = 0;
char buf[255];
template<>
LRESULT OnMsg<WM_CREATE>(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
strcpy(buf, "Press any key to start test");
return DefWindowProc(hWnd, WM_CREATE, wParam, lParam);
}
template<>
LRESULT OnMsg<WM_KEYDOWN>(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
nInt = 0;
strcpy(buf, "running test, please wait ...");
InvalidateRect(hWnd, NULL, true);
SendMessage(hWnd, WM_PAINT, 0, 0);
nStart = GetTickCount();
PostMessage(hWnd, WM_TIMER, 0, 0);
return DefWindowProc(hWnd, WM_KEYDOWN, wParam, lParam);
}
template<>
LRESULT OnMsg<WM_TIMER>(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
if (nInt++ < 100000) {
PostMessage(hWnd, WM_TIMER, 0, 0);
} else {
nEnd = GetTickCount();
sprintf(buf, "Time elapsed = %d msec",
((nEnd - nStart) * 1000) / CLOCKS_PER_SEC);
InvalidateRect(hWnd, NULL, true);
}
return 0;
}
template<>
LRESULT OnMsg<WM_DESTROY>(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
PostQuitMessage(0);
return 0;
}
template<>
LRESULT OnMsg<WM_PAINT>(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
PAINTSTRUCT ps;
HDC hdc;
hdc = BeginPaint(hWnd, &ps);
TextOut(hdc, 100, 100, buf, static_cast<int>(strlen(buf)));
EndPaint(hWnd, &ps);
return DefWindowProc(hWnd, WM_PAINT, wParam, lParam);
}
int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance, LPSTR szCmdLine, int nCmdShow)
{
InitMsgHandlers<NULL>();
static char szAppName[] = "demo";
HWND hwnd;
MSG msg;
WNDCLASSEX wndclass;
wndclass.cbSize = sizeof(wndclass);
wndclass.style = CS_HREDRAW | CS_VREDRAW;
wndclass.lpfnWndProc = WndProc;
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = 0;
wndclass.hInstance = hInstance;
wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wndclass.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
wndclass.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH);
wndclass.lpszClassName = szAppName;
wndclass.lpszMenuName = NULL;
RegisterClassEx(&wndclass);
hwnd = CreateWindow(szAppName, "Static Dispatch Demo",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL);
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
while ( GetMessage(&msg, NULL, 0, 0) ) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return static_cast<int>(msg.wParam);
}
The only requirement that is required to use the winevent.hpp event handling library is that there is a call to InitMsgHandlers<NULL>();. The NULL parameter is ignored, but is required to assure the correct order of compilation to use the library.
Event Dispatching
The winevent.hpp file takes care of the responsibility of dispatching events to the user defined specialization or to call the default handler.
template<typename int Msg_N>
LRESULT OnMsg(HWND hWnd, WPARAM wParam, LPARAM lParam) {
return DefWindowProc(hWnd, Msg_N, wParam, lParam);
}
typedef LRESULT (*MsgFxnPtr)(HWND, WPARAM, LPARAM);
MsgFxnPtr MsgHandlers[WM_APP + 1024];
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
return MsgHandlers[message](hWnd, wParam, lParam);
}
template<typename int Dummy>
void InitMsgHandlers() {
MsgHandlers[0] = &OnMsg<0>;
MsgHandlers[1] = &OnMsg<1>;
...
MsgHandlers[1023] = &OnMsg<1023>;
MsgHandlers[WM_APP + 0] = &OnMsg<WM_APP + 0>;
MsgHandlers[WM_APP + 1] = &OnMsg<WM_APP + 1>;
...
MsgHandlers[WM_APP + 1023] = &OnMsg<WM_APP + 1023>;
}
The user may notice a lot of superfluous Windows messages included, but this way, we can support future Windows messages as long as they follow below 1024 (WM_USER).
The InitMsgHandlers() function fills an array of message handlers with either a user defined template function specialization if it exists, otherwise it simply uses the default OnMsg(...) function which calls DefWindowProc(...). We use the compiler to do the work of deciding which function to use.
Summary
There are other ways to implement an event dispatching system for a Windows GUI, but this represents an easy to use alternative, function pointer or virtual function dispatch systems, which require significantly more work on behalf of the user of an event-driven framework.