Author Topic: Win32 SDK Tutorial - Form4 - Windows Messages, Instance Data, SetClassLongPtr()  (Read 2037 times)

Frederick Harris

  • Newbie
  • *
  • Posts: 47
/*
  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. 
*/

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

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


« Last Edit: October 14, 2021, 09:50:03 pm by Frederick Harris »