Author Topic: ProgEx37 -- Windows GUI Programming; Basic Template Program With Discussion  (Read 11146 times)

0 Members and 1 Guest are viewing this topic.

Offline Frederick J. Harris

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 768
  • Gender: Male
    • Frederick J. Harris
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...

Code: [Select]
//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...

Code: [Select]
//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...

Code: [Select]
//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...

Code: [Select]
//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.
« Last Edit: September 10, 2011, 03:19:07 PM by Frederick J. Harris »

Offline Frederick J. Harris

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 768
  • Gender: Male
    • Frederick J. Harris
Whew!  Having gotten through all that stuff about Windows Class Frameworks and character encodings, lets look at something of a basic template Windows Application!

Code: [Select]
/*
  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!
*/
« Last Edit: December 31, 2012, 09:36:04 PM by Frederick J. Harris »

Offline Frederick J. Harris

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 768
  • Gender: Male
    • Frederick J. Harris
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...

Code: [Select]
#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

Code: [Select]
//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;
}

Code: [Select]
/*
  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...
*/
« Last Edit: September 10, 2011, 03:42:53 PM by Frederick J. Harris »

Offline Frederick J. Harris

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 768
  • Gender: Male
    • Frederick J. Harris
So here is CBox2.  Check out the size!  Use CBox.h and CBox.cpp from the previous example (CBox1)...

Code: [Select]
/*
  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).
*/

« Last Edit: August 07, 2011, 03:03:00 AM by Frederick J. Harris »

Offline Frederick J. Harris

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 768
  • Gender: Male
    • Frederick J. Harris
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!

Code: [Select]
/*
  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;
}
« Last Edit: December 31, 2012, 09:41:04 PM by Frederick J. Harris »

Offline Frederick J. Harris

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 768
  • Gender: Male
    • Frederick J. Harris
Code: [Select]
/*
  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;
}