Archived

This topic is now archived and is closed to further replies.

mk83

Windows in a class

Recommended Posts

mk83    122
Hi Guys, I am trying to write a program, but to make it neater + in order to learn, I am trying to put the windows code into classes, it seemed to be going pretty well, I have managed to create a simple windows class that calls a static message handler, that is able to get a pointer to the class by using the getwindowlong function. All fine and well, when having multiple instances of a window each one responds only to the commands clicked on it! But then I created a second class, I create a window in this class by handing it the handle of the first window I have created and using GetWindowLong(ParentWindow, GWL_HINSTANCE) to get the instance of the program. This new class also has a static Message handling procedure, with the same code as for the previous class only modified to be applicable to the new class. When I create a new window with the class in my main program, it works fine, all the buttons work etc. when I create the new window from within the Message handling procedure of the first class I created, nothing works, the window appears, but the buttons don't work and if I trace through it, the Instance of the the class and the HWND of the window seem to get all jumbled up!!! I have tried many things, but just don't seem to see where I am going wrong, please help!! Source to create the first class
    
wMainWindow::wMainWindow(HINSTANCE hofProgram){
	setInstance(hofProgram);

   // define the window class

   #ifndef Main_Window_Class
   #define Main_Window_Class
   WNDCLASS wc;
   wc.style 		  = CS_HREDRAW | CS_VREDRAW;
   wc.lpfnWndProc   = &wMainWindow::WindowProc;
   wc.cbClsExtra    = 0;
   wc.cbWndExtra    = sizeof(wMainWindow*);
   wc.hInstance     = hInstance;
   wc.hIcon		     = NULL;
   wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
   wc.hbrBackground = (HBRUSH) COLOR_WINDOW;
   wc.lpszMenuName  = NULL;
   wc.lpszClassName = "Main Window Class";
   // Create the Window Class

   RegisterClass(&wc);
   #endif

   // Create an actual instance of the Window

   hMainWindow = CreateWindow(
                "Main Window Class", // Type of window we want to create

   				 "Transport manager Server", // Name of our Window

                WS_VISIBLE, // Style of our Window

                0,0,640,480, // Position and Size

                NULL, //No Parent

                NULL, //No Menu

                hInstance, // Belongs to the calling Program

                NULL);

   // Put a pointer to this instance of the class in the Window Header

   SetWindowLong(hMainWindow,GWL_USERDATA,(long)this);


   // create the exit button

   hbExit	  = CreateWindow(
   				"BUTTON",
      			"Exit",
               WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON,
               570,410,
               50,30,
               hMainWindow,
               NULL,
               (HINSTANCE) GetWindowLong(hMainWindow, GWL_HINSTANCE),
               NULL);

   //create the Add server button

   hbAddServer= CreateWindow(
   				"BUTTON",
      			"Add Server",
               WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON,
               470,410,
               90,30,
               hMainWindow,
               NULL,
               (HINSTANCE) GetWindowLong(hMainWindow, GWL_HINSTANCE),
               NULL);
};

LRESULT CALLBACK wMainWindow::WindowProc(HWND hwnd,	// handle of window

                                         UINT uMsg,	// message identifier

                                         WPARAM wParam,	// first message parameter

                                         LPARAM lParam 	// second message parameter

                                         ){
                                         
  // retrieve the pointer from the Window header

  wMainWindow* ThisWindow = (wMainWindow*) GetWindowLong (hwnd,GWL_USERDATA);
  switch (uMsg)
   {
   	case WM_CLOSE :
      	ProgActive = false;
         break;

      case WM_COMMAND :
         switch HIWORD(wParam)
         {
           	case BN_CLICKED :
            	if ((HWND)lParam == ThisWindow->hbExit)
               	ProgActive = false;

            	if ((HWND)lParam == ThisWindow->hbAddServer){
                HWND Temp = ThisWindow->GetHandle();
               	wServerWindow TestWindow(Temp);
               	TestWindow.Show();
               };

  	      		break;
         } // switch HIWORD(wParam)

         break;

   } // switch uMsg

	return(DefWindowProc(hwnd, uMsg, wParam, lParam));
};
  

