Author Topic: ProgEx38 -- Form2 - Window Procedure And Windows Messages  (Read 2079 times)

0 Members and 1 Guest are viewing this topic.

Offline Frederick J. Harris

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 742
  • Gender: Male
    • Frederick J. Harris
ProgEx38 -- Form2 - Window Procedure And Windows Messages
« on: December 03, 2009, 03:46:59 AM »
Code: [Select]
/*
  ProgEx38a -- Form2 - Window Procedure And Windows Messages.  C++ Win32 Sdk Api Program Shows
               Various Windows WM_XXXXXX Messages And Standard WndProc Construction.


  Form1 only processed WM_CREATE, WM_LBUTTONDOWN, WM_PAINT, and WM_DESTROY Windows messages in
  its Window Procedure.  This program - Form2 - will process all these messages more fully; but
  in addition, will process WM_SIZE, WM_CHAR, and WM_MOUSEMOVE.  Our goals here are rather lofty.
  What we want to do is create a window as we did in Form1, but when we move the mouse cursor
  over the window, we want displayed right on the Window's surface client area the x and y pixel
  location of the cursor.  The program must display a running tally of this.  Next, if we resize
  the Window by grabbing the borders with the mouse, we also want a continuous update of the
  width and height of the Window's client area.  Further, if we click anywhere in the client area
  of the window with the left mouse button, we want displayed near the point of the click the x and
  y pixel location of the click.  Next, we want to be able to type characters into the Window with
  the keyboard and see them as they are displayed kind of like a simple text editor.  Finally,
  we've tasked ourselves to do all this without one single global or static variable in the
  program.  Sounds like a lot, huh?

  Actually, I suppose it is, because there are a number of conceptual hurdles to overcome, but if
  you were able to follow what I did in ProgEx37 in the CBox3 and CBox4 examples in terms of the
  use of SetWindowLong() and GetWindowLong() to store 'instance data' in the form of pointers, you
  will be a long way to understanding what I am going to do here.  Let's briefly review that
  example, i.e., CBox3 of ProgEx37.  In that example we created a CBox object in the program's
  WM_CREATE message handler, and we output the specifications of the CBox object, i.e., its length,
  width, height, and volume in another message handling routine, that is, a WM_PAINT handler.  Now
  granted, the message handling code there was all within one procedure, that is, fnWndProc(), but
  its important to keep in mind that when Windows sent WM_PAINT or WM_CREATE messages to the
  Window Procedure, these were actually separate function calls, and any local automatic stack
  based variables within the Window Procedure came into and went out of scope in rapid succession
  and in no case preserved any data across function calls or invocations of the Window Procedure.
  The reason we were able to set values to the members of the CBox instance in WM_CREATE and
  retrieve those same values for display in WM_PAINT was that we allocated a CBox object on the
  heap dynamically and stored a pointer to that object in the window's instance data through the
  Window Extra Bytes of the Window structure.

  Now, I could have made things much easier on you by not doing this.  I could have declared my
  variables at global program scope.  Or, I could have specified the variables as statics in the
  Window Procedure.  Truth be told, this latter would be an acceptable alternative, because if
  a variable is declared as a static in a procedure at least it won't be accessible from every
  procedure in a program module, but only within those to which it is passed through function
  parameters.  But in this program example I'm not going to do that; we'll subject ourselves to
  the rigorous ascetic of doing things the hard, pure way.  We'll not allow ourselves the easy
  road of global variables.  Once you learn to do it this way you'll likely never turn back.  So
  take it like medicine and just grin and bear it is my advice.  Real Windows programmers don't
  need global variables!

  Another point I'd like to clear up in terms of my use of language before we proceed is my
  reference to 'message handlers'.  You've repeatedly seen me make reference to the term 'message
  handler' or 'message handling routine', for example, the 'WM_CREATE message handler'.  In CBox3
  and in our ProgEx37 template program we parsed the incoming Msg parameter of the Window
  Procedure with switch logic to route function execution to the correct case of the switch
  logic construct.  In major Windows programs involving tens of thousands of lines of code the
  Window Procedure can grow to gargantuan proportions.  From a code organization standpoint it
  can become difficult to maintain.  Many coders - me included, break the Window Procedure up
  into separate message handling functions.  Here is a simple example of what it could look like...

  long fnWndProc_OnCreate(HWND hWnd, WPARAM wParam, LPARAM lParam)
  {
   HINSTANCE hIns;
   hIns=((LPCREATESTRUCT)Wea->lParam)->hInstance;
   return 0;
  }


  long fnWndProc_OnClose(HWND hWnd, WPARAM wParam, LPARAM lParam)
  {
   if(MessageBox(hWnd,"Do You Wish To Exit This App?","Exit Check?",MB_YESNO)==IDYES)
   {
      DestroyWindow(hWnd);
      PostQuitMessage(WM_QUIT);
   }

   return 0;
  }


  LRESULT CALLBACK fnWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
  {
   switch(msg)
   {
    case WM_CREATE:
      return fnWndProc_OnCreate(hwnd, wParam, lParam);
    case WM_CLOSE:
      return fnWndProc_OnClose(hwnd, wParam, lParam);
   }

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

  In the above code the switch logic in fnWndProc() routes execution to fnWndProc_OnCreate() when
  a WM_CREATE message is received, and to fnWndProc_OnClose() when a WM_CLOSE message is received.
  All other messages are sent to DefWindowProc().  I'll not be doing that in this 1st instance of
  ProgEx38 because the code bulk is hardly unmanageable, but we'll take up this topic soon afterwards
  because I do think it's important to understand.

  OK, let's begin.  We'll start with the mouse move message.  First, I'd recommend you build
  yourself a project and compile and run this example to see what it does.  You should see a basic
  white window that displays mouse coordinates in the upper left hand corner of the window, as well
  as the width and height of the client area on the 2nd line.  On the 3rd line you should see whatever
  you type with your keyboard.  Also, if you left button click with your mouse anywhere within the
  client area of the window you should see displayed there the x/y coordinate pixel location of
  where you clicked.  The mouse move coordinates should be continuously updated as you move the mouse,
  as should the window width and height on the 2nd line as you resize the window.  Now we'll get to
  the 'magic' that makes this all work.

  The continuous display of the mouse cursor data is caused by the interplay of two specific messages,
  and they are WM_MOUSEMOVE and WM_PAINT.  This is what MSDN has to say about WM_MOUSEMOVE...

  WM_MOUSEMOVE

  The WM_MOUSEMOVE message is posted to a window when the cursor moves. If the mouse is not
  captured, the message is posted to the window that contains the cursor. Otherwise, the message
  is posted to the window that has captured the mouse.

  WM_MOUSEMOVE

  fwKeys = wParam;        // key flags
  xPos = LOWORD(lParam);  // horizontal position of cursor
  yPos = HIWORD(lParam);  // vertical position of cursor

  Parameters

  fwKeys    Value of wParam. Indicates whether various virtual keys are down. This parameter can be any
            combination of the following values: Value Description

            MK_CONTROL Set if the ctrl key is down.
            MK_LBUTTON Set if the left mouse button is down.
            MK_MBUTTON Set if the middle mouse button is down.
            MK_RBUTTON Set if the right mouse button is down.
            MK_SHIFT Set if the shift key is down.

  xPos      Value of the low-order word of lParam. Specifies the x-coordinate of the cursor. The coordinate
            is relative to the upper-left corner of the client area.

  yPos      Value of the high-order word of lParam. Specifies the y-coordinate of the cursor. The coordinate
            is relative to the upper-left corner of the client area.

                                         --End Microsoft--

  So what happens when you move the mouse over this program's window is that Windows posts a WM_MOUSEMOVE
  message to the program's message queue.  When this mouse move message arrives in the Window Procedure
  fnWndProc the msg parameter will equal WM_MOUSEMOVE (whatever that number is), the HWND parameter will
  contain the handle of the window to which the message applies, and the LPARAM parameter will have stuck
  in its low and high 16 bit words the xPos and yPos in pixels of the mouse.  These locations can be
  extracted with the LOWORD() and HIWORD() macros.

  What we're going to have to do, however, once we receive this data, is find some way to persist it across
  invocations of the Window Procedure so that the values can be output to the client window during another
  message - the WM_PAINT message.  Its in the nature of Windows Programming that all drawing to a window
  should be done during the WM_PAINT message - and that's a separate invocation of the Window Procedure.
  If we store the mouse coordinates in local variables during a WM_MOUSEMOVE message, and attempt to output
  these values during a WM_PAINT message - they'll be gone.  All you'll get on the screen are zeros or
  junk data.  It is possible to take the 'low road' and draw directly to the screen in a WM_MOUSEMOVE
  message by calling GetDC() to get a handle to a device context, but I'd strongly recommend against that.
  Its simply the improper way to do it - your results will be extremely poor. So what we are going to have
  to do is persist the data someway across invocations of the Window Procedure like we did in CBox3 and CBox4
  of ProgEx37.  In that example we created a CBox class dynamically and stored a pointer to it in the
  Window Class structure of the window.  We'll do the same here, although we don't even need anything as
  sophisticated as a C++ class to persist the data.  We'll just allocate a simple struct of type ProgramData
  as follows...

  struct ProgramData
  {
   short int xMouse;
   short int yMouse;
   short int xSize;
   short int ySize;
   short int xButton;
   short int yButton;
  };

  We'll allocate one of these in our WM_CREATE logic, and store a pointer to that at offset 4 in the
  Window Class struct.  At offset zero we'll store a pointer to a C++ Standard Library String object we'll
  also dynamically allocate in WM_CREATE.  Down in WinMain() note I set the .cbWndExtra bytes to 8 so
  as to store these two pointers, i.e., one to a string object to contain typed characters, and one to
  a ProgramData object to persist mouse coordinates, window size, and left button mouse clicks.  Here is
  what our WM_CREATE logic looks like...

  ...
  ...
  {
   ProgramData* pPD=NULL;

   case WM_CREATE:                         // This message is only received once at program startup.
   {                                       // Here we'll dynamically allocate memory on the heap to
        std::wstring* pStr;                // store inputs the program will receive in terms of key-
        pStr=new std::wstring;             // presses, mouse movements, button clicks, etc.  We'll
        SetWindowLong(hwnd,0,(long)pStr);  // store pointers to this memory in 8 bytes we've
        pPD=(ProgramData*)GlobalAlloc(GPTR, sizeof(ProgramData));
        SetWindowLong(hwnd,4,(long)pPD);   // allocated in theWNDCLASSEX struct down in WinMain()
        return 0;
   }
   ...
   ...
  }

  So what will need to happen when the Window Procedure receives a WM_MOUSEMOVE message - and it will
  receive hundreds of them per second as you move the mouse, is that the present coordinates of the
  mouse will need to be extracted from the lParam Window Procedure parameter and stored in our
  dynamically allocated ProgramData object.  Then an InvalidateRect() Api call needs to be made to
  force a WM_PAINT message from Windows, during which message the mouse coordinates will be extracted
  from the ProgramData object, and a TextOut() Api calls made to draw the mouse coordinates on the
  window.  Its a little wacky, I know.  But you'll get used to it and accept it after awhile.  Trust
  me.  So our WM_MOUSEMOVE handler will look like this....

  case WM_MOUSEMOVE:                     //Sent when user moves mouse and mouse cursor is over this
  {                                      //window.  1st retrieve ProgramData* from cbWndExtra bytes.
       pPD=(ProgramData*)GetWindowLong(hwnd,4);  //Then extract present mouse coord-
       pPD->xMouse=LOWORD(lParam);       //inates from lParam's Lo and Hi words.
       pPD->yMouse=HIWORD(lParam);       //Finally force repaint.
       InvalidateRect(hwnd,NULL,TRUE);
       return 0;
  }

  Note that InvalidateRect() call.  That is what forces the WM_PAINT that actually displays the
  rapidly changing mouse coordinate display you see in the window as you move the mouse.  Here
  is the pertinent WM_PAINT code that actually does the window painting.  I've removed everything
  except that code relating to the display of mouse movement coordinates...

  case WM_PAINT:         //The 1st thing to do in WM_PAINT is usually get your handle to a device
  {                      //context with BeginPaint() - which requires a PAINTSTRUCT variable.  Then
       PAINTSTRUCT ps;   //we'll extract our ProgramData* from .cbWndExtra bytes so we can get at
       std::wstring s1;  //the mouse coordinate data from the last WM_MOUSEMOVE message.  Next we'll
       std::wstring s2;  //build ourselves a C++ Standard String Class object (a local one) to
       TCHAR szBuffer[16];       //hold a nicely formatted string with our mouse coordinates.  We'll
       HDC hDC;                  //concatenate the thing together and need a character string buffer
       hDC=BeginPaint(hwnd,&ps); //to convert the numeric data to character format.  We'll use
       pPD=(ProgramData*)GetWindowLong(hwnd,4);      //sprintf - or its tchar counterpart _stprintf
       s1=_T("xMouse=");                             //to do the dirty work.  Then we'll finally
       _stprintf(szBuffer,_T("%d"),pPD->xMouse);     //string the whole mess together and output it
       s2=szBuffer;                                  //with TextOut() and release our devie context
       s1+=s2+_T("     yMouse=");                    //back to Windows.
       _stprintf(szBuffer,_T("%d"),pPD->yMouse);
       s2=szBuffer;
       s1+=s2;
       TextOut(hDC,0,0,s1.c_str(),s1.length());
       ...
       ...
       EndPaint(hwnd,&ps);
       return 0;
  }

  That's really pretty much it, and the same basic drill or logic covers all the other
  functionalities we wanted to have such as displaying typed characters, window size, and
  left button down clicks.  These others are just different messages such as WM_CHAR, WM_SIZE, and
  WM_LBUTTONDOWN.  You should look up these messages on MSDN to see how the data is transferred
  to the Window Procedure through the WPARAM and LPARAM WNDPROC parameters.  Right now it should
  be beginning to dawn on you the true elegance of the intellectual edifice that constitutes
  Windows - something which users of Windows as opposed to programmers cannot really conceive or
  appreciate.

  Finally, in our WM_DESTROY handler we call C++'s delete operator on our string object, call
  GlobalFree() on our ProgramData pointer (that's a Win Api memory de-allocation function I just
  threw in to show you there are various ways of acquiring/releasing memory), and PostQuiteMessage()
  to let us drop out of the message pump.

  If you don't have a couple years of fairly rigorous programming experience this program might be
  overwhelming.  It touches on several somewhat complex topics such as Windows Program structure and
  dynamic memory allocation.  If you can partly grasp it I'd recommend looking up and studying on the
  various Api functions used here such as CreateWindow(), RegisterClassEx(), SetWindowLong(),
  InvalidateRect(), BeginPaint(), and TextOut().  And if you are really into this sort of thing and
  have decided to make Win32 Sdk style programming your Windows Programming model of choice, you need
  to get yourself Charles Petzold's "Programming Windows" book.  His Windows 95 book would even be
  useful, and can be had for little more than the cost of postage.  That book also has a good
  section on Microsoft's OLE/COM (Object Linking and Embedding/Component Object Model) which his more
  often recommended fifth edition lacks.

  Using CodeBlocks and GNU MinGW I'm looking at about a 54 K executable with this program.  That seems
  a bit much to me.  The problem is the C++ Standard Library String Class.  In the next example let's
  try my String Class and see if we can't bring the size down some!
*/

