First A Little Perspective
One of the most recurring questions on C++ Forums goes something like this "...been doing console programs for some time now and would like to write a program that uses a real Window instead of the simple black console screen....I know C++ pretty good but....been searching the web for examples...just don't know enough about it to even ask the right questions as far as what to study or where to begin..."
Here is one place where you can begin, but there are others, which I'll shortly list. However, before doing that or looking at code, you need some background on the issues and terminology involved, so that you can at least ask the right questions and make the most informed choices in terms of what is right for you.
Windows, Unix, and Linux were written in C back in the early 1980s before C++ was even developed. The lowest level interfaces to these operating systems, that is, their Application Programming Interfaces (APIs ), are C based; not C++ based. Early Windows programmers used C to write Windows programs. From the beginning of Windows in 1985 until 1990 C was about the only way it could practically be done. By 1991 Microsoft developed Visual Basic (and Borland developed Delphi) and that allowed for drag and drop Windows Graphical User Interface (GUI) development. On the C++ front, also by 1990, it was realized that C++ object creation capabilities (OOP) could be used to 'wrap' the low level direct Api of Windows into class 'wrappers' that hide many of the low level and boiler plate type code from the coder; hopefully making it easier. These are termed 'Class Frameworks'. There are a lot of them in both the Windows world and the Unix/Linux world. A list of some of them would be Microsoft Foundation Classes (MFC), Object Windows Library (OWL), .NET, wxWidgets, Qt, ad infinitum. These last mentioned are 'cross platform' toolkits, in that GUI programs developed with them can be compiled for either Windows or Unix/Linux. You have to understand though that there is no fundamental C++ way of writing GUI programs. Any C++ Class Framework you use makes C based calls to the underlying API of the host operating system (whether that be Windows or Linux or whatever). In other words, a Class Framework is an 'abstraction layer' that sits on top of the underlying API, in the same sense as the methods of your classes are an abstraction layer on top of the procedural code you compiler generates (but you never see) when you compile your classes.
When you want to get started writing GUI programs (for any operating system) you have to decide what fits your personality, inclinations, and needs. If you are a fast results type person and you want to leverage as much as possible off other programmer's code, that is, you don't care so much how it works, only that it works, then perhaps one of the class frameworks that hide a lot of low level details is right for you. The positives of this choice are that you can get your program written quicker (after the initial learning curve); oftentimes with drag and drop type programming environments. The negatives of this style are that the programs can be somewhat slow, they are oftentimes large in terms of code size and required dependencies (a lot of library code compiled into them, or the necessity of packaging runtimes and Dlls with your exes), and what's considered the best framework changes every few years. The various frameworks come into and fall out of favor fairly rapidly.
On the other hand, if you've got a lot of time to sink into your code, you like to know how things work 'under the hood', so to speak, or you need to do some specialized things that aren't easily or even possible to do through the class framework code, then you need to use an operating system's native API directly. That's basically what the 'Win32' choice is in Visual Studio or Code::Blocks specifically. The advantages of this approach are extremely small fast code. Also, it tends to be stable and the underlying APIs don't change very frequently. The down side of course is its fairly steep learning curve, and the code can become tedious at times due to the detail required.
There is more to the issue though; its not that simple. If you want to excel and be a 'power user' of any of the Windows Class Frameworks, you just about have to have a firm understanding of the technology that is being wrapped by the Class Framework, that is, the underlying Api. If this is beginning to sound like a lot of work in that mastery of both is necessary, the sad fact is that it is a lot of work. Many only learn one or the other, and their work suffers accordingly. You'll have to decide for yourself once you have a better understanding of the issues and the code. In this tutorial I'm going to attempt to get you started writing Windows GUIs in C++ using the Windows Api directly. However, I'll touch on some of the issues of Class Frameworks, other languages, object oriented programming, etc.
Finally, the unquestioned ultimate source for learning to write Windows programs using its native Application Programming Interface is Charles Petzold's "Programming Windows" books. While he doesn't work for Microsoft, his material is so good that even Microsoft quotes him as being the source of authority on many fine points of detail. The fifth edition of this book is still available as of the time of this writing in mid 2011; however, his "Programming Windows 95" book is out of print and only available used. Here is the Amazon.com link to "Programming Windows, Fifth Edition"...
http://www.amazon.com/Programming-Windows-Microsoft-Charles-Petzold/dp/157231995X/ref=sr_1_1?s=books&ie=UTF8&qid=1293903473&sr=1-1
The Forger's Win32 tutorial here is also noteworthy...
http://www.winprog.org/tutorial/
Before I launch into the topic of creating apps with a graphical user interface ( GUI ) in Windows, let me say a few words about ansi verses wide character strings.
It has now become de rigueur in Win32 C/C++ coding circles to exclusively use type redefinitions of basic C and C++ data types for representing character or string data. For example, the C or C++ char data type can hold one ansi char. It takes two bytes to hold the typical UNICODE character, and for that the 16 bit (2 byte) unsigned short int type of C and C++ is used. In wchar.h is contained this...
typedef unsigned short wchar_t;
...which makes wchar_t synonymous with an unsigned short int. The way this interacts with the C Runtime string primitives, i.e., strcpy, strlen, etc, is that there are separate functions defined for the wide character strings.
Examples are probably best. Start up a new Win32 console project and run this...
//Ansi.cpp
#include <stdio.h>
#include <string.h>
int main()
{
char szBuffer[]="Hello, World!";
int iLen;
iLen=strlen(szBuffer);
printf("iLen = %d\n", iLen);
printf("sizeof(szBuffer) = %d\n",sizeof(szBuffer));
getchar();
return 0;
}
//iLen = 13
//sizeof(szBuffer) = 14
In the output above you see that "Hello, World!" has a length of 13 and the sizeof the char array containing it is 14 due to the null terminator inserted by the compiler. For wide characters it would look like this with the 'derived' wide character type...
//wchar.cpp
#include <stdio.h>
#include <string.h>
int main()
{
wchar_t szBuffer[]=L"Hello, World!";
int iLen;
iLen=wcslen(szBuffer);
printf("iLen = %d\n", iLen);
printf("sizeof(szBuffer) = %d\n",sizeof(szBuffer));
getchar();
return 0;
}
//iLen = 13
//sizeof(szBuffer) = 28
The length of the string reported by wcslen() is still 13, but the size of the array containing the string has increased to 28; there are two null bytes at the end because don't forget each character is now an unsigned short integer. Also take note of the capital 'L' preceding the "Hello, World!" literal character string. The capital 'L' tells the compiler to store the characters in unsigned short ints instead of single bytes.
Now comes the good part (depending on how you define 'good' I suppose)! Microsoft included a non - standard Microsoft specific header named tchar.h that contains macros (#defines) which key off of an identifying equate as so...
_UNICODE
If your program defines this entity before it encounters the includes and tchar.h is included then generic macro functions and data types will be defined which can be substituted for the actual ansi or wide character data types and functions. If tchar.h is included but the _UNICODE identifier is not encountered by the preprocessor, then the generic functions and data types reduce simply to the ansi single byte char based functions.
Clear as mud. Lets take one of our programs above and use the generic data types and macros provided in tchar.h instead of the actual C Runtime functions and data types...
//tchar1.cpp
#include <stdio.h>
#include <string.h>
#include <tchar.h>
int main()
{
TCHAR szBuffer[]=_T("Hello, World!");
int iLen;
iLen=_tcslen(szBuffer);
_tprintf(_T("iLen = %d\n"), iLen);
_tprintf(_T("sizeof(szBuffer) = %d\n"),sizeof(szBuffer));
getchar();
return 0;
}
//iLen = 13
//sizeof(szBuffer) = 14
It gives precisely the same output as Ansi.cpp (1st little program above). However, note that tchar.h is included. Note further that _UNICODE isn't defined. What will happen here is that a typedef in tchar.h will cause the TCHAR macro to reduce to char...
typedef char TCHAR; // from tchar.h
So then, the first part of the variable declaration...
TCHAR szBuffer[]= ...
...will reduce to this...
char szBuffer[]= ...
Note the _T() macro part. In wchar.cpp above we defined the wide character string like so...
wchar_t szBuffer[]=L"Hello, World!";
The 'L' preceding the literal string caused the compiler to store the characters in static memory in unsigned shorts instead of in single bytes. In tchar.cpp above, because _UNICODE isn't defined, the _T() macro simply reduces the quoted literal to itself, i.e., "Hello, World!", without the prepending 'L'. And _tcslen becomes strlen, _tprintf becomes printf, etc. All of which makes one wonder I suppose why one would want to go through all this hassle? Well, try this, which is the same exact program except for the one line at top...
#define _UNICODE...
Here it is...
//tchar2.cpp
#define _UNICODE
#include <stdio.h>
#include <string.h>
#include <tchar.h>
int main()
{
TCHAR szBuffer[]=_T("Hello, World!");
int iLen;
iLen=_tcslen(szBuffer);
_tprintf(_T("iLen = %d\n"), iLen);
_tprintf(_T("sizeof(szBuffer) = %d\n"),sizeof(szBuffer));
getchar();
return 0;
}
//iLen = 13
//sizeof(szBuffer) = 28
Same exact program, but just by adding one simple line its now a unicode or wide character string program. The _T("Hello, World") reduced to L"Hello, World!"; the tcslen(szBuffer) reduced to wcslen(szBuffer), etc. Pretty powerful stuff. I'd encourage you to open tchar.h and examine it all. The only drawback to it of course is that its ugly as sin and the last word in hassle. The only thing possibly worse are any of the other possible alternatives! If I want to use my name "Fred" I now have to do this...
_T("Fred")
instead of...
"Fred" ...
...which is 10 key presses instead of six and many of them awkward shifted keys to boot! All I can say is that its the cost of doing business in Windows C or C++. Resistance is futile. You must submit!
OK, so how does this relate to Win32 Application Programming Interface coding? All the Win32 functions come in both wide character (UNICODE) and ansi versions. Very shortly we'll be looking at the all powerful CreateWindow() function. Well, not exactly. There isn't any CreateWindow() function. What there are though is a CreateWindowA() function and a CreateWindowW() function with a macro named CreateWindow which reduces to a call to CreateWindowW if UNICODE is defined and CreateWindowA if it isn't. The missing '_' on UNICODE isn't a typo or oversight on my part. The Windows Api uses UNICODE as its identifier to key off of instead of the _UNICODE of tchar.h. So if you want to use the wide character versions of the Windows Api functions and the wide character versions of any C Runtime functions you might wish to use, then you'll need to define both symbols at the top of your source code files before your includes...
#define UNICODE
#define _UNICODE
I told you all that to tell you this... If you just downloaded Microsoft's Visual C++ Express from the internet, and have dug up some Win32 programs from somewhere from which you hope to get started with Win32 programming, there's a real good chance you are going to run afoul of this stuff I've just briefly explained. In other words, your code won't compile; there will be compiler errors - piles of them, and you are going to be lost and frustrated. For you see, those _UNICODE and UNICODE defines aren't always located at the top of your source code file; they can also be fed into the compiler tool chain by your development environment and this is how Microsoft's Visual Studio editor for C++ is setup. Its default 'Character Set' setting (which can be found in the IDE under...
Project Properties >> Configuration Properties >> General
...has a default setting of UNICODE. So to make this long story only slightly shorter you are likely going to have to deal with this character set issue at the outset of your Windows programming endeavors rather than at some later date more to your liking.
There are a number of 'things' you can do to deal with this issue, and some of the 'things' are better than others. What I'd recommend you do and what I am going to do in this tutorial is include the tchar.h header file with all my code and exclusively use the TCHAR and _T() or TEXT() macros for character strings. That way, these program examples of mine here and your programs will work regardless of whether some define (UNICODE, _UNICODE) is present or not. That's a powerful idea and should be your goal.
Whew! Having gotten through all that stuff about Windows Class Frameworks and character encodings, lets look at something of a basic template Windows Application!
/*
ProgEx37 - Form1 : Basic Graphical User Interface Program Using The Win32 Api.
Tested with VC 9 32 and 64 bit; Code::Blocks mingw 32 bit.
Now, to finally get to coding GUI apps! My discussion here will reference the
code in Main.cpp just below. Its not a very big program, so please don't be
intimidated by it! To create a GUI (Graphical User Interface) Windows program
that uses the raw low level C Api (however, you can compile it as a C++ program)
and that creates a main program window you must do the following things...
1) Set up a project in whatever development environment you are using, i.e.,
Microsoft Visual Studio, Code::Blocks, Dev-C++, etc., that will support the
creation of a Windows Graphical User Interface Win32 program. This is
important because your development environment must feed your code into the
compiler 'tool chain' (preprocessor, compiler, linker) and Windows programs
have specific requirements that must be met if they are to compile and link
without errors. In Code::Blocks, for example, when you create a new project,
set the type to a 'Win32 GUI Project'. In Visual Studio, create a Win32
project without ATL, .NET, or MFC support. Tell Visual Studio to create
an 'empty project' (we'll provide our own code). I wish to emphasize that
you can't skip this step even if your environment has allowed you to do so
in the past with console programs where you might have been able to open
your editor, start typing code, click the compile button, and have it work.
If that was the case and it worked your environment was making assumptions for
you and creating a default console project. This likely won't work with
Win32 GUI coding;
2) Include the required header file support. That would be Windows.h and tchar.h.
Windows.h is actually a master include that includes many other includes. Windows
is a very sophisticated complex system that requires many equates, structs,
typedefs, and function prototypes. Below you can see we've done that with these
lines...
#include <Windows.h>
#include <tchar.h>
3) Code a conformant Window Procedure and WinMain() function. These procedures must
conform to their required return values and function signatures. In looking at
both those procedures below you can see a lot of variable types with which you
might not be familiar such as HWND, WPARAM, HINSTANCE. These as well as many
other types are defined in Windows.h or one of the headers included from Windows.h.
WinMain() is the entry point of a Windows program, and the Window Procedure is a
concept central to the architecture of graphical user interface programs in that
it is the procedure the operating system calls to inform the program of user
actions or system events. In Main.cpp below I named it fnWndProc() and the address
of this function is passed to Windows at program start up through the WNDCLASSEX
struct;
4) In WinMain() declare a struct of type WNDCLASSEX (Window Class Extended) and
fill out the members of this complex type. Windows classifies all windows whether
we are talking about main program windows, buttons, text boxes, ActiveX controls,
whatever, as being members of some specific 'class'. Its really not exactly a
Class in the C++ sense, but rather a struct. However, its a rather complex struct
in that one of its members is actually a function pointer (more about that later).
Two particularly important WNDCLASSEX members are lpszClassName and lpfnWndProc.
Here is the declaration of a WNDCLASSEX struct...
typedef struct _WNDCLASSEX
{
UINT cbSize;
UINT style;
WNDPROC lpfnWndProc; //Function Windows calls by sending messages
int cbClsExtra;
int cbWndExtra;
HANDLE hInstance;
HICON hIcon;
HCURSOR hCursor;
HBRUSH hbrBackground;
LPCTSTR lpszMenuName;
LPCTSTR lpszClassName; //name of class
HICON hIconSm;
}WNDCLASSEX;
By the way, the above struct is in WinUser.h (one of the more noteworthy files
included by the master include Windows.h) and it too is dependent on the definition
or lack thereof of the UNICODE #define. What is actually used internally by Windows
is WNDCLASSEXA or WNDCLASSEXW. So you see this character encoding business isn't
some minor detail. Its actually fundamentally built in to the design of the
operating system and your compiler.
I'd like to further point out that the Microsoft Developer Network, i.e., MSDN, in
both its online and offline versions is truly an excellent source of information
for learning Windows programming. For example, by simply typing in 'WNDCLASSEX'
you'll be taken to a page with voluminous information on the WNDCLASSEX struct, as
well as links to related information. I imagine most Windows programmers are like
me in that they spend a large percentage of their coding time on MSDN. Its not
really optional.
But back to the details. The lpszClassName (long pointer to a string terminated by
zero that is going to be the class name) is the name of the class. This is a char
string (of one sort or another!). In the program below it is _T("Form1"). When it
comes time to create an instance of the class a function named CreateWindowEx() will
be used, and one of the many parameters to this function is the class name.
The lpfnWndProc is the address of the Window Procedure that will receive messages from
Windows when Windows itself detects that some item of interest that pertains to the
window has occurred. In our program below it is fnWndProc(). Windows will call
this function (send it a message) when the window is first created, for example. The
message the Window Procedure will receive in that case is the WM_CREATE message. In
the program below our only response to that message is to put up a message box
notifying us of the message. There are actually hundreds of messages. The program
below only takes specific action on four of them. All others are passed onto
Default Window Proc (DefWindowProc()). In other words, you are saying to Windows,
"I'm not interested in that message. You deal with it if you need to";
5) After successfully filling out all the required member fields of the WNDCLASSEX type,
and calling RegisterClassEx() to register the class with Windows, you then use the
big powerful CreateWindow() or CreateWindowEx() function to create an actual instance
of a window of the class you just registered. The WNDCLASSEX struct specifies
general characteristics of all windows of a class. The CreateWindow function actually
creates a specific window with specific dimensions, location on the screen, caption,
Window Styles (more about that later), etc. So in other words, or in OOP speak,
the CreateWindow() call instantiates on object of the class specified as the 1st
parameter of a CreateWindow() call or the 2nd parameter of a CreateWindowEx() call and
as such can be considered as a C based call of an object constructor. Here is a brief
description from Microsoft's documentation on the CreateWindow() call...
HWND CreateWindow
(
LPCTSTR lpClassName, // pointer to registered class name
LPCTSTR lpWindowName, // pointer to window name << window caption
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
HANDLE hInstance, // handle to application instance
LPVOID lpParam // pointer to window-creation data
);
Just in the way of giving you the flavor of the sequence of events that occur at the
point of the CreateWindowEx() call in WinMain(), and the interaction between WinMain()
and the Window Procedure - fnWndProc(), at the point of the CreateWindowEx() call in
WinMain(), and while code internal to Windows itself is executing, Windows will send
a message or make a call on the program's Window Procedure. And I want to emphasize
that this will occur before CreateWindowEx() in WinMain() returns. You will note that
the function signature of the Window Procedure shows it contains four parameters.
While the CreateWindowEx() call in WinMain() is executing Windows will call the Window
Procedure passing into the 1st parameter the HWND, i.e., window handle, of the newly
created window. This is the first knowledge the program will have of this all
important entity, and the Window Procedure will get it before CreateWindowEx() returns
down in WinMain() and assigns it to the local HWND variable there. The 2nd parameter
of the call Windows will make at that time to the Window Procedure is the message
parameter WM_CREATE. There are hundreds of messages and they are typed as unsigned
integers, and you can find them described in Windows.h. The WPARAM and LPARAM
parameters of the Window Procedure call contain message specific information Windows
wants your code to know about, so that your code logic can use it or not, as the case
may be. In the case of the WM_CREATE message we are discussing, the LPARAM parameter
will contain a pointer to a CREATESTRUCT structure, the discussion of which I will
save until later (its quite interesting and important, IMHO).
After the CreateWindowEx() call in WinMain() returns the local HWND variable hWnd
will receive the same Window Handle as the one just received in the Window Procedure,
and a ShowWindow() call will be made. The ShowWindow() call will make the Window
visible on the screen (other messages will be sent to the Window Procedure - this
time WM_SIZE and WM_PAINT messages), and the program will enter its message loop
(sometimes called the message pump) in the form of a while loop where it continually
'Gets' and'Translates' messages Windows has placed in a message queue structure set up
for that purpose. When it receives a message from Windows it dispatches it to the
Window Procedure for processing. Also, the program's Window Procedure can be called
directly from Windows bypassing the message queene. In any case, the Window Procedure
will receive all messages bound for the program in an orderly manner.
In the program below, when you first start it, you'll get a message box telling you
the Window Procedure has received a WM_CREATE message. This message is only received
one time, and in more complex programs is often used for program initialization chores
such as creating the user interface elements, i.e., buttons, edit boxes, etc., that
will decorate the main window. Once the window becomes visible the only functionality
of this program is to present message boxes when you click on the Form or [x] out to
close the window. However, it does have default functionality of being able to be
resized, maximized/minimized, moved, etc. When you do click on the little 'x' to
close the window and program, Windows will send the Window Procedure a WM_DESTROY
message. I decided to handle that message by calling the PostQuitMessage() function,
which will cause the program to exit out of the while loop message
pump down in WinMain().
It might be pointed out that after compiling this program with any C++ compiler of
which I'm aware, your executable will be in a size range from 7 or 8 K to a max of
perhaps 15 or 16 K, depending on the settings of various configurable compiler
settings. The reason its so small is that all its functionality is derived from code
contained in core Dlls that are a part of every Windows installation. There are no
additional code dependencies that would increase its size as you would have for
example with MFC or .NET libraries. Also, once you understand what this code is doing
you can use it as a template or basis for creating real programs that actually do
something useful, and you can do this without having to suffer through Wizard
generated code and piles of extra files that are largely a mystery.
*/
//Main.cpp
#include <windows.h>
#include <tchar.h>
LRESULT CALLBACK fnWndProc(HWND hwnd, unsigned int msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_CREATE: //This message is only received one time at program start up. Think of it
{ //as a constructor call.
MessageBox(hwnd,_T("Window Procedure Received WM_CREATE Message!"),_T("Message Report!"),MB_OK);
return 0;
}
case WM_LBUTTONDOWN: //This message comes through when you click the form with your left mouse button.
{
MessageBox(hwnd,_T("Window Procedure Received WM_LBUTTONDOWN Message!"),_T("Message Report!"),MB_OK);
return 0;
}
case WM_PAINT: //This message comes through whenever any part of the window becoms 'invalid'.
{ //At program start up the whole window is invalid so must be drawn.
TCHAR szBuffer[]=_T("Click Anywhere On Form (And Oh Yeah...Hello, World!)");
//LPCTSTR szBuffer=_T("Click Anywhere On Form (And Oh Yeah...Hello, World!)"); // << can use this too
PAINTSTRUCT ps; // Necessary for BeginPaint(0 / EndPaint() calls
HDC hDC; // Handle (virtual memory pointer) to drawing characteristics
hDC=BeginPaint(hwnd,&ps); // Required protocol for WM_PAINT handler
int iBkMode=SetBkMode(hDC,TRANSPARENT); // Save Background Mode characteristic of drawing context
TextOut(hDC,40,20,szBuffer,(int)_tcslen(szBuffer)); // Draw Text
SetBkMode(hDC,iBkMode); // Return Drawing Context To Original State
EndPaint(hwnd,&ps); // End Of WM_PAINT protocol
return 0;
}
case WM_DESTROY: //This message comes through when you click the [x] close button in the upper right
{ //corner of your window.
MessageBox(hwnd,_T("Window Procedure Received WM_DESTROY Message!"),_T("Message Report!"),MB_OK);
PostQuitMessage(0);
return 0;
}
}
return (DefWindowProc(hwnd, msg, wParam, lParam));
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszArgument, int iShow)
{
TCHAR szClassName[]=_T("Form1");
WNDCLASSEX wc;
MSG messages;
HWND hWnd;
wc.lpszClassName = szClassName; //Important Field! Character string identifying window class
wc.lpfnWndProc = fnWndProc; //Important Field! Function Pointer. Address of Window Procedure
wc.cbSize = sizeof (WNDCLASSEX); //Those top two fields I just listed are very important. The
wc.style = 0; //others are of course necessary too, but fully understanding all
wc.hIcon = LoadIcon(NULL,IDI_APPLICATION); //the implications of the .szClassName and .lpfnWndProc fields will
wc.hInstance = hInstance; //go a long way to helping you understand Win32 coding. The
wc.hIconSm = 0; //.hBrushBackground field will be the color of the Window's
wc.hCursor = LoadCursor(NULL,IDC_ARROW); //background. The .cbWndExtra field is very useful as it allows
wc.hbrBackground = (HBRUSH)COLOR_BTNSHADOW; //you to associate object (Window) data to the instantiated Window's
wc.cbWndExtra = 0; //internal structure, i.e., accomodate member data.
wc.cbClsExtra = 0;
wc.lpszMenuName = NULL;
RegisterClassEx(&wc),
hWnd=CreateWindowEx(0,szClassName,szClassName,WS_OVERLAPPEDWINDOW,350,250,450,300,HWND_DESKTOP,0,hInstance,0);
ShowWindow(hWnd,iShow);
while(GetMessage(&messages,NULL,0,0)) //All important message pump. This logic continually retrieves
{ //messages from the program's message queene and dispatches them
TranslateMessage(&messages); //to the Window Procedure for processing.
DispatchMessage(&messages);
}
return (int)messages.wParam;
}
*/
/*
Let's move on to another program!
*/
Let's start with a simple console C++ program with a simple class to calculate the volume of a box, and work on converting it to a GUI (graphical user interface) program. We'll modify our template program above for that. But first, so that we are all on the same page, let's just do it as a simple console mode program of a type with which you should be comfortable...
#ifndef CBox_h
#define CBox_h
class CBox
{
public:
CBox(double,double,double); //Constructor
~CBox(); //Destructor
double GetLength() const; //m_Length accessor
double GetWidth () const; //m_Width accessor
double GetHeight() const; //m_Height accessor
double Volume () const; //Returns Volume() of Box
private:
double m_Length;
double m_Width;
double m_Height;
};
#endif
//CBox.cpp
#include "CBox.h"
CBox::CBox(double dblLength, double dblWidth, double dblHeight)
{
this->m_Length=dblLength;
this->m_Width=dblWidth;
this->m_Height=dblHeight;
}
CBox::~CBox()
{
//destructor
}
double CBox::GetLength() const
{
return this->m_Length;
}
double CBox::GetWidth () const
{
return this->m_Width;
}
double CBox::GetHeight() const
{
return this->m_Height;
}
double CBox::Volume () const
{
return m_Length*m_Width*m_Height;
}
/*
CBox1 -- Moving From The Console To GUI -- First Steps
Now that we have a template of sorts (ProgEx 37 above) for getting a GUI up
and running in Windows, lets try to see how we might convert our console code
to GUI. Lets just start with a simple program to calculate the volume of a
box, but lets wrap the thing up in a C++ class we'll name CBox. Here's the
code...
*/
//CBox1
#include <iostream>
#include "CBox.h"
using namespace std;
int main()
{
CBox Box(2.0, 3.0, 4.0);
cout << "Box.GetLength() = " << Box.GetLength() << endl;
cout << "Box.GetWidth() = " << Box.GetWidth () << endl;
cout << "Box.GetHeight() = " << Box.GetHeight() << endl;
cout << "Box.Volume() = " << Box.Volume () << endl;
return 0;
}
/*
Compiling: CBox.cpp
Linking console executable: CBox1.exe
Output size is 457.50 KB
Process terminated with status 0 (0 minutes, 0 seconds)
0 errors, 0 warnings
Box.GetLength() = 2
Box.GetWidth() = 3
Box.GetHeight() = 4
Box.Volume() = 24
Above is my output and even by telling my Code::Blocks IDE that I wanted to
'Strip All Symbols From The Executable' to minimize code size, and 'Optimize
For Size' (Project >> Build Options >> Compiler Settings), I'm still coming
in with a 457 K executable for a dumb console program that just multiplies
a few numbers together. My sophisticated GUI example above, i.e., ProgEx37
was only 8 K in comparison! I know hard drive space is cheap, but this is
ridiculous! Lets try a CBox2 like this...
*/
So here is CBox2. Check out the size! Use CBox.h and CBox.cpp from the previous example (CBox1)...
/*
CBox2
//Compiler Output
Compiling: CBox.cpp
Linking console executable: CBox2.exe
Output size is 19.00 KB
Process terminated with status 0 (0 minutes, 0 seconds)
0 errors, 0 warnings
//Program Output
Box.GetLength() = 2.00
Box.GetWidth() = 3.00
Box.GetHeight() = 4.00
Box.Volume() = 24.00
*/
//CBox2
#include <stdio.h>
#include "CBox.h"
int main()
{
CBox Box(2.0, 3.0, 4.0);
printf("Box.GetLength() = %3.2f\n",Box.GetLength());
printf("Box.GetWidth() = %3.2f\n",Box.GetWidth());
printf("Box.GetHeight() = %3.2f\n",Box.GetHeight());
printf("Box.Volume() = %3.2f\n",Box.Volume());
return 0;
}
/*
Well, we've got a 19 K executable with that. Better than 457 K for a do
nothing program. And it's just as easy. Lets stick with that and see about
converting it to GUI (If you don't know what that %3.2f stuff is about, they
are printf format specifiers. The '3' is field size, the '2' is number of
decimal places, and the 'f' tells printf its formatting a floating point
number).
*/
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;
}
/*
CBox4 Using Window Properties Instead of Windows Extra Bytes To Store Instance Data
In the last program, that is CBox3, we used the Windows Api functions SetWindowLong()
and GetWindowLong() to store or retrieve data associated with our window. The Api
functions GetProp() / SetProp() can also be used for this. If you type SetProp into
your browser's search feature you'll be led to Microsoft's documentation on SetProp,
which should look something like this...
SetProp Function
Adds a new entry or changes an existing entry in the property list of the specified window.
The function adds a new entry to the list if the specified character string does not exist
already in the list. The new entry contains the string and the handle. Otherwise, the
function replaces the string's current handle with the specified handle.
Syntax
BOOL WINAPI SetProp
(
__in HWND hWnd // A handle to the window whose property list receives the new entry.
__in LPCTSTR lpString, // A null-terminated string or an atom that identifies a string.
__in_opt HANDLE hData // A handle to the data to be copied to the property list. The data
// handle can identify any value useful to the application.
);
--------------- End MSDN ----------------
The only changes to CBox3 were to use SetProp() to store the CBox*, GetProp() to retrieve it
in the WM_PAINT handler, and RemoveProp() in the WM_DESTROY handler to remove the property
from the property list. Try CBox4 below. You might prefer to use Window Properties over
.cbWndExtra bytes to associate data with your windows. I don't know this for a fact, as I've
never tested it, but window properties might not be as fast as SetWindowLong() data due to
its being based on string searches.
*/
//Main.cpp //C++ Object Oriented Program using native
#define UNICODE //Windows C Api to display the dimensions
#define _UNICODE //and volume of a class CBox object. Note
#include <windows.h> //that this app contains no global variables.
#include <tchar.h>
#include <stdio.h>
#include "CBox.h"
LRESULT CALLBACK WndProc(HWND hWnd, unsigned int msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_CREATE:
{
CBox* pBox=NULL;
pBox=new CBox(2.0,3.0,4.0); //Instead of using SetWindowLong()
if(pBox) //to associate data with a window,
SetProp(hWnd,_T("pBox"),(HANDLE)pBox); //one can alternately use the
else //SetProp(), GetProp(), and
return -1; //RemoveProp() Api functions.
return 0;
}
case WM_PAINT:
{
CBox* pBox=NULL;
HFONT hFont,hTmp;
PAINTSTRUCT ps;
HDC hDC;
TCHAR szBuffer[128];
hDC=BeginPaint(hWnd,&ps);
SetBkMode(hDC,TRANSPARENT);
hFont=CreateFont
(
28,0,0,0,FW_HEAVY,0,0,0,ANSI_CHARSET,OUT_DEFAULT_PRECIS,
CLIP_DEFAULT_PRECIS,PROOF_QUALITY,DEFAULT_PITCH,_T("Courier New")
);
hTmp=(HFONT)SelectObject(hDC,hFont);
pBox=(CBox*)GetProp(hWnd,_T("pBox"));
_stprintf(szBuffer,_T("Box.GetLength() = %6.2f"),pBox->GetLength());
TextOut(hDC,25,30,szBuffer,_tcslen(szBuffer));
_stprintf(szBuffer,_T("Box.GetWidth() = %6.2f"),pBox->GetWidth());
TextOut(hDC,25,60,szBuffer,_tcslen(szBuffer));
_stprintf(szBuffer,_T("Box.GetHeight() = %6.2f"),pBox->GetHeight());
TextOut(hDC,25,90,szBuffer,_tcslen(szBuffer));
_stprintf(szBuffer,_T("Box.Volume() = %6.2f"),pBox->Volume());
TextOut(hDC,25,120,szBuffer,_tcslen(szBuffer));
SelectObject(hDC,hTmp);
DeleteObject(hFont);
EndPaint(hWnd,&ps);
return 0;
}
case WM_DESTROY:
{
CBox* pBox=NULL; //Use RemoveProp() to remove
pBox=(CBox*)RemoveProp(hWnd,_T("pBox")); //the property from Windows
if(pBox) //property list. The return
delete pBox; //value from RemoveProp() is
PostQuitMessage(0); //the numeric value of the
return 0; //CBox* (in this case).
}
}
return (DefWindowProc(hWnd, msg, wParam, lParam));
}
int WINAPI WinMain(HINSTANCE hIns, HINSTANCE hPrevIns, LPSTR lpszArgument, int iShow)
{
TCHAR szClassName[]=_T("CBox4");
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=4;
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 messages.wParam;
}