52875.fb2
Alright! We've got our object properly associated with the window handle, and messages being properly routed. But what about that flexibility we mentioned earlier? As it stands, the window procedure has to handle all possible messages – an approach that would require reengineering the class for every new application. Not going to cut it.
In considering how to make this more flexible, I was struct by the fact that MFC and VCL call their methods of associating window messages with message handlers message maps. Being the STL afficionado that I am, that immediately struct a chord with me. How's about I use a std::map to tie a particular message to a particular message handler? That way I could insert and replace a message handler at any time without modifying the class.
For those of you not familar with the Standard Template Library (STL), I heartily recommend SGI's STL Documentation as both an introduction and a reference.
typedef long (* tyMessageHandler)(Window &, HWND, long, long);
typedef std::map<long, tyMessageHandler> tyMessageMap;
typedef tyMessageMap::iterator tyMessageIterator;
The Window class contains a single instance of tyMessageMap . This message map is then searched for the existence of a handler for a given message by the message router, and if none exist the default window procedure is invoked. I also chose to provide two (static) message handlers, partly as a template and partly to provide default functionality.
// Window::GetMessageHandler returns the address of the registered
// message handler if one exists
tyMessageIterator Window::GetMessageHandler(long message) {
// m_MsgHandlers is a tyMessageMap instance
tyMessageIterator it = m_MsgHandlers.find(message);
if (it == m_MsgHandlers.end()) return NULL;
return it;
}
// Window::OnClose is a static method called in response to WM_CLOSE
long Window::OnClose(Window &wnd, HWND hwnd, long param0, long param1) {
DestroyWindow(hwnd);
return 0;
}
// Window::OnDestroy is a static method called in response to WM_DESTROY
long Window::OnDestroy(Window &wnd, HWND hwnd, long param0, long param1) {
PostQuitMessage(0);
return 0;
}
// Final message handler version
LRESULT CALLBACK Window::MsgRouter(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) {
Window *wnd = 0;
if (message == WM_NCCREATE) {
// retrieve Window instance from window creation data and associate
wnd = reinterpret_cast<Window *>((LPCREATESTRUCT)lparam)->lpCreateParams;
::SetWindowLong(hwnd, GWL_USERDATA, reinterpret_cast<long>(wnd));
// save window handle
wnd->SetHWND(hwnd);
} else
// retrieve associated Window instance
wnd = reinterpret_cast<Window *>(::GetWindowLong(hwnd, GWL_USERDATA));
if (wnd) {
tyMessageIterator it;
it = wnd->GetMessageHandler(message);
if (it != NULL) return (it->second)((*wnd), hwnd, wparam, lparam);
}
return DefWindowProc(hwnd, message, wparam, lparam);
}
Okay, so the message router takes care of object association, checks to see if there is an appropriate message handler and calls the default window procedure if there isn't. Fine. Now how do you add these message handlers? Behold the Window::RegisterMessageHandler method!
tyMessageHandler Window::RegisterMessageHandler(long message, tyMessageHandler handler) {
tyMessageHandler m = NULL;
tyMessageIterator it = m_MsgHandlers.find(message);
if (it != m_MsgHandlers.end()) m = it->second;
m_MsgHandlers.insert(std::pair<long,tyMessageHandler>(message, handler));
return m;
}
Alright, so it wasn't so dramatic. The RegisterMessageHandler method inserts a message handler into the message map and returns the previous message handler, if there was one. That about wraps it up for message handling (I say about because I'll revisit one of the methods described above later). Now let's turn to integrating the Window with the application message pump.