//Main.cpp
#define  UNICODE                    //Will cause 'W' versions of Api functions to be used.
#define  _UNICODE                   //Will cause wide character versions of C i/o to be used.
#include <windows.h>                //Master Windows include file
#include <tchar.h>                  //Macros for ansi/wide char support
#include <string>                   //C++ Standard String Class Support


struct ProgramData                  //This object will be used to persist mouse coordinate,
{                                   //client window dimensions, and left mouse click coordinates
 short int xMouse;                  //across invocations of the Window Procedure so that the
 short int yMouse;                  //data can be displayed on the window during WM_PAINT
 short int xSize;                   //messages.  An instance of this object will be allocated
 short int ySize;                   //dynamically in WM_CREATE, and the pointer to it stored
 short int xButton;                 //as instance data in the window's WNDCLASSEX::cbWndExtra
 short int yButton;                 //bytes.
};


LRESULT CALLBACK fnWndProc(HWND hwnd, unsigned int msg, WPARAM wParam, LPARAM lParam)
{
 ProgramData* pPD=NULL;

 switch(msg)
 {
    case WM_CREATE:                 //This message is only received once at program startup.
    {                               //Here we'll dynamically allocate memory on the heap to
         std::wstring* pStr;        //store inputs the program will receive in terms of key-
         pStr=new std::wstring;     //presses, mouse movements, button clicks, etc.  We'll
         SetWindowLong(hwnd,0,(long)pStr);//store pointers to this memory in 8 bytes we've
         pPD=(ProgramData*)GlobalAlloc(GPTR,sizeof(ProgramData));
         if(pPD)                    //allocated in theWNDCLASSEX struct down in WinMain()
         {
            SetWindowLong(hwnd,4,(long)pPD);
            return 0;
         }
         else
         {
            return -1;
         }
    }
    case WM_SIZE:          //Sent when the Window is first shown or resized.  We need
    {                      //to first retrieve the ProgramData* from the cbWndExtra
         pPD=(ProgramData*)GetWindowLong(hwnd,4); //bytes.  Then we can retrieve from
         pPD->xSize=LOWORD(lParam);               //lParam's lo and hi words the width
         pPD->ySize=HIWORD(lParam);               //and height of the window.  Then
         InvalidateRect(hwnd,NULL,TRUE);          //force a WM_PAINT to redraw the
         return 0;                                //newly acquired info.
    }
    case WM_CHAR:             //Sent when this program has the focus and the user hits
    {                         //a char on the keypad.  Same drill as above; retrieve
         std::wstring* pStr;  //String pointer though;  Then just add the wParam (which
         pStr=(std::wstring*)GetWindowLong(hwnd,0); //holds the key code pressed) to the
         *pStr+=+wParam;                            //String and force the repaint as
         InvalidateRect(hwnd,NULL,FALSE);           //above.
         return 0;
    }
    case WM_MOUSEMOVE:     //Sent when user moves mouse and mouse cursor is over this
    {                      //window.  1st retrieve ProgramData* from cbWndExtra bytes.
         pPD=(ProgramData*)GetWindowLong(hwnd,4);  //Then extract present mouse coord-
         pPD->xMouse=LOWORD(lParam);     //inates from lParam's Lo and Hi words.
         pPD->yMouse=HIWORD(lParam);     //Finally force repaint.
         InvalidateRect(hwnd,NULL,TRUE);
         return 0;
    }
    case WM_LBUTTONDOWN:   //Sent when user left button mouse clicks over window.  In
    {                      //terms of the rest, you should be getting the idea by now!
         pPD=(ProgramData*)GetWindowLong(hwnd,4);
         pPD->xButton=LOWORD(lParam);
         pPD->yButton=HIWORD(lParam);
         InvalidateRect(hwnd,NULL,FALSE);
         return 0;
    }
    case WM_PAINT:         //All drawing to a window should be done during a WM_PAINT message.
    {                      //That is why in all of the above message handling code except that
         PAINTSTRUCT ps;   //for WM_CREATE there is an InvalidateRect() Api call.  That call
         std::wstring s1;  //will cause Windows to invalidate the client area of the window,
         std::wstring s2;  //and when this occurs windows will post to the window's message
         std::wstring* pStr;       //queue a WM_PAINT message.  In this code just left we are
         TCHAR szBuffer[16];       //allocating a few standard string objects, and a TCHAR
         HDC hDC;                  //buffer.  These will support the text we wish to display
         hDC=BeginPaint(hwnd,&ps); //using TextOut().  What we first do though is call
         pPD=(ProgramData*)GetWindowLong(hwnd,4);      //BeginPaint() to get a handle to a
         s1=_T("xMouse=");                             //device context (which is necessary
         _stprintf(szBuffer,_T("%d"),pPD->xMouse);     //for GDI (Graphics Device Interface)
         s2=szBuffer;                                  //function calls, and then call
         s1+=s2+_T("     yMouse=");                    //GetWindowLong() to retrieve our
         _stprintf(szBuffer,_T("%d"),pPD->yMouse);     //ProgramData pointer from the Window
         s2=szBuffer;                                  //Class struct.  Having done that we can
         s1+=s2;                                       //chug through some string minipulation
         TextOut(hDC,0,0,s1.c_str(),s1.length());      //code to compose the messages we want
         if(pPD->xButton||pPD->yButton)                //to display in our window, which
         {                                             //messages contain the various numeric
            s1=_T("xButton=");                         //information we want to convey.  Note
            _stprintf(szBuffer,_T("%d"),pPD->xButton); //that _stprintf() is used to convert
            s2=szBuffer;                               //the integral data to a string format
            s1+=s2+_T("    yButton=");                 //so it can be output in a std::string.
            _stprintf(szBuffer,_T("%d"),pPD->yButton); //If you know a better way to do it go
            s2=szBuffer;                               //for it.  I'm not really expert on the
            s1+=s2;                                    //std::string class, as I mostly use my
            TextOut                                    //own.  Finally, after outputting all
            (                                          //the data we want we need to call
             hDC,                                      //EndPaint() to release the device
             pPD->xButton+12,                          //context.  This is important because
             pPD->yButton,                             //if you don't release memory objects
             s1.c_str(),                               //and resources back to the operating
             s1.length()                               //system you'll cause a memory leak, and
            );                                         //your program will start to behave
            pPD->xButton=0, pPD->yButton=0;            //rather badly.  Essentially, .NET
         }                                             //is Microsoft's answer to the problem
         s1=_T("Width=");                              //that the average coder can't be trusted
         _stprintf(szBuffer,_T("%d"),pPD->xSize);      //to do things like this correctly, so
         s2=szBuffer;                                  //the framework takes care of it for
         s1+=s2+_T("    Height=");                     //him/her.  So you've got to ask yourself,
         _stprintf(szBuffer,_T("%d"),pPD->ySize);      //"Are you better than the average coder?"
         s2=szBuffer;                                  //
         s1+=s2;                                       //In WM_DESTROY below we'll release the
         TextOut(hDC,0,20,s1.c_str(),s1.length());     //std::string pointer we acquired with
         pStr=(std::wstring*)GetWindowLong(hwnd,0);    //new by calling delete on it, and we'll
         TextOut(hDC,0,40,pStr->c_str(),pStr->length());  //use GlobalFree() on the ProgramData
         EndPaint(hwnd,&ps);                              //pointer we acquired with GlobalAlloc().
         return 0;
    }
    case WM_DESTROY:
    {
         std::wstring* pStr;
         pStr=(std::wstring*)GetWindowLong(hwnd,0);
         delete pStr;
         pPD=(ProgramData*)GetWindowLong(hwnd,4);
         if(pPD)
            GlobalFree(pPD);   //send the WM_DESTROY.  Anyway, what we need to do here is
         PostQuitMessage(0);//release our heap allocations and post a Quit message.
         return 0;
    }
 }

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


int WINAPI WinMain(HINSTANCE hIns, HINSTANCE hPrevIns, LPSTR lpszArgument, int iShow)
{
 TCHAR szClassName[]=_T("Form2");
 TCHAR szCaption[80];
 WNDCLASSEX wc;
 MSG messages;
 HWND hWnd;

 wc.lpszClassName=szClassName,                          wc.lpfnWndProc=fnWndProc;
 wc.cbSize=sizeof (WNDCLASSEX),                         wc.style=CS_DBLCLKS;
 wc.hIcon=LoadIcon(NULL,IDI_APPLICATION),               wc.hInstance=hIns;
 wc.hIconSm=LoadIcon(NULL, IDI_APPLICATION),            wc.hCursor=LoadCursor(NULL,IDC_ARROW);
 wc.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH),  wc.cbWndExtra=8;
 wc.lpszMenuName=NULL,                                  wc.cbClsExtra=0;
 RegisterClassEx(&wc);
 _tcscpy(szCaption,_T("Try Typing Text, Moving Mouse, Clicking Left Mouse Button, Or Sizing Window!"));
 hWnd=CreateWindowEx(0,szClassName,szCaption,WS_OVERLAPPEDWINDOW,100,100,600,400,HWND_DESKTOP,0,hIns,0);
 ShowWindow(hWnd,iShow);
 while(GetMessage(&messages,NULL,0,0))
 {
    TranslateMessage(&messages);
    DispatchMessage(&messages);
 }

 return messages.wParam;
}

