/*
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;
}
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.
//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;
}
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.
'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
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...
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.
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...
//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...
//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
//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;
}
//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()...
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...
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?