Valhalla Legends Forums Archive | Advanced Programming | OO Window wrapper library (Win32)

AuthorMessageTime
Eibro
I lost my old library, which was kind of sad, but it gave me a chance to write a new one. Everything went fine, though i'm having a problem associating a control of a built-in type with a WindowBase derived object. My class hierarchy is as follows:

WindowClass
WindowBase -> Window
WindowBase -> Control -> Edit (Button, Static, etc.)

Now, for a brief explanation: With windows created using a user-defined class (eg. fill out a WNDCLASSEX struct) I pass WindowBase::StaticWindowProcedure as the callback function. Along with that, I pass a this pointer to CreateWindowEx() as the lpParam. This allows me to catch the WM_NCCREATE message in WindowBase::StaticWindowProcedure and grab the WindowBase-derived object from the lpCreateParams member of the passed CREATESTRUCT. Armed with the window handle, and the associated object, I bind the two together using SetWindowLongPtr(hWnd, GWLP_USERDATA, Object);

This looks like:[code]   static WindowBase* RealObject = 0;
   if (uMsg == WM_NCCREATE)
   {
      CREATESTRUCT* CreateStruct = reinterpret_cast<CREATESTRUCT*>(lParam);
      RealObject = reinterpret_cast<WindowBase*>(CreateStruct->lpCreateParams);

      try
      {
         WindowBase::BindObjectToHandle(hWnd, RealObject);
         RealObject->m_hWnd = hWnd;
      } catch (WindowingException& Error) {
         Error.Display(hWnd);
         ExitProcess(Error.LastError());
      }
   } else {
      RealObject = WindowBase::RetrieveObjectFromHandle(hWnd);   
      if (RealObject && RealObject->MessageHandler(uMsg, wParam, lParam) == TRUE)
         return 0;
   }[/code]As you can see, messages are routed to the correct object after retrieving a pointer to the object from GetWindowLongPtr(hWnd, GWLP_USERDATA); and calling RealObject->MessageHandler(...);
This is all fine and dandy, however, the WM_NCCREATE and WM_CREATE messages are dispatched before CreateWindowEx returns, which means I can't subclass the window until after these messages are dispatched and handled. So, when using built-in control types I can't retrieve the associated object from information passed with WM_CREATE or WM_NCCREATE messages as they're handled in the default (un-subclassed) window procedure. I think I have a way around this, but I'd like to hear suggestions from others first, as mine is a little ... ick.

Thanks.
September 3, 2003, 4:49 AM
Eibro
This is what I came up with:[code]Control::Control(TCHAR* szClassName)
//   : WindowBase(szClassName)
{
   WindowClass ThisClass;
   static tstring SubclassName(szClassName);
   SubclassName.append(TEXT("_WLIB_SUBCLASS"));

   GetClassInfoEx(GetModuleHandle(0), szClassName, &ThisClass);

   m_DefaultProcedure = ThisClass.lpfnWndProc;
   ThisClass.lpfnWndProc = WindowBase::StaticWindowProcedure;
   ThisClass.lpszClassName = SubclassName.c_str();
   ThisClass.cbSize = sizeof(WNDCLASSEX);

   RegisterClassEx(&ThisClass);
   
   m_ClassName = const_cast<TCHAR*>(SubclassName.c_str());
}[/code]Essentially, I sort of Superclass each built-in class type. So when using an Edit object (Control-derived) its window class is actually "EDIT_WLIB_SUBCLASS", which falls back on "EDIT"s default window procedure for all messages. This way, you can subclass any controls by deriving your own object from a base type and implementing your own virtual MessageHandler routine.

Though, the above code is a little ... sloppy; it attempts to register a [subclassed] window class everytime you create a control. Too bad there isn't some sort of IsWindowClass() function; I could use that to determine if the window class was registered or not.
September 3, 2003, 6:32 AM
Skywing
You could use GetClassInfo/GetClassInfoEx to determine if the class is preexisting.

Why can't you simply set the subclass in WM_CREATE?