« Last Edit: September 13, 2011, 03:43:18 AM by Frederick J. Harris »

Offline Frederick J. Harris

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 742
  • Gender: Male
    • Frederick J. Harris
Re: ProgEx38 -- Form2 - Window Procedure And Windows Messages
« Reply #1 on: September 13, 2011, 03:52:26 AM »
ProgEx38b -- Form2 - Window Procedure And Windows Messages Using My String Class Instead of C++ Standard Library String Class.  Saves 25 K from C++  Standard Library String Class.

To try my string class you'll need to include Strings.cpp and Strings.h as shown in the list of includes below.  They are located on this site on my COM Programming board under the topic...

Grid Custom Control Project - Converting It To COM

located here...

http://www.jose.it-berater.org/smfforum/index.php?topic=4176.0

The Strings.cpp file and the Strings.h file are listed in Post #42...

http://www.jose.it-berater.org/smfforum/index.php?topic=4176.30

You can just copy the text into your code editor or whatever.  One difference you'll see with my String Class is that I have an overloaded operator= for
converting a numeric value directly to a string, so all one needs to do is directly assign it, i.e.,

  String s1;
  int iNumber=12345;

  s1=iNumber;
  s1.Print(true);

  Output:
  ===========
  12345

So, instead of having to use _stprintf() to write a mouse coordinate, for example, into a string buffer such as szBuffer[16] as in the last example, I just do this...

  s2=pPD->xMouse;

