Advertisement

Win32 MSG.message Property

Started by April 13, 2001 06:18 PM
21 comments, last by PyroBoy 23 years, 9 months ago
After you call PeekMessage, you''re left with a MSG structure describing the message that was just retrieved, and you pass that to DispatchMessage to get it into your window procedure. This structure has a "message" property that identifies the message retrieved. Is this the same value as would be present inside the window procedure after DispatchMessage is called? For example, could you do this?: MSG mess; if (PeekMessage(&mess, winHandle, 0, 0, PM_REMOVE)) { if (mess.message == WM_CLOSE) //do some stuff else //pass unhandled messages to the default procedure //with DispatchMessage etc. } I''ve been trying to catch messages this way, and it just doesn''t seem to wanna work... I''ve traced it through with the debugger, and the mess.message seems to be exactly the same value as the uMsg parameter of the window procedure, where logic of this sort is usually done. I ask because I''m trying to write a wrapper class that will take care of all this stuff. I don''t wanna have to set up a big honkin'' callback procedure every time I create a window. I''d just like to be able to create a window object and call a method to retrieve information about the next message in its queue. Simple, straightforward, and frustrating as hell due to the structure of the windows message system! :-) Is this impossible? Anyone been able to implement something similar?
Yep, the message member of MSG is the same one that gets passed to the window procedure. My guess is that you're not calling TranslateMessage and DispatchMessage, which is preventing the os from talking to your Window.

Here's some code I use to handle messages in just about all of my umpteen zillion DirectX wrappers:

while (1){   // Check to see if we have a message in the queue   if (PeekMessage (&msg, NULL, 0, 0, PM_REMOVE))   {      // Exit the message loop on WM_QUIT      if (msg.message == WM_QUIT)      {         break;      }      // Process other messages      else      {         TranslateMessage (&msg);         DispatchMessage (&msg);      }   }   else if (g_bGameActive)   {      // Do stuff here...   }   else   {      // Wait for something to happen      WaitMessage ();   }}


Hope that helps.

-Rabid

*Edit: Errrr...just viewed this after I posted it, and this thing totally destroyed my formatting. I hope collapsed brackets don't challenge your sanity...

Edited by - RabidOcelot on April 13, 2001 11:20:00 PM

Edited by - RabidOcelot on April 13, 2001 11:21:55 PM
Advertisement
Never mind that last edit. I should have tried pre tags in the first place. d''oh.
I've got it set up just about like that. It's retrieving all kinds of messages that I don't intend to trap myself, and passing them off to the default procedure quite nicely. It's just not getting the ones I want. Right now I'm just trying to capture WM_CLOSE...

Here's what I have set up. If anyone can figure out why this absolutely refuses to trap WM_CLOSE, I'll be eternally gratefull:

My wrapper class, CWindow, has this method:bool CWindow::GetMessage(MSG *messPtr){    //Check message queue    if (PeekMessage(&message, winHandle, 0, 0, PM_REMOVE))    {	        //Send message data out to caller        messPtr->message = message.message;        messPtr->hwnd = message.hwnd;        messPtr->wParam = message.wParam;        messPtr->lParam = message.lParam;        messPtr->time = message.time;        messPtr->pt = message.pt;        return true;    }    else return false;}


So as you can see, if there's a message waiting, it passes the message data to a MSG structure provided by the app that's calling the function, and returns true. If there's no message, it just returns false.

So in main I've got it being used like this:

//Stuff that was previously declared...CWindow mainWin (initialized and created);bool done = false;MSG mess;//Main loopwhile (!done){     //Check for windows messages     if (mainWin.GetMessage(&mess))     {         //Handle messages         if (mess.message == WM_CLOSE) done = true;         else mainWin.DefaultMessageHandler(&mess);     }}CWindow's DefaultMessageHandler function looks like this:void CWindow::DefaultMessageHandler(MSG *messPtr){    //Send message to handler    TranslateMessage(messPtr);    DispatchMessage(messPtr);}The actual message procedure itself is defined as a static member function of CWindow:LRESULT CALLBACK CWindow::MsgHandler(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam){     return DefWindowProc(hWnd, Msg, wParam, lParam);}


Through the debugger, I can watch the messages being captured, passed to the calling function(main), evaluated, and the ones I don't trap being fed to the default procedure. It all works great, except there's no bloody WM_CLOSE!!! If I change my main loop to quit on WM_PAINT, it catches it just fine and terminates the app, no problem. It's like windows isn't sending WM_CLOSE at all(through either clicking the close button on the window or sending directly via alt-F4 - I've tried both). The window dissapears, but the app keeps running, checking the message loop of a closed window.

Can someone offer any insight? This is driving me crazy! :-)

Edited by - PyroBoy on April 14, 2001 3:46:25 PM
Usually this is done so that the windows share one WndProc, but that WndProc calls another function to process messages based on which window handle it receives.

To do this, place a protected virtual member function called OnMessage in your CWindow class, which will receive and process messages. Make a static class member function called WndProc which can be used as a Windows callback. Have the callback function use the hWnd parameter to find which class instance it belongs to, and then call the class''s OnMessage function with the message.


Here is an example that shows you how to implement it:

Windows allows you to store 4 bytes of information in each HWND, which happens to be the perfect size for a pointer to a class, so when the CWindow class is created, you should simply store its this pointer in the HWND using SetWindowLong. Each time a message is sent to CWindow::WndProc, use GetWindowLong to retrieve the pointer to the class instance and call its virtual CWindow::OnMessage function, which will process the message.


class CWindow{public:  CWindow();  ~CWindow();protected:  virtual LRESULT OnMessage(UINT nMsg, WPARAM wParam, LPARAM lParam);private:  static LRESULT CALLBACK WndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam);  HWND handle;};CWindow::CWindow() : handle(NULL){  // add your own parameters in place of the ellipses, but the  // last one must be "this"  CreateWindow(..., this);}CWindow::~CWindow(){  // destroys the HWND, send CWindow::WndProc a WM_DESTROY first  if( handle ) DestroyWindow(handle);}LRESULT CWindow::OnMessage(UINT nMsg, WPARAM wParam, LPARAM lParam){  // all messages come here - derived classes can override this  return DefWindowProc(handle, nMsg, wParam, lParam);}LRESULT CALLBACK CWindow::WndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam){  CWindow* current = (CWindow*) GetWindowLong(hWnd, GWL_USERDATA);  // gotta set the handle here instead of in the constructor,  // since CreateWindow calls CWindow::WndProc before returning  // to CWindow::CWindow to give us the handle.  if( !current && nMsg == WM_NCCREATE )  {    // Extract the "this" pointer we stored in the constructor    LPCREATESTRUCT p = (LPCREATESTRUCT) lParam;    current = (CWindow*) p.dwUserData;    // attach the C++ object to the window handle    current->handle = hWnd;  }  // if there is still no pointer, something went terribly wrong!  if( !current )    return DefWindowProc(hWnd, nMsg, wParam, lParam);  // allow each window to process its own messages  return current->OnMessage(nMsg, wParam, lParam);}




The nice thing is that CWindow::WndProc only has to be written once, and the user can do other processing simply by deriving from CWindow and overriding the virtual function called OnMessage:


class CMainWindow : public CWindow{protected:  virtual LRESULT OnMessage(UINT nMsg, WPARAM wParam, LPARAM lParam)  {    if( nMsg == WM_CLOSE )    {      PostQuitMessage(0);      return 0;    }    else return CWindow::OnMessage(nMsg, wParam, lParam);  }};


Yeah, that''s the other way I thought of doing it. Virtual event function(s)...

That still doesn''t solve the mystery of the dissapearing WM_CLOSE though... What conditions could possibly cause windows to omit sending this message?

Advertisement
This is just a guess, and I could be way out in left field with this one, but...

Maybe your main loop never recieves WM_CLOSE because you never call PostQuitMessage anywhere?

I think Windows uses DestroyWindow (and thus WM_DESTROY and WM_NCDESTROY) when it wants to close a window, whereas PostQuitMessage causes a WM_CLOSE message to get posted to the message queue of the calling process.

Try calling PostQuitMessage when you determine that your application should be terminated.

-Rabid

Edited by - RabidOcelot on April 15, 2001 1:12:35 PM
Well, that could work, but since my app uses the trapping of WM_CLOSE to determine when it should be terminated, it would never get the chance to call it.
In a game situation I''m sure there would be other, application-defined conditions where the app could be terminated, but right now I''m just trying to get it closing a simple window when the user clicks the close button at the top.

I just tried trapping WM_DESTROY and WM_NCDESTROY and neither of those happen when the window is closed either. I tried setting it to WM_KEYDOWN, and it worked great. The app closed and terminated properly when I hit a key. It''s like windows just isn''t sending the proper closing messages. It''s gotta be something in my code, since when I had it set up to dispatch messages to an app-defined handler callback, it found WM_CLOSE just fine. Argh!!! :-)

Heh, this just keeps getting weirder =)

Ok, new hypothesis:

My window recieves WM_CLOSE because I do the following in response to the user pressing ESC:

case WM_KEYDOWN:   // Test which key was pressed   switch (wParam)   {      case VK_ESCAPE:         // Close the window         PostMessage (hWnd, WM_CLOSE, 0, 0);         break;   }   break;


Since you''re relying on the user pressing the window''s close button, perhaps you can trap WM_SYSCOMMAND where wParam == SC_CLOSE.

Gave that a shot... It doesn''t find any WM_SYSCOMMAND messages, regardless of the value of wParam.
Isn''t the Alt-F4 key combination set up so that it sends a WM_CLOSE message to the active window regardless of whatever else is going on? Cuz alt-f4ing my app closes the window, but the main loop never sees a WM_CLOSE - just like when I push the close button.
Like I said, I have had this set up in the past to trap messages inside the designated windows procedure, and it always properly caught WM_CLOSE, so I''d like to revisit my original question here... Is the "message" parameter of the MSG structure retrieved via PeekMessage a valid windows message identifier? If not, this would explain a few things, since the window IS dissapearing when I close it, leading me to believe WM_CLOSE is being sent, but failing the if (MSG.message == WM_CLOSE) test because the retrieved message value is screwy, and is being passed to the default procedure, which makes the window graphics dissapear but doesn''t cause my program to recognise that it''s time to quit.

Hmmm. Interesting bit to add here... I just tried checking for WM_CLOSE inside the message handler itself (the one DispatchMessage sends to) and it found it just fine... So it would appear that some messages (WM_CLOSE for one) are changed in value before ending up in the message procedure. So the answer to my original question appears to be "hell no!"...

I guess I''ve got some re-arranging to do! :-)

This topic is closed to new replies.

Advertisement