Edit: Fixed incorrectly addressed second portion of post.
September 3, 2003, 2:50 PM
Eibro
[quote author=Skywing link=board=23;threadid=2554;start=0#msg19932 date=1062600644]
You could use GetClassInfo/GetClassInfoEx to determine if the class is preexisting.

Why can't you simply set the subclass in WM_CREATE?

Edit: Fixed incorrectly addressed second portion of post.
[/quote]Originally, I was doing this:
[code]CreateWindowEx(...);
SetWindowLongPtr(hWnd, GWLP_WNDPROC, WindowBase::StaticWindowProcedure);[/code]I *thought* this was working, but WM_NCCREATE and WM_CREATE messages were dispatched to the default window procedure before CreateWindowEx returned. Therefore, I couldn't subclass the window until these key messages were handled by the default handler. I think the first message I received from any control was WM_PAINT.
September 3, 2003, 5:17 PM
Skywing
[quote author=Eibro link=board=23;threadid=2554;start=0#msg19951 date=1062609471]
[quote author=Skywing link=board=23;threadid=2554;start=0#msg19932 date=1062600644]
You could use GetClassInfo/GetClassInfoEx to determine if the class is preexisting.

Why can't you simply set the subclass in WM_CREATE?

Edit: Fixed incorrectly addressed second portion of post.
[/quote]Originally, I was doing this:
[code]CreateWindowEx(...);
SetWindowLongPtr(hWnd, GWLP_WNDPROC, WindowBase::StaticWindowProcedure);[/code]I *thought* this was working, but WM_NCCREATE and WM_CREATE messages were dispatched to the default window procedure before CreateWindowEx returned. Therefore, I couldn't subclass the window until these key messages were handled by the default handler. I think the first message I received from any control was WM_PAINT.
[/quote]
Since you are using a custom window class, you should be able to control the window procedure those first few messages are sent to. Just stick the SetWindowLongPtr call in there?
September 3, 2003, 8:47 PM
Kp
[quote author=Eibro link=board=23;threadid=2554;start=0#msg19888 date=1062564552]
[code] static WindowBase* RealObject = 0;[/code][/quote]Why is this pointer static? You initialize it in both control paths anyway.

Also, have you considered having a WindowBase method that creates the window, subclasses it, and retrieves whatever information you want at the time of creation? That approach won't get you the NCCREATE, but since it gives you the window handle and you're in the implementation of a WindowBase method - that should be enough info to do your binding?
September 3, 2003, 9:05 PM
Eibro
[quote author=Kp link=board=23;threadid=2554;start=0#msg19996 date=1062623143]
[quote author=Eibro link=board=23;threadid=2554;start=0#msg19888 date=1062564552]
[code] static WindowBase* RealObject = 0;[/code][/quote]Why is this pointer static? You initialize it in both control paths anyway.

Also, have you considered having a WindowBase method that creates the window, subclasses it, and retrieves whatever information you want at the time of creation? That approach won't get you the NCCREATE, but since it gives you the window handle and you're in the implementation of a WindowBase method - that should be enough info to do your binding?
[/quote]It's static because-- I don't know why. It's just a habit, any variables I declare in a window procedure are always static.
Anyway, I read your response and it sounded perfect. By perfect, I mean, too easy. I knew there was a reason why I didn't do it this way to begin with; but I tried anyway. Using that method I wasn't able to catch the WM_CREATE and WM_NCCREATE messages, and none of my objects recieved this notification. I suppose I could try saving the CREATESTRUCT* passed in the static window procedure and dispatching my own WM_CREATE message once the object/handle relationship is resolved; but one couldn't assume certain things about the state of the window during this notification as they could with the real WM_CREATE message.
September 4, 2003, 1:38 AM
Kp
[quote author=Eibro link=board=23;threadid=2554;start=0#msg20038 date=1062639524]It's static because-- I don't know why. It's just a habit, any variables I declare in a window procedure are always static.
Anyway, I read your response and it sounded perfect. By perfect, I mean, too easy. I knew there was a reason why I didn't do it this way to begin with; but I tried anyway. Using that method I wasn't able to catch the WM_CREATE and WM_NCCREATE messages, and none of my objects recieved this notification. I suppose I could try saving the CREATESTRUCT* passed in the static window procedure and dispatching my own WM_CREATE message once the object/handle relationship is resolved; but one couldn't assume certain things about the state of the window during this notification as they could with the real WM_CREATE message.[/quote]From your earlier posts, it seemed that the only reason you needed to catch the WM_CREATE was to set the userdata window long ptr to your object's data - a task which can be done any time you have the window handle and the pointer to the object. Did I miss some more involved task that requires being in WM_CREATE to do it?
September 4, 2003, 5:04 AM
Eibro
[quote author=Kp link=board=23;threadid=2554;start=0#msg20095 date=1062651872]
[quote author=Eibro link=board=23;threadid=2554;start=0#msg20038 date=1062639524]It's static because-- I don't know why. It's just a habit, any variables I declare in a window procedure are always static.
Anyway, I read your response and it sounded perfect. By perfect, I mean, too easy. I knew there was a reason why I didn't do it this way to begin with; but I tried anyway. Using that method I wasn't able to catch the WM_CREATE and WM_NCCREATE messages, and none of my objects recieved this notification. I suppose I could try saving the CREATESTRUCT* passed in the static window procedure and dispatching my own WM_CREATE message once the object/handle relationship is resolved; but one couldn't assume certain things about the state of the window during this notification as they could with the real WM_CREATE message.[/quote]From your earlier posts, it seemed that the only reason you needed to catch the WM_CREATE was to set the userdata window long ptr to your object's data - a task which can be done any time you have the window handle and the pointer to the object. Did I miss some more involved task that requires being in WM_CREATE to do it?
[/quote]WM_NCCREATE is the earliest i'm able to associate Object/Handle. It's essential that it's done then so the window objects can recieve all initialization messages. (WM_CREATE, mostly) Most applications do initialization of child controls and such in WM_CREATE, so missing this message is unacceptable.

Edit: Hmm, it seems like this is the way MFC operates as well, by associating in the WM_NCCREATE message.
Also, I just found this page which seems to describe exactly what i'm doing (towards the bottom) http://hyper.sunjapan.com.cn/~hz/win32/subclas3.htm
September 4, 2003, 5:30 AM

Search