Otherwise, everything is about the same (at least in terms of this program).  Its coming in for me at 29 K which is quite a bit smaller than the 62 K with the Standard C++ Library String Class.  Mine isn't based on the basic_string class and associated STL templates.  You can control whether it uses ansi or wide character strings with the UNICODE/_UNICODE defines.

Code: [Select]
//Main.cpp
#define UNICODE
#define _UNICODE
#include <windows.h>
#include <tchar.h>
#include "Strings.h"

struct ProgramData
{
 short int xMouse;
 short int yMouse;
 short int xSize;
 short int ySize;
 short int xButton;
 short int yButton;
};


LRESULT CALLBACK fnWndProc(HWND hwnd, unsigned int msg, WPARAM wParam, LPARAM lParam)
{
 ProgramData* pPD=NULL;

 switch(msg)
 {
    case WM_CREATE:
    {
         String* pStr=new String;
         SetWindowLong(hwnd,0,(long)pStr);
         pPD=(ProgramData*)GlobalAlloc(GPTR,sizeof(ProgramData));
         if(pPD)
         {
            SetWindowLong(hwnd,4,(long)pPD);
            return 0;
         }
         else
         {
            return 0;
         }
    }
    case WM_SIZE:
    {
         pPD=(ProgramData*)GetWindowLong(hwnd,4);
         pPD->xSize=LOWORD(lParam), pPD->ySize=HIWORD(lParam);
         InvalidateRect(hwnd,NULL,TRUE);
         return 0;
    }
    case WM_CHAR:
    {
         String* pStr=(String*)GetWindowLong(hwnd,0);
         *pStr=*pStr+wParam;
         InvalidateRect(hwnd,NULL,FALSE);
         return 0;
    }
    case WM_MOUSEMOVE:
    {
         pPD=(ProgramData*)GetWindowLong(hwnd,4);
         pPD->xMouse=LOWORD(lParam), pPD->yMouse=HIWORD(lParam);
         InvalidateRect(hwnd,NULL,TRUE);
         return 0;
    }
    case WM_LBUTTONDOWN:
    {
         pPD=(ProgramData*)GetWindowLong(hwnd,4);
         pPD->xButton=LOWORD(lParam), pPD->yButton=HIWORD(lParam);
         InvalidateRect(hwnd,NULL,FALSE);
         return 0;
    }
    case WM_PAINT:
    {
         PAINTSTRUCT ps;
         String s1,s2;
         String* pStr;
         HDC hDC;
         hDC=BeginPaint(hwnd,&ps);
         pPD=(ProgramData*)GetWindowLong(hwnd,4);
         s1=_T("xMouse="),          s2=pPD->xMouse;
         s1+=s2+_T("     yMouse="), s2=pPD->yMouse;
         s1+=s2;
         TextOut(hDC,0,0,s1.lpStr(),s1.Len());
         if(pPD->xButton||pPD->yButton)
         {
            s1=_T("xButton="),      s2=pPD->xButton, s1+=s2;
            s1+=_T("    yButton="), s2=pPD->yButton, s1+=s2;
            TextOut(hDC,pPD->xButton+12,pPD->yButton,s1.lpStr(),s1.Len());
            pPD->xButton=0, pPD->yButton=0;
         }
         s1=_T("Width="),        s2=pPD->xSize,  s1+=s2;
         s1+=_T("    Height="),  s2=pPD->ySize,  s1+=s2;
         TextOut(hDC,0,20,s1.lpStr(),s1.Len());
         pStr=(String*)GetWindowLong(hwnd,0);
         TextOut(hDC,0,40,pStr->lpStr(),pStr->Len());
         EndPaint(hwnd,&ps);
         return 0;
    }
    case WM_DESTROY:
    {
         String* pStr;
         pStr=(String*)GetWindowLong(hwnd,0);
         delete pStr;
         pPD=(ProgramData*)GetWindowLong(hwnd,4);
         GlobalFree(pPD);
         PostQuitMessage(0);
         return 0;
    }
 }

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


int WINAPI WinMain(HINSTANCE hIns, HINSTANCE hPrevIns, LPSTR lpszArgument, int iShow)
{
 TCHAR szClassName[]=_T("Form2");
 TCHAR szCaption[80];
 WNDCLASSEX wc;
 MSG messages;
 HWND hWnd;

 wc.lpszClassName=szClassName,                          wc.lpfnWndProc=fnWndProc;
 wc.cbSize=sizeof (WNDCLASSEX),                         wc.style=CS_DBLCLKS;
 wc.hIcon=LoadIcon(NULL,IDI_APPLICATION),               wc.hInstance=hIns;
 wc.hIconSm=LoadIcon(NULL, IDI_APPLICATION),            wc.hCursor=LoadCursor(NULL,IDC_ARROW);
 wc.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH),  wc.cbWndExtra=8;
 wc.lpszMenuName=NULL,                                  wc.cbClsExtra=0;
 RegisterClassEx(&wc);
 _tcscpy(szCaption,_T("Try Typing Text, Moving Mouse, Clicking Left Mouse Button, Or Sizing Window!"));
 hWnd=CreateWindowEx(0,szClassName,szCaption,WS_OVERLAPPEDWINDOW,100,100,600,400,HWND_DESKTOP,0,hIns,0);
 ShowWindow(hWnd,iShow);
 while(GetMessage(&messages,NULL,0,0))
 {
    TranslateMessage(&messages);
    DispatchMessage(&messages);
 }

 return messages.wParam;
}
« Last Edit: September 14, 2011, 09:21:38 PM by Frederick J. Harris »