for the second class

      
wServerWindow::wServerWindow(HWND ParentWindow){

	setInstance((HINSTANCE) GetWindowLong(ParentWindow, GWL_HINSTANCE)); \\ sets hInstance

   // Create an actual instance of the Window

   hServerWindow = CreateWindow(
                "Server Window Class", // Type of window we want to create

   				 "Servers", // Name of our Window

                WS_OVERLAPPEDWINDOW, // Style of our Window

                0,0,320,240, // Position and Size

                NULL, //Parent Window

                NULL, //No Menu

                hInstance, // Belongs to the calling Program

                NULL);

   if (!(hServerWindow)) {
   	// define the window class

   	WNDCLASS wc;
   	wc.style 		  = CS_HREDRAW | CS_VREDRAW;
   	wc.lpfnWndProc   = &wServerWindow::WindowProc;
   	wc.cbClsExtra    = 0;
   	wc.cbWndExtra    = sizeof(wServerWindow*);
      wc.hInstance     = hInstance;
   	wc.hIcon		     = NULL;
   	wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
   	wc.hbrBackground = (HBRUSH) COLOR_WINDOW;
   	wc.lpszMenuName  = NULL;
   	wc.lpszClassName = "Server Window Class";
   	// Create the Window Class

   	RegisterClass(&wc);

   	hServerWindow = CreateWindow(
                "Server Window Class", // Type of window we want to create

   				 "Servers", // Name of our Window

                WS_OVERLAPPEDWINDOW, // Style of our Window

                0,0,320,240, // Position and Size

                NULL, //Parent Window

                NULL, //No Menu

                hInstance, // Belongs to the calling Program

                NULL);
   };

   // Put a pointer to this instance of the class in the Window Header

   SetWindowLong(hServerWindow,GWL_USERDATA,(long)this);

   // create the exit button

   hbExit	  = CreateWindow(
   				"BUTTON",
      			"Exit",
               WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON,
               10,10,
               50,30,
               hServerWindow,
               NULL,
               (HINSTANCE) GetWindowLong(ParentWindow, GWL_HINSTANCE),
               NULL);

};

LRESULT CALLBACK wServerWindow::WindowProc(HWND hwnd,	// handle of window

                                         UINT uMsg,	// message identifier

                                         WPARAM wParam,	// first message parameter

                                         LPARAM lParam 	// second message parameter

                                         ){

  // retrieve the pointer from the Window header

  wServerWindow* ThisWindow = (wServerWindow*) GetWindowLong (hwnd,GWL_USERDATA);
  switch (uMsg)
   {
      case WM_COMMAND :
         switch HIWORD(wParam)
         {
           	case BN_CLICKED :
              	ThisWindow->Close();
  	      		break;
         } // switch HIWORD(wParam)

         break;

   } // switch uMsg

	return(DefWindowProc(hwnd, uMsg, wParam, lParam));
};
    
The message handling procedure of the first class creates a new window of the second class!! but it does not function correctly, If a create a new window of the second class from within the main programm and not from the Message handling procedure the second window behaves correctly. What am I missing? Thanks for the help, MK83 Edited by - mk83 on November 8, 2001 4:29:59 AM

Share this post


Link to post
Share on other sites
mk83    122
Hi Magmai,

How would it normally be done? this is the first time I am trying it so any help would be great!!

Thanks,
Robert

Share this post


Link to post
Share on other sites
Guest Anonymous Poster   
Guest Anonymous Poster
From a quick lookover it looks as if in the wServerWindow ctor you are creating a window of class "Server Window Class" before you have actually registered that class.

Share this post


Link to post
Share on other sites
Shannon Barber    1681
Often a window class is registered for each window, and the code for registration and creation is pushed into a base class. And they usually use RegisterWindowEx and CreateWindowEx (they were added with Win95 for additional window styles).

quote:

From a quick lookover it looks as if in the wServerWindow ctor you are creating a window of class "Server Window Class" before you have actually registered that class.


He tries to create a window, and if it fails he calls the register and then create again. Thought you had it for a minute...

Share this post


Link to post
Share on other sites
Guest Anonymous Poster   
Guest Anonymous Poster
Oops - I missed that.

Anyway (luckily), I''m pretty confident that the problem is this:

When you call CreateWindow() in the wServerWindow ctor you are not passing in the handle of the parent window (which is the handle given to the ctor). This would account for messages failing to reach the window.

