/*
Form4.cpp -- Windows Messages, Instance Data, SetClassLongPtr(), SetWindowLongPtr()
This program illustrates the following concepts:
1) Using memory allocations to store 'instance data' associated with a window;
2) Storing such data as part of the WNDCLASSEX::cbWndExtra bytes;
3) Counting instantiated window instances using SetClassLongPtr()/GetClassLongPtr();
4) Processing/handling WM_CREATE, WM_SIZE, WM_MOUSEMOVE, WM_LBUTTONDOWN, WM_CHAR,
WM_PAINT, and WM_DESTROY messages;
5) Instantiation of multiple instances/windows of a Window Class;
6) Another More Sophisticated Modularization Technique Using Function Pointers.
At program startup four windows become visible. You can grab each window with the
mouse to seperate it from the others as they'll overlap in a cascaded fashion. When
you move the mouse over any window you'll get a continuous report in each respective
window of the mouse's x/y coordinates with respect to that specific window. This
is made possible by handling the WM_MOUSEMOVE message in fnWndProc_OnMouseMove().
When you size any of the four windows you'll likewise get a report of the window's
size through processing of the WM_SIZE message. If you click the left mouse button
over any window instance you'll get the position of the click reported in the window
with TextOut() calls (as the case with the other actions). Finally, you can select
any window and type characters in it and they'll show up in that window. Actually,
its a very, very simple word processor in that regard. Finally, the program keeps
track of each instance's creation/destruction, and you'll get counts of remaining windows
left after closing each window.
The program starts out with a pretty normal WinMain(), but after registering the
"Form4" Window Class, a for loop iterates from zero to three instantiating four instances
of the "Form3" Window Class with typical CreateWindowEx() / ShowWindow() Api calls. Each
CreateWindow() call precipitates a WM_CREATE message being sent to thw Window Procedure
registered for the Window Class - fnWndProc(). So fnWndProc_OnCreate() will be called four
times - one call for each instantiation of the "Form3" Class.
On the 1st call of fnWndProc_OnCreate() the call to GetWindowLongPtr() will retrieve
a zero and store it in the iCtr variable. iCtr will then be incremented to one, and that
value will be stored back in the Window Class bytes. This operation will be repeated three
more times. In that way, we'll have a count of created/instantiated windows/instances of
the "Form3" Class.
The first thing that occurs in fnWndProc_OnCreate(), which function can be considered
a C based 'Constructor' call for an object of the Form3 Class, is that a memory allocation
occurs for however many bytes are required to store a ProgramData object. See Form3.h for
a definition of a ProgramData object. The reason such an object is necessary in this
example program relates to how Windows was designed in terms of outputing text or graphics
to a window through the WM_PAINT message. All drawing on window objects should be done
during the processing of WM_PAINT messages. Where this becomes an issue is that this
program wants to perform a continuous output of text to each respective window as the user
moves the mouse over a window, clicks the left mouse button down over a window, resizes a
window by dragging a border, or types text into a window. Windows provides this information
to the program through calls to the Window Procedure fnWndproc() where the HWND parameter
specifies the specific window of the Form3 Class where some user action is occurring, and
the MSG parameter specifies the message such as WM_MOUSEMOVE, WM_LBUTTONDOWN, WM_SIZE, WM_CHAR,
etc. The WPARAM and LPARAM parameters specify information specific to the message such as
where the mouse cursor may be at at some specific time. The ProgramData object simply persists
this data across function calls so that it may be output to a window during a WM_PAINT message.
The astute reader may wonder why I didn't make an instance of my ProgramData object global
or static to the application. I tend to not use global or static variables in my programs
if I can avoid them because I consider them to be a poor form of coding. So what I did here
is allocate enough bytes in each fnWndProc_OnCreate() call to store a ProgramData object,
and the returned pointer from the memory allocation call I stored in a pProgramData variable,
and that address I persisted within the WNDCLASSEX::cbWndExtra bytes of the registered Window
Class. Note down in WinMain() I requested these additional bytes be reserved for me by this
line...
wc.cbWndExtra = sizeof(void*);
That would give me four bytes in an x86 build or eight bytes in an x64 build. So the pointer
to the memory allocation gets stored as a member variable of the window object with a call
to SetWindowLongPtr() right after the memory allocation call. That's what C based OOP looks
like.
Finally, in fnWndProc() I store a count of created windows using GetClassLongPtr() and
SetClassLongPtr().
In terms of such procedures as fnWndProc_OnMouseMove(), fnWndProc_OnSize(), etc, these
procedures are called through my function pointer setup as seen in Form3.h. In that setup
in the Window Procedure fnWndProc() I'm using a for loop instead of switch logic to match
the incomming msg with the code to handle that specific message. This involves iterating
through an array of EVENTHANDLER objects attempting to match the incomming msg with an entry
in the array, and if a match is made between msg and EVENTHANDLER::Msg, a message handler
routine is called through its address stored in EVENTHANDLER::fnPtr. In these message handler
functions my pointer to a ProgramData() object is retrieved from the respective window's
instance data through GetWindowLongPtr() calls, and the specifics of the data which Windows
the operating system wants to convey to the program is stored in that ProgramData object,
then persisted back to the memory through SetWindowLongPtr() calls. Immediately following
that are found InvalidateRect() calls which force a WM_PAINT message. That is how the
information becomes immediately visible in each window. In general, to paint/draw in Windows,
one must store the data to be drawn in some object or variable, then call InvalidateRect()
to force a WM_PAINT message. Only in that way will the data drawn on a window 'persist'.
If you try to take the 'easy way out' and draw from some other procedure other than WM_PAINT,
you'll come to grief. The drawn text won't persist. If you minimize the window then restore
it, or move some other window over the drawn text, it will be gone! Don't do it! Do all
your drawing during WM_PAINT.
*/
// Form4.cpp
// cl Form4.cpp /O1 /Os kernel32.lib user32.lib gdi32.lib // 120,320 Bytes
// cl Form4.cpp /O1 /Os /GR- /GS- /Gy TCLib.lib kernel32.lib user32.lib gdi32.lib // 7,168 Bytes
// PATH=C:\Program Files (x86)\Embarcadero\Dev-Cpp\TDM-GCC-64\bin
//#define TCLib
#ifndef UNICODE
#define UNICODE
#endif
#ifndef _UNICODE
#define _UNICODE
#endif
#include <windows.h>
#ifdef TCLib
#include "string.h"
#include "stdio.h"
#else
#include <string.h>
#include <stdio.h>
#endif
#include "Form4.h"
LRESULT fnWndProc_OnCreate(WndEventArgs& Wea)
{
ProgramData* pProgramData = NULL;
ULONG_PTR iCtr = 0;
pProgramData=(ProgramData*)HeapAlloc // Allocate memory for a ProgramData object - see
( // Form3.h. Within this object we'll store data
GetProcessHeap(), // received from Windows in WPARAM or LPARAM para-
HEAP_ZERO_MEMORY, // meters. This data needs to be stored or
sizeof(ProgramData) // persisted because it must be output through
); // another function call - WM_PAINT.
SetWindowLongPtr // We use SetWindowLongPtr() to store this pointer
( // as part of the instantiated window's window data.
Wea.hWnd, // Room was made for it when the "Form3" Window Class
0, // was Registered in WndMain() and the WNDCLASSEX.cbWndExtra
(LONG_PTR)pProgramData // bytes were set to sizeof(void*). The ProgramData*
); // will be stored at offset zero in the .cbWndExtra bytes.
iCtr=GetClassLongPtr(Wea.hWnd,0); // Retrieve current value stored in Class bytes and
iCtr++; // increment it. Then store incremented count back
SetClassLongPtr(Wea.hWnd,0,iCtr); // in Class bytes.
return 0;
}
LRESULT fnWndProc_OnSize(WndEventArgs& Wea)
{
ProgramData* pProgramData=NULL; // This procedure will be called from fnWndProc() when
// a WM_SIZE message is received. First retrieve the
pProgramData= // ProgramData pointer fro the WNDCLASSEX::cbWndExtra bytes.
(ProgramData*)GetWindowLongPtr // The LOWORD of LPARAM will contain the horizontal size
( // of the window and the HIWORD of LPARAM will contain the
Wea.hWnd, // height of the window. The InvalidateRect() call will
0 // force a WM_PAINT message, which will cause fnWndProc_OnPaint()
); // to retrieve the data from the ProgramData object and
pProgramData->xSize=LOWORD(Wea.lParam); // paint the text to the window in a continuous updata.
pProgramData->ySize=HIWORD(Wea.lParam); // Its one mean machine!
InvalidateRect(Wea.hWnd,NULL,TRUE); //
return 0;
}
LRESULT fnWndProc_OnChar(WndEventArgs& Wea) // About the same as WM_SIZE above, except that we
{ // need to store the char sent to us in the
ProgramData* pProgramData=NULL; // LOWORD(wParam) within a char array at the correct
int iLen=0; // position. We can call strlen() to get the length
// of whatever is stored in the szBuffer char array,
pProgramData=(ProgramData*)GetWindowLongPtr(Wea.hWnd,0); // and position the insert operation to insert in
iLen=wcslen(pProgramData->szBuffer); // the next available array slot. Make sure to insert
pProgramData->szBuffer[iLen]=LOWORD(Wea.wParam); // a NULL byte directly afterwards so that it is a
pProgramData->szBuffer[iLen+1]=0; // NULL terminated char array. InvalidateRect() will
InvalidateRect(Wea.hWnd,NULL,FALSE); // then paint the new text string to the correct
// window.
return 0;
}
LRESULT fnWndProc_OnMouseMove(WndEventArgs& Wea) // About the same as the last two. You should know
{ // the drill by now. Retrieve the ProgramData pointer
ProgramData* pProgramData=NULL; // from the WNDCLASSEX::cbWndExtra bytes, copy the
// WPARAM info to it, then call InvalidateRect() to
pProgramData=(ProgramData*)GetWindowLongPtr(Wea.hWnd,0); // force a WM_PAINT message, which message will draw
pProgramData->xMouse=LOWORD(Wea.lParam); // all the data stored in the ProgramData object to the
pProgramData->yMouse=HIWORD(Wea.lParam); // correct window, i.e., the window with which the user
InvalidateRect(Wea.hWnd,NULL,TRUE); // is interacting.
return 0;
}
LRESULT fnWndProc_OnLButtonDown(WndEventArgs& Wea) // Same as message handlers above.
{
ProgramData* pProgramData=NULL;
pProgramData=(ProgramData*)GetWindowLongPtr(Wea.hWnd,0);
pProgramData->xButton=LOWORD(Wea.lParam);
pProgramData->yButton=HIWORD(Wea.lParam);
InvalidateRect(Wea.hWnd,NULL,FALSE);
return 0;
}
LRESULT fnWndProc_OnPaint(WndEventArgs& Wea)
{
ProgramData* pProgramData=NULL;
wchar_t szXMouse[8];
wchar_t szYMouse[8];
wchar_t szXSize[8];
wchar_t szYSize[8];
wchar_t szString[32];
PAINTSTRUCT ps;
HDC hDC=NULL;
hDC=BeginPaint(Wea.hWnd,&ps);
pProgramData=(ProgramData*)GetWindowLongPtr(Wea.hWnd,0);
swprintf(szXMouse,L"%d",pProgramData->xMouse);
swprintf(szYMouse,L"%d",pProgramData->yMouse);
wcscpy(szString,L"xMouse=");
wcscat(szString,szXMouse);
wcscat(szString,L" yMouse=");
wcscat(szString,szYMouse);
TextOut(hDC,0,0,szString,wcslen(szString));
if(pProgramData->xButton||pProgramData->yButton)
{
wchar_t szXBtn[8];
wchar_t szYBtn[8];
swprintf(szXBtn,L"%d",pProgramData->xButton);
swprintf(szYBtn,L"%d",pProgramData->yButton);
wcscpy(szString,L"xButton=");
wcscat(szString,szXBtn);
wcscat(szString,L" yButton=");
wcscat(szString,szYBtn);
TextOut(hDC,pProgramData->xButton+12,pProgramData->yButton,szString,wcslen(szString));
pProgramData->xButton=0, pProgramData->yButton=0;
}
swprintf(szXSize,L"%d",pProgramData->xSize);
swprintf(szYSize,L"%d",pProgramData->ySize);
wcscpy(szString,L"Width=");
wcscat(szString,szXSize);
wcscat(szString,L" Height=");
wcscat(szString,szYSize);
TextOut(hDC,0,40,szString,wcslen(szString));
TextOut(hDC,0,20,pProgramData->szBuffer,wcslen(pProgramData->szBuffer));
EndPaint(Wea.hWnd,&ps);
return 0;
}
LRESULT fnWndProc_OnDestroy(WndEventArgs& Wea)
{
ProgramData* pProgramData=NULL;
wchar_t szBuffer[128];
ULONG_PTR iCtr;
pProgramData=(ProgramData*)GetWindowLongPtr(Wea.hWnd,0);
HeapFree(GetProcessHeap(),0,pProgramData);
iCtr=GetClassLongPtr(Wea.hWnd,0);
iCtr--;
switch(iCtr)
{
case 0:
MessageBox(Wea.hWnd,L"No More Windows Left! Will Call PostQuitMessage()!",L"Time To Go!",MB_OK);
PostQuitMessage(0);
break;
case 1:
MessageBox(Wea.hWnd,L"One More Window Left!",L"Getting Near The End!",MB_OK);
break;
case 2:
MessageBox(Wea.hWnd,L"Still Two Window Left!",L"Not Out Of Windows Yet!",MB_OK);
break;
case 3:
MessageBox(Wea.hWnd,L"Still Got A Lot Of Windows Left!",L"Not Out Of Windows Yet!",MB_OK);
break;
}
SetClassLongPtr(Wea.hWnd,0,(LONG_PTR)iCtr);
return 0;
}
LRESULT __stdcall fnWndProc(HWND hwnd, unsigned int msg, WPARAM wParam,LPARAM lParam)
{
WndEventArgs Wea; // This procedure loops through the EVENTHANDER struct array
// to try to make a match with the msg parameter of the WndProc.
for(size_t i=0; i<dim(EventHandler); i++) // If a match is made the event handling procedure is called
{ // through a function pointer - (EventHandler[i].fnPtr). If no
if(EventHandler[i].Msg==msg) // match is found the msg is passed onto DefWindowProc().
{
Wea.hWnd=hwnd, Wea.lParam=lParam, Wea.wParam=wParam;
return (*EventHandler[i].fnPtr)(Wea);
}
}
return (DefWindowProc(hwnd, msg, wParam, lParam));
}
int WINAPI WinMain(HINSTANCE hIns, HINSTANCE hPrevIns, LPSTR lpszArgument, int iShow)
{
wchar_t szClassName[]=L"Form4";
wchar_t szCaption[80];
WNDCLASSEX wc;
MSG messages;
HWND hWnd;
wc.lpszClassName = szClassName, wc.lpfnWndProc = fnWndProc;
wc.cbSize = sizeof (WNDCLASSEX), wc.style = CS_HREDRAW|CS_VREDRAW;
wc.hIcon = LoadIcon(NULL,IDI_APPLICATION), wc.hInstance = hIns;
wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION), wc.hCursor = LoadCursor(NULL,IDC_ARROW);
wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH), wc.cbWndExtra = sizeof(void*);
wc.lpszMenuName = NULL, wc.cbClsExtra = sizeof(void*);
RegisterClassEx(&wc);
wcscpy(szCaption,L"Try Typing Text, Moving Mouse, Clicking Left Mouse Button, Or Sizing Window!");
for(size_t i=0; i<4; i++)
{
hWnd=CreateWindowEx(0,szClassName,szCaption,WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,CW_USEDEFAULT,600,400,HWND_DESKTOP,0,hIns,0);
ShowWindow(hWnd,iShow);
}
while(GetMessage(&messages,NULL,0,0))
{
TranslateMessage(&messages);
DispatchMessage(&messages);
}
return messages.wParam;
}
And here is Form4.h...
//Form4.h
#ifndef Form4_h
#define Form4_h
#define dim(x) (sizeof(x) / sizeof(x[0]))
struct WndEventArgs
{
HWND hWnd;
WPARAM wParam;
LPARAM lParam;
HINSTANCE hIns;
};
struct EVENTHANDLER
{
unsigned int Msg;
LRESULT (*fnPtr)(WndEventArgs&);
};
LRESULT fnWndProc_OnCreate (WndEventArgs& Wea);
LRESULT fnWndProc_OnSize (WndEventArgs& Wea);
LRESULT fnWndProc_OnChar (WndEventArgs& Wea);
LRESULT fnWndProc_OnMouseMove (WndEventArgs& Wea);
LRESULT fnWndProc_OnLButtonDown (WndEventArgs& Wea);
LRESULT fnWndProc_OnPaint (WndEventArgs& Wea);
LRESULT fnWndProc_OnDestroy (WndEventArgs& Wea);
const EVENTHANDLER EventHandler[]=
{
{WM_CREATE, fnWndProc_OnCreate},
{WM_SIZE, fnWndProc_OnSize},
{WM_CHAR, fnWndProc_OnChar},
{WM_MOUSEMOVE, fnWndProc_OnMouseMove},
{WM_LBUTTONDOWN, fnWndProc_OnLButtonDown},
{WM_PAINT, fnWndProc_OnPaint},
{WM_DESTROY, fnWndProc_OnDestroy}
};
struct ProgramData
{
short int xMouse;
short int yMouse;
short int xSize;
short int ySize;
short int xButton;
short int yButton;
wchar_t szBuffer[128];
};
#endif