Offline Frederick J. Harris

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 742
  • Gender: Male
    • Frederick J. Harris
Re: ProgEx38 -- Form2 - Window Procedure And Windows Messages
« Reply #2 on: September 13, 2011, 04:07:07 AM »
We had 54 K for this program using the C++ Standard Library String Class, and we were able to bring that down to 29 K using my String Class.  I thought it would be interesting to see the size of the exact same program in PowerBASIC, so here is that one posted below.   I'm seeing 12 K on this, which is interesting.  The advantage PowerBASIC has here is that it has a 'built in' String data type, whereas with C++ String Class code has to be compiled in to achieve decent string handling.  If I removed the string class from these C++ programs and just used the string primitives in string.h C style I might be able to get it down to PowerBASIC size, or even a K or two smaller, but I'll leave that as an exercise for the reader/learner.  I've done enough of that to last me a couple lifetimes.

Code: [Select]
'PowerBASIC Version 10.02; PowerBASIC Includes; Compiles to 12 K.
#Compile                  Exe
#Dim                      All
%UNICODE                  = 1
#If %Def(%UNICODE)
    Macro ZStr            = WStringz
    Macro BStr            = WString
    %SIZEOF_CHAR          = 2
#Else
    Macro ZStr            = Asciiz
    Macro BStr            = String
    %SIZEOF_CHAR          = 1
#EndIf
#Include "Win32api.inc"

Type ProgramData
 xMouse                   As Integer
 yMouse                   As Integer
 xSize                    As Integer
 ySize                    As Integer
 xButton                  As Integer
 yButton                  As Integer
End Type

