Выбрать главу

// 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.

The Window Class and the Application Message Pump

A typical Windows message pump looks something like this:

MSG msg;

while(GetMessage(&msg, hwnd, 0, 0)) {

 TranslateMessage(&msg);

 DispatchMessage(&msg);

// other procedures

}

Microsoft actually advises against this form of message loop because GetMessage has a tri-state return value and the above may cause an attempt at execution even when there was an error.

We do it anyway.

This article is getting long and I'm getting tired, so I'll dump the code and then break it down.

// Window::OnDestroy, revisited

long Window::OnDestroy(Window &wnd, HWND hwnd, long param0, long param1) {

 PostQuitMessage(0);

 wnd->SetExit(true);

 return 0;

}

// Window::HandleMessage ties everything together

bool Window::HandleMessages() {

 static MSG msg;

 if (!m_hwnd) throw std::runtime_error(std::string("Window not yet created"));

 if ((m_UsePeekMessage) ? ::PeekMessage(&msg, m_hwnd, 0, 0, PM_REMOVE) : ::GetMessage(&msg, m_hwnd, 0, 0)) {

  ::TranslateMessage(&msg);

  ::DispatchMessage(&msg);

 } else {

  if (IsExit()) {

   SetExitCode((long)msg.lParam);

   return false;

  }

  if (m_UseWaitMessage) WaitMessage();

 }

 return true;

}

There's a bunch of functions not introduced here. Obviously, the constructor and methods like Create and ShowWindow ; I leave this as an exercise for the inexperienced reader and a chore for the expert. There are also the boolean variables m_UseWaitMessage, m_UsePeekMessage and m_Exit ; the exit code for the Window (to pass to the application if the Window is the main window); and the static method to register the windowclass. The code above is fairly self-explanatory. As is evident, I use exceptions to avoid having to pass and compare return values for application-terminating errors. I also use the m_UsePeekMessage and m_UseWaitMessage as discriminants between using GetMessage and PeekMessage , and whether or not to use WaitMessage respectively.

That's it. And here's a simple example of my implementation in use:

Window *g_wnd;

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE /* hPrevInstance */,

 LPSTR lpCmdLine, int nShowCmd) {

 try {

  g_wnd = new Window(hInstance);

  g_wnd->Create(NULL, WS_OVERLAPPEDWINDOW|WS_VISIBLE);

  if (!g_wnd) throw std::runtime_error(std::string("Initialization Failed: Window::Create"));

  g_wnd->UsePeekMessage();

  g_wnd->UseWaitMessage(false);

  g_wnd->ShowWindow(nShowCmd);

  while(true) {

   if (!g_wnd->HandleMessages()) break;

  }

 } catch(std::runtime_error &e) {

  ::MessageBox(NULL, e.what(), "Runtime Error", MB_OK|MB_ICONERROR|MB_DEFBUTTON1|MB_TASKMODAL|MB_TOPMOST);

  return –1;

 } catch(std::logic_error &e) {

  ::MessageBox(NULL, e.what(), "Logic Error", MB_OK|MB_ICONERROR|MB_DEFBUTTON1|MB_TASKMODAL|MB_TOPMOST);

  return –1;

 } catch(…) {

  ::MessageBox(NULL, "Unhandled Exception", "Unknown Error", MB_OK|MB_ICONERROR|MB_DEFBUTTON1|MB_TASKMODAL|MB_TOPMOST);

  return –1;

 }

 return g_wnd->ExitCode();

}