A quick one today, I’ve got a basic application up and running using the simple framework located here.
One interesting thing to note is that Nehe uses global variables for the active, fullscreen and keys variables. I don’t want to do this. I want to have these as members of an Application class to begin with and eventually I’ll create an input handler class and also a renderer that may hold onto these. However due to the way in which the windows are created only static non-member functions can be used to handle the callbacks from the window to handle messages like WM_NCCREATE, WM_ACTIVATE, WM_CLOSE, WM_KEYDOWN and so on. To allow a member function to be able to handle these messages you need to do perform some trickery. You need to define two static callback methods that will handle and messages from the window.
Firstly, we need to alter the way in which the window is created. The first thing we need to do is the give the name of one of the static methods you defined as the initial callback i.e. called on creation of the window:
wc.lpfnWndProc = &InitialWndProc; // WndProc Handles Messages
The next thing you need to do is to alter the CreateWindowEx call and add change the last parameter from NULL to ‘this’. This last parameter is an optional extra data that is passed to the creation of the window. By passing this we are passing a pointer to the current Application class to the window creation callback.
So I mentioned two static methods earlier. The first method is to handle the creation of the window:
static LRESULT CALLBACK InitialWndProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) {
if (Msg == WM_NCCREATE) {
LPCREATESTRUCT create_struct = reinterpret_cast(lParam);
void * lpCreateParam = create_struct->lpCreateParams;
Application * theApp = reinterpret_cast< Application*>(lpCreateParam);
SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast(theApp));
SetWindowLongPtr(hWnd, GWLP_WNDPROC, reinterpret_cast(&StaticWndProc));
return theApp->WndProc(hWnd, Msg, wParam, lParam);
}
return DefWindowProc(hWnd, Msg, wParam, lParam);
}
Here we are taking the extra data that was passed into the CreateWindowEX, casting it to an Application pointer and then telling hWnd (the handle to the window) what the new user data (GWLP_USERDATA) will be and also WNDPROC will be that will handle messages from now on in. Essentially what this does is that it tells the window that all messages should be sent to the static method StaticWndProc with the user data being theApp (which is cast to a long pointer). Also note the call to theApp->WndProc, this is important later.
The second static method you need to define is the one that will pass on all messages that come to the window during runtime.
static LRESULT CALLBACK StaticWndProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) {
LONG_PTR user_data = GetWindowLongPtr(hWnd, GWLP_USERDATA);
Application * theApp = reinterpret_cast< Application*>(user_data);
return theApp->WndProc(hWnd, Msg, wParam, lParam);
}
This is simple, all it does is take the user data from window that we setup earlier, cast it to the right Application* type and then pass along the callback to the class’s desired member.
So now we can have a member function on Application like so:
LRESULT CALLBACK Application::WndProc( HWND hWnd, // Handle For This Window
UINT uMsg, // Message For This Window
WPARAM wParam, // Additional Message Information
LPARAM lParam) // Additional Message Information
{
switch (uMsg) // Check For Windows Messages
{
case WM_ACTIVATE: // Watch For Window Activate Message
{
if (!HIWORD(wParam)) // Check Minimization State
{
m_active=TRUE; // Program Is Active
}
else
{
m_active=FALSE; // Program Is No Longer Active
}
return 0; // Return To The Message Loop
}
case WM_SYSCOMMAND: // Intercept System Commands
{
switch (wParam) // Check System Calls
{
case SC_SCREENSAVE: // Screensaver Trying To Start?
case SC_MONITORPOWER: // Monitor Trying To Enter Powersave?
return 0; // Prevent From Happening
}
break; // Exit
}
case WM_CLOSE: // Did We Receive A Close Message?
{
PostQuitMessage(0); // Send A Quit Message
return 0; // Jump Back
}
case WM_KEYDOWN: // Is A Key Being Held Down?
{
m_keys[wParam] = TRUE; // If So, Mark It As TRUE
return 0; // Jump Back
}
case WM_KEYUP: // Has A Key Been Released?
{
m_keys[wParam] = FALSE; // If So, Mark It As FALSE
return 0; // Jump Back
}
case WM_SIZE: // Resize The OpenGL Window
{
reSizeGLScene(LOWORD(lParam),HIWORD(lParam)); // LoWord=Width, HiWord=Height
return 0; // Jump Back
}
}
// Pass All Unhandled Messages To DefWindowProc
return DefWindowProc(hWnd,uMsg,wParam,lParam);
}
And have achieved what I wanted, be able to have member variables and a member function be able to handle the various messages that come from the window.
Note: There is no error handling if the Application casting goes wrong, I might add that at some point.