Function fnWndProc(ByVal hWnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
  Local pPD As ProgramData Ptr
  Local pStr As ZStr Ptr

  Select Case As Long wMsg
    Case %WM_CREATE
      pStr=GlobalAlloc(%GPTR,256)
      If pStr Then
         Call SetWindowLong(hWnd,0,pStr)
      Else
         Function=-1 : Exit Function
      End If
      pPD=GlobalAlloc(%GPTR,SizeOf(ProgramData))
      If pPD Then
         Call SetWindowLong(hWnd,4,pPD)
      Else
         Function=-1 : Exit Function
      End If
      Function=0 : Exit Function
    Case %WM_MOUSEMOVE
      pPD=GetWindowLong(hWnd,4)
      @pPD.xMouse=LOWRD(lParam) : @pPD.yMouse=HIWRD(lParam)
      InvalidateRect(hWnd,Byval %NULL, %TRUE)
      Function = 0 : Exit Function
    Case %WM_SIZE
      pPD=GetWindowLong(hWnd,4)
      @pPD.xSize=LOWRD(lParam)  : @pPD.ySize=HIWRD(lParam)
      InvalidateRect(hWnd,Byval 0,%True)
      Function=0 : Exit Function
    Case %WM_CHAR
      pStr=GetWindowLong(hWnd,0)
      @pStr=@pStr+Chr$(wParam)
      InvalidateRect(hWnd,Byval 0,%True)
      Function=0 : Exit Function
    Case %WM_LBUTTONDOWN
      pPD=GetWindowLong(hWnd,4)
      @pPD.xButton=LOWRD(lParam) : @pPD.yButton=HIWRD(lParam)
      InvalidateRect(hWnd,Byval %NULL, %TRUE)
      Function=0 : Exit Function
    Case %WM_PAINT
      Local ps As PAINTSTRUCT
      Local strLine As BStr
      Local hDC As Dword
      hDC=BeginPaint(hWnd,ps)
      pStr=GetWindowLong(hWnd,0) : pPD=GetWindowLong(hWnd,4)
      strLine="@pPD.xMouse="+Trim$(Str$(@pPD.xMouse)) & "    @pPD.yMouse="+Trim$(Str$(@pPD.yMouse))
      TextOut(hDC,0,0,Byval Strptr(strLine),Len(strLine))
      strLine="@pPD.xSize="+Trim$(Str$(@pPD.xSize)) & "    @pPD.ySize="+Trim$(Str$(@pPD.ySize))
      TextOut(hDC,0,20,Byval Strptr(strLine),Len(strLine))
      TextOut(hDC,0,40,@pStr,Len(@pStr))
      If @pPD.xButton Or @pPD.yButton Then
         strLine="xButton=" & Trim$(Str$(@pPD.xButton)) & "    yButton=" & Trim$(Str$(@pPD.yButton))
         TextOut(hDC,@pPD.xButton+12,@pPD.yButton,Byval Strptr(strLine),Len(strLine))
         @pPD.xButton=0 : @pPD.yButton=0
      End If
      Call EndPaint(hWnd,ps)
      Function=0 : Exit Function
    Case %WM_DESTROY
      pStr=GetWindowLong(hWnd,0)
      If pStr Then
         GlobalFree(pStr)
      End If
      pPD=GetWindowLong(hWnd,4)
      If pPD Then
         GlobalFree(pPD)
      End If
      Call PostQuitMessage(0)
      Function=0 : Exit Function
  End Select

  fnWndProc=DefWindowProc(hWnd, wMsg, wParam, lParam)
End Function

Function WinMain(ByVal hIns As Long,ByVal hPrevIns As Long,ByVal lpCmdLine As Asciiz Ptr,ByVal iShow As Long) As Long
  Local szClassName As ZStr*16
  Local wc As WndClassEx
  Local hWnd As Dword
  Local Msg As tagMsg

  szClassName="Form2a"
  wc.cbSize=SizeOf(wc)                               : wc.style=0
  wc.lpfnWndProc=CodePtr(fnWndProc)                  : wc.cbClsExtra=0
  wc.cbWndExtra=8                                    : wc.hInstance=hIns
  wc.hIcon=LoadIcon(%NULL,ByVal %IDI_APPLICATION)    : wc.hCursor=LoadCursor(%NULL,ByVal %IDC_ARROW)
  wc.hbrBackground=GetStockObject(%WHITE_BRUSH)      : wc.lpszMenuName=%NULL
  wc.lpszClassName=VarPtr(szClassName)               : wc.hIconSm=LoadIcon(%NULL,ByVal %IDI_APPLICATION)
  Call RegisterClassEx(wc)
  hWnd=CreateWindowEx(0,szClassName,"Form2a",%WS_OVERLAPPEDWINDOW,200,100,425,360,%HWND_DESKTOP,0,hIns,ByVal 0)
  Call ShowWindow(hWnd,iShow)
  While GetMessage(Msg,%NULL,0,0)
    Call TranslateMessage(Msg)
    Call DispatchMessage(Msg)
  Wend

  WinMain=msg.wParam
End Function

Offline Frederick J. Harris

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 742
  • Gender: Male
    • Frederick J. Harris
Re: ProgEx38 -- Form2 - Window Procedure And Windows Messages
« Reply #3 on: September 14, 2011, 09:23:18 PM »
ProgEx38c  Window Procedure Continued….

     We’re not done with Program Example 38.  There’s still a lot more that can be gleaned from this example!  What I’m hoping you’ve gleaned so far is that the Window Procedure concept and how it operates is rather central to Windows graphical user interface programming.  What we’ve seen so far in Program Examples 37 and 38 is that there are four parameters to the Window Procedure…

Code: [Select]
LRESULT CALLBACK WindowProc
(
  HWND hwnd,      // handle to window
  UINT uMsg,      // message identifier
  WPARAM wParam,  // first message parameter
  LPARAM lParam   // second message parameter
);

…and we’ve covered the last three in some detail showing quite a few different messages and the associated data Windows loads into the WPARAM and LPARAM variables with each message.  What hasn’t changed very much in the example programs we’ve looked at so far is the first parameter, that is, the HWND parameter.  In all the programs we’ve looked at a main program window was created in our WinMain() function with a CreateWindowEx() Api call, and the return value from that function would have been the only HWND parameter that would have come through to the Window Procedures we’ve seen that were just servicing that one main window. 

     The fact is though that the CreateWindowEx() call can be thought of as a C based object creation call, and as many objects of a given class as specified by the 2nd lpClassName parameter can be created as you wish.

Code: [Select]
HWND CreateWindowEx
(
  DWORD dwExStyle,      // extended window style
  LPCTSTR lpClassName,  // pointer to registered class name
  LPCTSTR lpWindowName, // pointer to window name
  DWORD dwStyle,        // window style
  int x,                // horizontal position of window
  int y,                // vertical position of window
  int nWidth,           // window width
  int nHeight,          // window height
  HWND hWndParent,      // handle to parent or owner window
  HMENU hMenu,          // handle to menu, or child-window identifier
  HINSTANCE hInstance,  // handle to application instance
  LPVOID lpParam        // pointer to window-creation data
);

However, all the instantiated objects of the class will use the Window Procedure specified for the class in the WNDCLASSEX::lpfnWndProc member, and the first parameter of the Window Procedure, i.e., the HWND parameter, will reflect the actual object to which the uMsg, wParam, and lParam parameters apply.  In other words, there can be a one to many relationship between a Window Procedure and the windows to which the various messages and associated data applies.  We have not seen that so far but let’s look at an example of that now, and let’s use our ProgEx38a as a starting point.  What we’ll do is go down to WinMain() and instead of our single CreateWindowEx() call which creates our main program window which registers mouse movements, characters typed, etc., we’ll run that CreateWindowEx() call through a for loop and create four program windows!   Here is the code for that…

Code: [Select]
//Main.cpp - ProgEx38c
#define  UNICODE
#define  _UNICODE
#include <windows.h>
#include <tchar.h>
#include <string>


struct ProgramData
{
 short int xMouse;
 short int yMouse;
 short int xSize;
 short int ySize;
 short int xButton;
 short int yButton;
};


LRESULT CALLBACK fnWndProc(HWND hwnd, unsigned int msg, WPARAM wParam, LPARAM lParam)
{
 ProgramData* pPD=NULL;

 switch(msg)
 {
    case WM_CREATE:
    {
         std::wstring* pStr;
         pStr=new std::wstring;
         SetWindowLong(hwnd,0,(long)pStr);
         pPD=(ProgramData*)GlobalAlloc(GPTR,sizeof(ProgramData));
         if(pPD)
            SetWindowLong(hwnd,4,(long)pPD);
         else
            return -1;
         long iCntr=GetClassLong(hwnd,0);
         iCntr++;
         SetClassLong(hwnd,0,iCntr);
         return 0;
    }
    case WM_SIZE:
    {
         pPD=(ProgramData*)GetWindowLong(hwnd,4);
         pPD->xSize=LOWORD(lParam);
         pPD->ySize=HIWORD(lParam);
         InvalidateRect(hwnd,NULL,TRUE);
         return 0;
    }
    case WM_CHAR:
    {
         std::wstring* pStr;
         pStr=(std::wstring*)GetWindowLong(hwnd,0);
         *pStr+=+wParam;
         InvalidateRect(hwnd,NULL,FALSE);
         return 0;
    }
    case WM_MOUSEMOVE:
    {
         pPD=(ProgramData*)GetWindowLong(hwnd,4);
         pPD->xMouse=LOWORD(lParam);
         pPD->yMouse=HIWORD(lParam);
         InvalidateRect(hwnd,NULL,TRUE);
         return 0;
    }
    case WM_LBUTTONDOWN:
    {
         pPD=(ProgramData*)GetWindowLong(hwnd,4);
         pPD->xButton=LOWORD(lParam);
         pPD->yButton=HIWORD(lParam);
         InvalidateRect(hwnd,NULL,FALSE);
         return 0;
    }
    case WM_PAINT:
    {
         PAINTSTRUCT ps;
         std::wstring s1;
         std::wstring s2;
         std::wstring* pStr;
         TCHAR szBuffer[16];
         HDC hDC;
         hDC=BeginPaint(hwnd,&ps);
         pPD=(ProgramData*)GetWindowLong(hwnd,4);
         s1=_T("xMouse=");
         _stprintf(szBuffer,_T("%d"),pPD->xMouse);
         s2=szBuffer;
         s1+=s2+_T("     yMouse=");
         _stprintf(szBuffer,_T("%d"),pPD->yMouse);
         s2=szBuffer;
         s1+=s2;
         TextOut(hDC,0,0,s1.c_str(),s1.length());
         if(pPD->xButton||pPD->yButton)
         {
            s1=_T("xButton=");
            _stprintf(szBuffer,_T("%d"),pPD->xButton);
            s2=szBuffer;
            s1+=s2+_T("    yButton=");
            _stprintf(szBuffer,_T("%d"),pPD->yButton);
            s2=szBuffer;
            s1+=s2;
            TextOut
            (
             hDC,
             pPD->xButton+12,
             pPD->yButton,
             s1.c_str(),
             s1.length()
            );
            pPD->xButton=0, pPD->yButton=0;
         }
         s1=_T("Width=");
         _stprintf(szBuffer,_T("%d"),pPD->xSize);
         s2=szBuffer;
         s1+=s2+_T("    Height=");
         _stprintf(szBuffer,_T("%d"),pPD->ySize);
         s2=szBuffer;
         s1+=s2;
         TextOut(hDC,0,20,s1.c_str(),s1.length());
         pStr=(std::wstring*)GetWindowLong(hwnd,0);
         TextOut(hDC,0,40,pStr->c_str(),pStr->length());
         EndPaint(hwnd,&ps);
         return 0;
    }
    case WM_DESTROY:
    {
         std::wstring* pStr;
         pStr=(std::wstring*)GetWindowLong(hwnd,0);
         delete pStr;
         pPD=(ProgramData*)GetWindowLong(hwnd,4);
         if(pPD)
            GlobalFree(pPD);
         long iCntr=GetClassLong(hwnd,0);
         iCntr--;
         if(iCntr==0)
         {
            MessageBox(hwnd,_T("No More Windows Left!"),_T("Will Now Close..."),MB_OK);
            PostQuitMessage(0);
         }
         else
            SetClassLong(hwnd,0,iCntr);
         return 0;
    }
 }

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


int WINAPI WinMain(HINSTANCE hIns, HINSTANCE hPrevIns, LPSTR lpszArgument, int iShow)
{
 TCHAR szClassName[]=_T("Form2");
 TCHAR szCaption[80];
 WNDCLASSEX wc;
 MSG messages;
 HWND hWnd;

 wc.lpszClassName=szClassName,                          wc.lpfnWndProc=fnWndProc;
 wc.cbSize=sizeof (WNDCLASSEX),                         wc.style=CS_DBLCLKS;
 wc.hIcon=LoadIcon(NULL,IDI_APPLICATION),               wc.hInstance=hIns;
 wc.hIconSm=LoadIcon(NULL, IDI_APPLICATION),            wc.hCursor=LoadCursor(NULL,IDC_ARROW);
 wc.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH),  wc.cbWndExtra=8;
 wc.lpszMenuName=NULL,                                  wc.cbClsExtra=4;
 RegisterClassEx(&wc);
 _tcscpy(szCaption,_T("Try Typing Text, Moving Mouse, Clicking Left Mouse Button, Or Sizing Window!"));
 for(unsigned i=0; i<4; i++)
 {
     hWnd=CreateWindowEx(0,szClassName,szCaption,WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,CW_USEDEFAULT,325,300,0,0,hIns,0);
     ShowWindow(hWnd,iShow);
 }
 while(GetMessage(&messages,NULL,0,0))
 {
    TranslateMessage(&messages);
    DispatchMessage(&messages);
 }

 return messages.wParam;
}

     I’d recommend you compile and run this program to get the feel of it.  What you should see are four program windows that are completely and totally independent of one another.  Also, if you compare this code to either of the previous ProgEx38 examples you’ll see very little has been changed to support this interesting new functionality.  Naturally, there have been some changes, and I’d like to now cover them.  However, first I’d like to make a few comments. 

     Have you totally convinced yourself that these four instances of our “Form2” class are completely independent of each other?  For example, start the program and move the four windows apart to give each its own room on your desktop.  Try activating each window by clicking it with your mouse and typing some text into it.  Try resizing each one a bit differently.  Do the window outputs our WM_PAINT handlers are drawing reflect the correct data for each window?  If so, do you think this would still be the case if instead of allocating our std::wstring and our ProgramData objects dynamically we would have allocated them as statics or globals?  Ah Ha!  Now that’s something to think about, isn’t it?

     In this new light take another look at the WM_CREATE handler and think about what will happen as the for loop down in WinMain() successively makes each CreateWindowEx() call.  There will be a separate WM_CREATE message sent for each CreateWindowEx() call, and each such call will cause memory to be allocated and associated strictly with that window and its HWND in the window’s instance structure.  And when you do anything at all to any of the windows the results will be reflected in changes to that window’s data only, and each display will reflect the correct data for only that window.  Think for a moment of how you might attempt to accomplish this feat using globals, statics and arrays, and I guarantee you that what you’ll be envisioning will turn out to be a mess of a hack.  Are you beginning to sense the elegance of the design of the Windows Api that it allows something as complex as this to be done so simply?

     There are some issues though, so let’s dive into them.  We’ve just seen how each window’s creation will result in a WM_CREATE call to the Window Procedure for that HWND.  Likewise, each click of the x close out button in the title bar will result in a WM_DESTROY message for that respective window.  But do you recall also in all our previous programs we’ve had to locate a PostQuitMessage() call in our WM_DESTROY handlers to end the message loop running down in WinMain() so that the program could terminate?  In our case here if we did that on any of the four windows not only that window but all four windows would be destroyed.  Its not too hard to imagine a scenario where this would be undesirable behavior for a program.  If you have a word processor opened and its an MDI app (multiple document interface) that can manage several documents, if you close out any one document would you want the whole app to close along with all the other documents, perhaps losing data in the process?  I think not.  So the behavior we would prefer to see is that closing out any window won’t affect the other windows.  When the last window is closed – in our case here the 4th window, then the PostQuitMessage() function can be called and the app terminated.

     But to do that we are going to have to count object instances, aren’t we?  Does ‘count object instances’ ring any bells for you?  If you’ve diligently studied your C++ it should.  Think static data members of a class.  If you are a bit hazy on the topic let's take a moment with our old CBox class and modify it some so we can count object instances.  Here is CBox5...

Code: [Select]
//CBox.h
#ifndef CBox_h
#define CBox_h

class CBox
{
 public:
 static int iObjectCount;       // Static Class Data Member
 CBox();                        // Uninitialized Constructor
 ~CBox();                       // Destructor
 double     Volume()  const;    // {return m_Length*m_Width*m_Height;}
 double&    length();           // {return m_Length;}
 double&    width();            // {return m_Width;}
 double&    height();           // {return m_Height;}

 private:
 double     m_Length;
 double     m_Width;
 double     m_Height;
};
#endif

Code: [Select]
//CBox.cpp
#include "CBox.h"
int CBox::iObjectCount=0;

CBox::CBox()
{
 this->m_Length = 0.0;
 this->m_Width  = 0.0;
 this->m_Height = 0.0;
 iObjectCount++;
}

CBox::~CBox()
{
 //destructor
}

double& CBox::length()
{
 return m_Length;
}

double& CBox::width()
{
 return m_Width;
}

double& CBox::height()
{
 return m_Height;
}

double CBox::Volume() const
{
 return m_Length*m_Width*m_Height;
}

Code: [Select]
//Main.cpp
#include <cstdio>
#include "CBox.h"

int main(void)
{
 CBox bx[3];  //Let's make three CBoxes

 bx[0].length()=1.0, bx[0].width()=2.0, bx[0].height()=3.0; //give them some
 bx[1].length()=2.0, bx[1].width()=3.0, bx[1].height()=4.0; //dimensions, then
 bx[2].length()=3.0, bx[2].width()=4.0, bx[2].height()=5.0; //output the static
 printf("bx[0].Volume()     = %2.0f\n",bx[0].Volume());     //member variable
 printf("bx[1].Volume()     = %2.0f\n",bx[1].Volume());     //iObjectCount to
 printf("bx[2].Volume()     = %2.0f\n",bx[2].Volume());     //see how many
 printf("\nCBox::iObjectCount = %d\n",CBox::iObjectCount);  //CBoxes exist.
 getchar();

 return 0;
}

/*
bx[0].Volume()     =  6
bx[1].Volume()     = 24
bx[2].Volume()     = 60

CBox::iObjectCount = 3
*/

     What we've done above in CBox.h is add a static data member to our CBox class to count object instances...

static int iObjectCount;       // Static Class Data Member

And in CBox.cpp we've initialized it to zero...

int CBox::iObjectCount=0;      // << in CBox.cpp


     When the three CBoxes were created in main() above the un-initialized constructor for the CBox class incremented the static iObjectCount variable for each call of the constructor.  As can be seen in the output from the program it 'knows' three objects were created.  So it looks like this is a concept that would be helpful to us in counting windows if we can find some C counterpart to it in our use of the Windows Api.  And don't forget we don't do global variables, so sticking an iObjectCount in our ProgEx38c example is off limits to us!

     Not to worry!  Microsoft's Windows designers thought of all this!  If you look at the WNDCLASSEX struct you'll see that other field in addition to the .cbWndExtra bytes field that we've been making good use of so far and that is the .cbClassExtra field.  This field is for associating data with a class itself rather than with instances of the class.  It certainly looks applicable in terms of what we are needing to do here, i.e., count object instances and when none are left post a quit message to the Program's message queue.  Due to the fact that our initial design of ProgEx38 used dynamic memory allocations for its instance data, the changes we needed to make to the program to support the existence of multiple instances were minor.  In WinMain() we set the WNDCLASSEX::cbClassExtra bytes to four to store our object count.  In the WM_CREATE handler we increment whatever is read in through GetClassLong()...

Code: [Select]
WM_CREATE:
  long iCntr=GetClassLong(hwnd,0);
  iCntr++;
  SetClassLong(hwnd,0,iCntr);

And in WM_DESTROY we decrement the value stored in the .cbClassExtra bytes...

Code: [Select]
WM_DESTROY:
  long iCntr=GetClassLong(hwnd,0);
  iCntr--;
  if(iCntr==0)
  {
     MessageBox(hwnd,_T("No More Windows Left!"),_T("Will Now Close..."),MB_OK);
     PostQuitMessage(0);
  }
  else
     SetClassLong(hwnd,0,iCntr);

     Note that Windows nulls out both the .cbWndExtra and .cbClassExtra bytes at the point of the RegisterClassEx() call to register the class, so the count in our case above starts out at zero.  When the value stored by GetClassLong() reaches zero we call PostQuitMessage() and put up the MessageBox() that no more window objects are left.  Not too much extra code for a lot of extra functionality.  What do you think?
« Last Edit: September 14, 2011, 09:27:34 PM by Frederick J. Harris »