Share this post


Link to post
Share on other sites
mk83    122
Thanks for the help so far, unfortunatly I have still not been able to fix the problem! I''m wondering if it is perhaps a scope problem? I.e. the instance of the class I create only has scope in the function that that creates it? That would explain why calls to the instance of the class don''t work outside of the original creating function?

I''m gonna try using global variables and see if I can get around the problem that way,

Just another question is there a way of declaring something so that it has a wider scope than just the creating structure?

Magmai, should I create a base class and then inherit from those for my new classes? That would make sense because most of the windows will have the same class and create functions, my only problem with that is that the base class would have to have the Message handling function as part of it! but the different versions of windows would have different buttons etc on it so each message handling function would have to be different and that would not be possible with a base class, or am I missing something? I''ll play around a bit and see if I can get it to work!

Thanks,
MK83

Share this post


Link to post
Share on other sites
DEimers    122
This is a very interesting subject, I think, and I would appreciate it if someone could post a link to a tutorial or source code on the subject.

Judging by the past, it seems certain that something we cannot imagine today will dominate the landscape in 2021. That''''s why the future of the PC is even more exciting than the past.

Share this post


Link to post
Share on other sites
avianRR    100
Ok, there''s a very simple reason why what your trying to do will not work. The address your providing for the wndProc is the address of a function in a class. When you call the function in a line of code like...
MyWindow.WndProc();
the compiler will secretly pass a parameter to the function telling it which instance of that class type (i.e. the ''this'' pointer value).
When windows calls the WndProc function it does not pass the ''this'' parameter. Therefore the function has no idea which instance it is and act''s unpredictably.

P:\ray\main.cpp(74) : error C2440: ''type cast'' : cannot convert from ''long (__thiscall Wnd::*)(struct HWND__ *,unsigned int,unsigned int,long)'' to ''long (__stdcall *)(struct HWND__ *,unsigned int,unsigned int,long)''

if you look at this error you''ll notice that the WndProc of the class is a function call of type __thiscall, meaning that it passes the ''this'' parameter. and the (WNDPROC) definition is a __stdcall meaning that no extra info is passed to the call.

The only way I''ve found to be able to do something like what you want is to do the following...

after the call to CreateWindow add this...

SetWindowLong(hWnd,GWL_USERDATA,this);

this stoues a reference to the object in the windows own data space.
Then write a standalone WndProc function like this...

LRESULT CALLBACK WndProc(HWND hWnd,UINT uMsg,WPARAM wParam,LPARAM lParam){

MyWndClass *CurrentWnd = (MyWndClass *)GetWindowLong(hWnd,GWL_USERDATA);

if(!CurrentWnd)
return DefWindowProc(hWnd,uMsg,wParam,lParam);

return CurrentWnd->WndProc(hWnd,uMsg,wParam,lParam);
}

this retrieves the reference to the window class and calls it WndProc function.
And refer to this function in the WNDCLASS struct...

wc.lpfnWndProc = (WNDPROC)WndProc;

Thats what I do.

Share this post


Link to post
Share on other sites
Guest Anonymous Poster   
Guest Anonymous Poster
In the original post the poster says that the WindowProc() methods for the classes are static, as they have to be. This means they are not passed an invisible ''this'' pointer (since they can be used from outside a member of the class).

The real problem is the failure to give ''wServerWindow'' windows a handle to their parent when CreateWindow is called for them. The difference in behaviour described in the original post between creation from inside a ''wMainWindow'' and directly from the app is that when created directly, the window doesn''t need a parent as it is effectively the main window.

Share this post


Link to post
Share on other sites
mk83    122
Hi Guys,

I managed to get it working, I''m not really sure why or how, maybe someone will be able to explain, for some reason, ( I think it may have some thing to do with scope !)

wServerWindow TestWindow(Temp);

does not work, but this does :

wServerWindow* TestWindow;
TestWindow = new wServerWindow(Temp);

If anyone can explain why, I would appreciate it!

Thanks,
MK83

Share this post


Link to post
Share on other sites
Guest Anonymous Poster   
Guest Anonymous Poster
Doh! I can''t believe we missed this!

When you declare a variable inside a function (or any code block) it by default belongs to the ''automatic'' storage class. This means that memory is allocatated for it (from the stack) when that block is entered and then freed up when the block is exited.

What was happening originally, when you were using

wServerWindow TestWindow(Temp);

was that an instance of this class was constructed on entering the function and subsequently destroyed on exit. Also, the HWND you are storing in ''Temp'' will be undefined when the wServerWindow instance is created.

When you change the code to

wServerWindow* TestWindow = new wServerWindow(Temp);

the POINTER is automatically allocated (from the stack) on entering the function but the actual class instance is allocated dynamically (from the heap) when that line of code is reached. When the function exits, the POINTER is destroyed but the class instance lives on. Unfortunately you now have no pointer to that instance, causing a memory leak (as you can''t delete it).

What you need to do is keep a list of pointers to the child windows created by a parent - you can then delete the children when the parent is deleted.


Share this post


Link to post
Share on other sites
FallenGod    122
= Just Some Suggestions =

Unrelated to the issue you are having, but suggestions for your code...

You may want to wrap your RegisterClass(&wc); and population of the wc structure with a if statement to make sure you don''t reregister the class at runtime rather than at compile time and catch any errors RegisterClass would pass back. something like...

if (!GetClassInfo(hInstance, "Main Window Class", &wc))
{
WNDCLASS wc;
// ... stuff inbetween ...
if (!RegisterClass(&wc))
throw "Error registering window class";
}
Same goes for your second class. Rather then trying creation and then failing if you aren''t registered you can just check to see if you are registered. This way you can actually catch the real errors when they occur.


To simplify the use of your class from within your callback you may want to change the way you do things. Rather than having to get the stored this pointer and use ThisWindow->GetHandle() and such, you can create a simple wrapper function that will allow you to have your window procedure as a standard function rather than a static function.

LRESULT CALLBACK wMainWindow::WindowProc(HWND hwnd, // handle of window
UINT uMsg, // message identifier
WPARAM wParam, // first message parameter
LPARAM lParam // second message parameter
){
MainWindow* ThisWindow = (wMainWindow*) GetWindowLong (hwnd, GWL_USERDATA);
if (ThisWindow)
return (ThisWindow->ProcessMessage (hWnd, uMessage, wParam, lParam));
else // else call the default proc
return (DefWindowProc (hWnd, uMessage, wParam, lParam));
}

This replaces the windowProc you have already and is declared static in your class.
Now you just create a class called ProcessMessage that takes the same parameters as a normal window procedure, but make it a nonstatic part of your class. This greatly simplifies things since you don''t have to use ThisWindow-> anymore. This works well with message crackers too. If you have no idea what they are you may want to investigate them (http://www.acm.uiuc.edu/windevils/windevils97/workshop/98/6/).
The only problem I have had with this is that you either can''t process your WM_CREATE message (since it may be sent before the SetWindowLong is called) or you have to add special code for WM_CREATE. This isn''t a problem for me, I never use WM_CREATE, but you can work around this by processing the WM_CREATE in the WindowProc and passing the this pointer into your CreateWindow call so you end up being able to call the same ProcessMessage function.

Good Luck

Share this post


Link to post
Share on other sites
Andy Le Galle    122
Yep thats the one, that method is the best ive found of encapsulating a window in a class. If you try to keep EVERYTHING concerning a window in a class, then you have to make winproc static, which mean you cannot easily subclass or even do multiple instances of a window. I tried to get round this but it got very messed up, very quickly. Keeping a winproc function out of classes means that you have to have a little more code not in objects but the trade off is that the whole system becomes infinatly more useable and less error prone. If anyones interested ive included a semi-complete set of windows wrappers, which i wrote for the fun of it a while back, it is usable - just need error checking and someone to write the wrappers for some more controls.

http://hades.itl.net/~leg27/winwrap.zip

Andy

Share this post


Link to post
Share on other sites
Guest Anonymous Poster   
Guest Anonymous Poster
"I create a window in this class by handing it the handle of the first window I have created and using GetWindowLong(ParentWindow, GWL_HINSTANCE) to get the instance of the program."

You might also want to to look into GetModuleHandle(NULL) as it does the same thing ie HMODULE == HINSTANCE

Share this post


Link to post
Share on other sites