So, let's convert the last two CBox programs to GUI! And thank you James Fuller for pointing out some casting issues with 64 bit mingw w64!
/*
CBox3
One of the central ideas of object oriented programming is that data and
the methods that operate on that data be associated closely together
within some sort of containing structure or object. That is essentially
the idea of a class, and we have one in our simple CBox class which has three
data members, and a Volume() method which operates on those members. What
about windows in a graphical user interface, however? Are they not also
objects? Indeed they are! To create a main program window in ProgEx37's
WinMain() function, we had to instantiate a WNDCLASSEX struct, and fill out
the members of that struct. Note that somewhat like our CBox's Volume()
method, one of the members of that WNDCLASSEX struct was actually a function,
or more specifically, a pointer to a function, i.e., WNDCLASSEX::lpfnWndProc,
which, believe it or not, at the binary level amounts to pretty much the same
thing as a function. So we have a CBox class and an instance of that class
with sides 2.0, 3.0, and 4.0, and we have a Window Class which we'll name
CBox3 (see wc.lpszClassName down in WinMain()) and which creates a GUI
window. And just what is the relationship between these two seemingly
unrelated entities? Well, nothing really, unless we create one in our efforts
to bring order out of chaos. But consider this; Classes are container objects
that can hold or contain not only simple data primitives such as integers
and floating point numbers, but also other composite objects such as other
structs and class objects. So, since the purpose of CBox3 is to calculate
and display in a GUI information about a Cbox object with sides 2.0, 3.0, and
4.0, why don't we include our CBox object as part of the CBox3's Window Class?
But how, you ask? In our C++ CBox class we had three doubles to hold the
CBox::m_Length, CBox::m_Width, and CBox::m_Height data members of CBox. How
are we going to put a CBox object in CBox3's WNDCLASSEX struct/class? Its
all a matter of the sizes of things.
The size of a CBox object is 24 bytes because it contains three doubles which
are eight bytes each. The Volume() method doesn't add anything to the size
of an instantiated CBox object because it pertains to the class - not to any
instance of the class. When the operating system loads a program into memory
that instantiates any CBoxs, it will only create one Volume() function which
all instances of the class will use. When a CBox needs to be calculated a
'this' pointer to the memory allocation for the three doubles will be passed
to the Volume() method.
In the case of the Window Class CBox3 which is registered down in WinMain()
below, the .cbWndExtra member of the class can be used to tell Windows to
allocate extra bytes within the class to store user specified data, thus
allowing us to store a CBox object within the Window Class structure, and
thereby and in that manner form our logical association of a GUI window with
what had heretofore been only something showing up in a black console screen!
The actual mechanics of how we are going to do this is that we are going to
assign four or eight bytes to the .cbWndExtra bytes, which will allow us to
store a pointer to an instantiated CBox object within our Window Class
structure. We can allocate this pointer with C++'s new operator. The way its
finally stored there is through a Windows Api function named
SetWindowLongPtr().
Hold on a minute you holler! Why are we storing anything, you ask? In our
console programs CBox1 and CBox2 we didn't store anything. All we did was
click the run button and we got the volume of our CBox object displayed in our
console window!
Yes, yes, yes -- all true. But just how long, might I ask, did that console
program really exist? How long did it live? In other words, how long was it
actually running? Given the speed of modern computers, probably about
0.00000001 seconds from the time you clicked the 'Run' button until it
terminated. By the time you saw its output on the console screen, in micro-
processor significant timing terms, it had been long dead. Now think about
ProgEx37 where a GUI window presented itself on your screen and you could
interact with it. For example, you could simply sit and watch it and do
nothing more than marvel at its simple existence and being (and savor the
fact that you finally found out how to create a GUI window). Or you could
drag it around on the screen, minimize it, maximize it, etc. You could even
click it repeatedly with your mouse and read the MessageBox() output and
think about how that is being caught and generated within the Window
Procedure with its switch logic. During all this time - the program lives!
It is alive. All of which raises the question of its creation, existence,
and destruction.
If we look at our CBox object we see it does have a constructor which will
execute whether a CBox is created on the program stack as in CBox1 and CBox2,
or whether it is created dynamically with the new operator as in CBox3 below.
But what is the constructor for our GUI program's CBox3 class below?
Actually, when the CreateWindowEx() call in WinMain() executes code internal
to the Windows operating system creates an instance of the CBox3 class, and
when it finishes doing whatever it needs to do it calls the Window Procedure
for the CBox3 class which is WndProc() below, and when it does this it sets
the msg parameter to the WM_CREATE message. Therefore, the constructor for
our GUI window of class CBox3 is in effect a message handling routine for the
WM_CREATE message! I'll have more to say about that as we proceed through
this tutorial. But for now lets take a look at what happens under the
WM_CREATE case of the switch below....
CBox* pBox=new CBox(2.0,3.0,4.0);
if(pBox)
SetWindowLongPtr(hWnd,0,(LONG_PTR)pBox);
else
return -1;
return 0;
In the above code snippet a CBox object is dynamically created and a pointer
to it is temporarily stored in the automatic local variable pBox. We can
assume the object was successfully created if pNew contains a non null value,
and if that is the case the Windows Api function SetWindowLongPtr() is used to
store or persist the pointer to the now 'alive' CBox object within the
instantiated class structure of GUI window CBox3. As previously mentioned,
we allocated storage for four .cbWndExtra bytes in the class structure of
CBox3 down in WinMain() where the class was registered with Windows. Further
realize, and this is important, at the point in time where this code snippet
in the WM_CREATE handler is executing, we are actually 'inside' the
CreateWindowEx() call down in WinMain(), and that function has not as of yet
returned and assigned a valid window handle ( HWND ) to the HWND variable
allocated down in WinMain(), i.e.,
HWND hWnd;
In fact, if the new operator fails to construct the CBox object (which isn't
likely), the else clause of the if will execute and a -1 will be returned
from WndProc(). Minus one is considered failure in this case, and that would
cause the CreateWindowEx() call in WinMain() to fail, and hWnd would be
assigned a NULL at the point of the CreateWindowEx() call. Assuming success
a zero will be returned from WndProc and the HWND variable will contain a
valid window handle. Window handles are actually a very important concept
in Windows programming and the essence of these variable types is that they
are essentially virtual memory pointers to protected parts of the operating
system which only Windows itself can manipulate. Sometimes they are called
'opaque pointers'. Windows knows how to deal with them and provides them to
us as tokens we can use when we call upon Windows through its various
functions to do things for us. Other than passing them back to various
Windows Api functions which require them as parameters, about the only thing
we can do with them is output their values if we wish to examine them. We
can't dereference them, do calculations with them, or anything of that sort.
If you output their values you'll usually find the values fall in the positive
range of a 32 bit variable, that is, between 0 and about 2.1 billion.
So where are we at this point in explaining the workings of CBox3 below?
At the point of return from the successful WM_CREATE handler (which is
actually a function call and at the same time a constructor call of the CBox3
Window Class), we have a living CBox object, a pointer to which is persisted
within CBox3's instantiated structure, and we have a successfully created GUI
window which is, however, not yet visible. In WinMain() the next code line to
execute after the CreateWindow() call is a call to ShowWindow(). At this
point Windows will send the Window Procedure a WM_SIZE message, which CBox3's
Window Procedure doesn't process (it passes it to DefWindowProc() for
processing), and finally the very important WM_PAINT message, which our
Window Procedure does process. When you look at the code in WM_PAINT you'll
see that quite a bit is going on!
The first thing we have to do there is allocate a bunch of local automatic
variables to reference the various objects we'll need to manipulate. First
and foremost is a CBox object, a pointer to which is stored within the CBox3
object. Note that the CBox pointer 'pBox' we allocated in the WM_CREATE
handler went out of scope when WM_CREATE finished executing. So we need
to allocate a new pointer and get the address of our persisted CBox back into
it. We used SetWindowLong() to store the pointer in the Window's structure
and we'll use GetWindowLong() to retrieve it back out. To get the pointer
back we pass the hwnd parameter and the offset within the .cbWndExtra bytes
of where we stored the pointer. Our pointer is stored at offset zero in our
.cbWndExtra bytes.
At this point it might occur to you to question why I am doing this when I
could have created a global or static CBox object. This would have saved
having our CBox object's pointer go out of scope when WM_CREATE finished, and
we could have just used that instance to output and draw stuff, which is what
we want to do in our WM_PAINT processing.
I could give you the easy answer or the hard answer or both. Why don't I give
you both? OK, the easy answer is that I don't use global variables in my
Windows GUI applications. The hard answer is that in complex and large GUI
applications with many windows that do many different things you can not
always be certain of the state of globally defined data, in that you seldom
know what the user might have done as he/she points and clicks about your
application's various windows. Object Oriented Programming is something of
an answer to this problem in that one of its principals, as we previously
discussed, is that an object's data should be closely associated with it and
under its protection. It shouldn't just be hanging out there exposed on
every side to anything some errant procedure or user wishes to do to it. In
this application, the purpose of which is to create a CBox object with sides
2.0, 3.0, and 4.0, and display the sides and volume of the box in a GUI
window, we 'contained' our CBox object within our Window object, and therefore
we have no need to leave its privates exposed and hanging all over the place
for the world to see. The other thing my policy of 'No Global Variables'
achieves is freedom from the time wasting and difficult process of trying to
decide as you write an application's code which variables should be globals
and which should be locals passed through procedure parameters. I believe that
once you make the choice to create that first global variable, you've stepped
out upon a 'slippery slope' where the creation of each one after that becomes
easier (its easier to take the lazy route of not passing data through
parameters), and by the time the application is done you've got all this
global data acted on by procedures which you'll likely never be able to reuse
in other applications simply because the procedures aren't generalized or
self-contained in any way - they act on global data specific to one
application - and not the one you are presently working on.
So in this case we want to output the sides and volume of our CBox object and
we retrieve it with GetWindowLong(). That isn't the first Api function call
in the WM_PAINT handler though; BeginPaint() is. You really need to look up
all these various Windows Api functions in your documentation to see what
Microsoft has to say about them. BeginPaint() returns a handle to a device
context (its one of those opaque pointers I mentioned) and requires a HWND
parameter and the address of a PAINTSTRUCT object. The rest of the code
manipulates some complex GDI objects (Graphics Device Interface) to do such
things as create fonts, set painting modes, and draw text. You eventually
need to learn about the details of all these operations, but for now I'd like
you to simply concentrate on the big picture of what's going on. We're simply
retrieving our CBox object from the CBox3 instance we created in WinMain(),
i.e., our main program window, and we're using the TextOut() function to draw
the length, width, height, and volume of the box to the screen. At the
conclusion of the WM_PAINT handler all our local variables go out of scope
and are automatically destroyed, but not before we released back to Windows
various resources we needed for our work such as the FONT object we created.
At the conclusion of this procedure the window is visible on the screen and
you should be able to see the results. I'd encourage you to set up a new GUI
project and run the code if you haven't done so already. I'd like to point
out that when the window becomes visible you can best think of it as a self
contained GUI object that contains our CBox object as one of its members.
Further, each time you do anything to the window that obscures any part of it
such as minimizing it or dragging another window over it, when the window
becomes exposed again such as by restoring it or dragging another window away
from it, that WM_PAINT handler will execute and display the statistics for our
CBox object which is still very much in existence in computer memory. What
will finally destroy the CBox object and the window will be a click on the
[x] button to close the window.
When that happens Windows will send this program a WM_CLOSE message which this
program doesn't have a specific handler for, and so it will be passed on to
DefWindowProc() for default processing. The default processing for a WM_CLOSE
message is for Windows to send the window to which it pertains a WM_DESTROY
message. That message this program does handle.
It handles it by once more retrieving the pointer to the CBox object and then
it calls delete on the pointer to release its 24 bytes or whatever the
operating system allocated to it. Finally, PostQuitMessage() is called and
this call causes the message pump in WinMain() to terminate and WinMain() to
return.
Note To Visual Studio users: I used Visual Studio 2008 to create and test this app.
I used these preprocessor definitions to turn off various compiler warnings ...
_CRT_SECURE_NO_WARNINGS;
_CRT_NON_CONFORMING_SWPRINTFS;
If you wish to recode the application so as to eliminate the necessity for these
definitions, feel free to do so.
*/
//Main.cpp //C++ Object Oriented Program using native Windows C Api to display
//#define UNICODE //the dimensions and volume of a CBox Class object. Note that this
//#define _UNICODE //app contains no global variables. Using Code::Blocks, if you
#include <windows.h> //uncomment the two UNICODE defines just left, you'll get a wide
#include <tchar.h> //character compile. These aren't needed in Visual Studio. This app
#include <stdio.h> //tested with Visual Studio 2008 Professional with 32 bit and 64 bit
#include "CBox.h" //platforms, and with 32 bit platform using mingw and Code::Blocks.
LRESULT CALLBACK WndProc(HWND hWnd, unsigned int msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_CREATE: //This message is received one time as a
{ //result of the CreateWindow() call in
CBox* pBox=NULL; //WinMain(). The CreateWindow() call in
pBox=new CBox(2.0,3.0,4.0); //WinMain will not return until this case
if(pBox) //handler returns. A new CBox is created
SetWindowLongPtr(hWnd,0,(LONG_PTR)pBox); //here on the heap. A pointer to it is
else //stored in the instantiated window's class
return -1; //structure. This is how the CBox object
return 0; //is persisted across invocations of the
} //Window Procedure without using globals.
case WM_PAINT:
{
CBox* pBox=NULL; //The Window Procedure is called with the msg parameter
HFONT hFont,hTmp; //of WM_PAINT any time any part of the Window's client
PAINTSTRUCT ps; //area becomes invalid. This will be true right after
HDC hDC; //processing of the WM_CREATE handler when the window
TCHAR szBuffer[128]; //is not yet visible. The BeginPaint() Api function is
hDC=BeginPaint(hWnd,&ps); //particularly important in Windows Programming using
int iBkMode=SetBkMode(hDC,TRANSPARENT); //Windows original GDI functions. One of
hFont= //the parameters of BeginPaint() is a PAINTSTRUCT struct.
CreateFont //It would be a considerable revelation to you in your
( //Windows programming to output to a text log file the
-1*(18*GetDeviceCaps(hDC,LOGPIXELSY))/72, //various fields of the PAINTSTRUCT and
0, //their values while you resize the window and obscure it
0, //then once more reveal it with another window. What
0, //you'll find is that this WM_PAINT handler is being
FW_HEAVY, //called furiously dozens of times per second in response
0, //to your interactions with your display. An HDC (Handle
0, //to a Device Context) is a virtual memory pointer which
0, //points to an object Windows maintains in memory to hold
ANSI_CHARSET, //various attributes of the display device to which it is
OUT_DEFAULT_PRECIS, //rendering output. Many of these attributes are objects
CLIP_DEFAULT_PRECIS, //themselves, such as the FONT object at left being created.
PROOF_QUALITY, //Once this FONT object hFont is created, it has to be
DEFAULT_PITCH, //selected into the device context with the SelectObject()
_T("Courier New") //function just below. The method of operation of the
); //SelectObject() function is such that the return value is
hTmp=(HFONT)SelectObject(hDC,hFont); //a handle to the object removed out of the
pBox=(CBox*)GetWindowLongPtr(hWnd,0); //default device context, and this must be saved
_stprintf(szBuffer,_T("Box.GetLength() = %6.2f"),pBox->GetLength()); //so that at
TextOut(hDC,25,30,szBuffer,(int)_tcslen(szBuffer)); //termination of
_stprintf(szBuffer,_T("Box.GetWidth() = %6.2f"),pBox->GetWidth()); //WM_PAINT processing
TextOut(hDC,25,60,szBuffer,(int)_tcslen(szBuffer)); //it can be selected
_stprintf(szBuffer,_T("Box.GetHeight() = %6.2f"),pBox->GetHeight()); //back into the device
TextOut(hDC,25,90,szBuffer,(int)_tcslen(szBuffer)); //context. If this
_stprintf(szBuffer,_T("Box.Volume() = %6.2f"),pBox->Volume()); //all seems complicated
TextOut(hDC,25,120,szBuffer,(int)_tcslen(szBuffer)); //to you, its essentially
SelectObject(hDC,hTmp); //equivalent to Rocket Science. Since this window's purpose in life
DeleteObject(hFont); //is to display the specs on a CBox object, it has a pointer to one
SetBkMode(hDC,iBkMode); //of these stored as part of its instantiated Window Structure.
EndPaint(hWnd,&ps); //It is in these four exta bytes that I'm storing the pointer to
return 0; //the CBox object, whose 'duration' is the lifetime of the program.
//It remains alive until the user clicks the [x] button to close
} //the app, at which point delete is called on the CBox object.
case WM_DESTROY: //Try to think about this whole application as a C++ object. It
{ //is created and allocates memory; it hides all its internal details
CBox* pBox=NULL; //through hidden private members; and it cleans up after itself at
pBox=(CBox*)GetWindowLong(hWnd,0); //termination. Note that you can think of the WM_CREATE
if(pBox) //message as a call of an object constructor, and the WM_DESTROY
delete pBox; //message as a call to an object destructor. The WM_PAINT handler
PostQuitMessage(0); //causes the object to render itself. Further realize that the
return 0; //awkward coding structure of this application with the large switch
} //can be completely altered so that each WM_message is routed to a
} //seperate message or 'event' handling function.
return (DefWindowProc(hWnd, msg, wParam, lParam));
}
int WINAPI WinMain(HINSTANCE hIns, HINSTANCE hPrevIns, LPSTR lpszArgument, int iShow)
{
TCHAR szClassName[]=_T("CBox3");
MSG messages;
WNDCLASS wc;
HWND hWnd;
wc.lpszClassName=szClassName, wc.lpfnWndProc=WndProc;
wc.style=0, wc.hIcon=LoadIcon(NULL,IDI_APPLICATION);
wc.hInstance=hIns, wc.hCursor=LoadCursor(NULL,IDC_ARROW);
wc.hbrBackground=(HBRUSH)COLOR_BTNSHADOW, wc.cbWndExtra=sizeof(void*);
wc.lpszMenuName=NULL, wc.cbClsExtra=0;
RegisterClass(&wc);
hWnd=CreateWindow(szClassName,szClassName,WS_OVERLAPPEDWINDOW^WS_MAXIMIZEBOX,100,100,400,300,0,0,hIns,0);
ShowWindow(hWnd,iShow);
while(GetMessage(&messages,NULL,0,0))
{
TranslateMessage(&messages);
DispatchMessage(&messages);
}
return (int)messages.wParam;
}