Author Topic: Win32 SDK Tutorial - Form2 - Child Window Controls And Window Procedures  (Read 486 times)

Frederick Harris

  • Newbie
  • *
  • Posts: 47
/*
   Form2.cpp  -- Child Window Controls And Window Procedures
   
   In Form1.cpp we registered a Window Class in WinMain() with the RegisterClassEx() Windows Api
   function, which takes a single argument of a WNDCLASSEX object.  Immediately afterward we
   instantiated an object of the Class with the CreateWindowEx() function, and that function call
   created our main window.  In Form2.cpp we'll do the same to create our main application window,
   but we'll make additional CreateWindowEx() calls to illustrate the creation of 'child window
   controls'.  Specifically, we'll create an "edit" control and a "button" control as childs of
   our main program window.  The user can enter text in the edit control, or not, and when the
   button is pressed, we'll retrieve the text, if any, from the edit control, and display it in a
   Message Box.

   Note in the code below the Window Procedure fnWndProc() is very different from the Form1 project.
   Graphical User Interface (GUI) programs for any operating system utilize a 'message driven
   architecture'.  What that means is that the operating system monitors all user interactions
   with the running program, and 'calls back' into the program's code to inform that code of what
   the user is doing, or of system events which might be of interest to the running program.  It
   can do this because it has a pointer to the function the program's author provided as part of
   the WNDCLASSEX::lpfnWndProc structure/class.  That would be our fnWndProc() function below.
   Note that unlike with Form1 whose if statement tested for a WM_DESTROY message only in it's
   Window Procedure, in Form2 we are using a switch logic construct to map incomming messages
   received in the 2nd unsigned int parameter of the WndProc, to the code or 'message handlers'
   that will handle that specific message.       

   The two additional messages that our Form2 Window Proicedure handles that are not seen in Form1
   are the WM_CREATE message, and the WM_COMMAND message.  The WM_CREATE message is kind of special
   in that it will only ever be received one time at program start up.  It is actually a C based
   object constructor call, and is actually running 'inside' the CreateWindow() call in WinMain()
   that creates the main program window.  We'll prove that in the next version of this program.  In
   this particular program you'll see two more CreateWindow() calls in the WM_CREATE handler code
   whose purpose is to create the two child window controls as described just above, i.e., an
   'edit' control, and a 'button' control.  Note that unlike our CreateWindow() call in WinMain()
   that created the main program window, we didn't register any edit or button classes in this app
   as we did with our Form2 class using RegisterClassEx() in WinMain().  Rather, we're using
   pre-defined operating system classes that are referred to as 'controls', or, more specifically,
   as Windows 'Standard Controls'.  Among this group would be edit controls, buttons, combo-boxes,
   list boxes, and labels.  This is a rather elegant design.  All GUI objects in Windows are windows
   created by one function call which is the CreateWindow() or CreateWindowEx() call.  The specific
   type of window or the 'class' of the window is specified by the szClassName parameter of the
   call.

   The 2nd new message we've encountered here is the WM_COMMAND message.  That is how 'child' window
   controls communicate with their parent.  Count across to the 9th parameter of any of the 
   CreateWindowEx() calls in Form2 - either in WinMain() where we created our main program window,
   or in WM_CREATE handler code where we created the two child window controls, and you'll find
   the 'parent' of that respective window.  The parent of the main program window is specified as
   the desktop, which is in some ways like having no parent at all.  But for the edit and button
   child window controls their parent is specified as being the main program window.  That's where
   Windows the operating system will send WM_COMMAND messages if the user of the running program
   interacts with the child window control in any way, for example, in the case of a button,
   clicking it.

   All of which brings us to the WPARAM and LPARAM parameters of the Window Procedure.  It is
   through these two parameters that Windows informs the program of which child window control the
   message applies to, and what specifically the user did with the control.  These are 'composite'
   parameters, and the information is packed rather tightly inside them.  In x64 code they are 8
   byte entities, and the lower four bytes of WPARAM is the control identifier of the control, and
   the upper four bytes is an equate/define in the Windows header files which specifies the specific
   'notification' message of what the user did with the control.  Note that the control identifier
   is the 10th parameter of the CreateWindowEx() call used to create the control.  In the case of
   the button below it is the #define IDC_BUTTON 1500 seen right below the program includes.  If you
   do an MSDN search on BN_CLICKED you'll come up with this....

   https://docs.microsoft.com/en-us/windows/win32/controls/bn-clicked

   So in the WM_COMMAND handler code below I'm hoping my explanation above will allow the reader to
   make sense out of my if statement where I'm testing to see if an incomming Windows message
   contains the Control Id of our button, and if the notificaation is that the user clicked it.  If
   the user did, the LPARAM will contain the HWND (Window Handle) of the button.  As an aside, it is
   for reasons such as this that it is never necessary to have global variables in Windows programs. 
   All the information the coder will ever need is contained within the parameters of the various
   functions which one interacts with in Windows coding.

   As an example of the lack of necessity for global variables in Windows SDK coding, check out what
   code actually executes inside the if statement described above in the WM_COMMAND handler code if
   the user actually did click the button to extract the text from the edit control.  The Windows
   Api function to extract text from a control (any control) is GetWindowText().  Look it up on
   MSDN.  To use that function we need the HWND (Window Handle) of the control, a pointer to a
   memory buffer large enough to copy the text to, and the actual size of that buffer.  When we
   created the edit control in the WM_CREATE handler code we didn't save or persist the returned
   handle of the edit control - in fact, I purposly threw it away just to prove this point of not
   needing global variables.  But we need it here to extract text from the edit control if the user
   typed any.  How will we get it?  It was lost! 

   The GetDlgItem() Windows Api function returns the HWND of a control given the Control Id of the
   control, and the HWND of the control's parent - which we both have.  Therefore, while there might
   be a bit of excessive concision in my code, this line retrieves into szBuffer whatever the user
   typed into the edit control....

   GetWindowText(GetDlgItem(hWnd,IDC_EDIT),szBuffer,256);
 
   Finally, a MessageBox() presents that information to the user.  Pretty simple program really, but
   it elucidates critically important processes in Windows Api SDK style coding.  Important points
   the reader should grasp from this simple program is the way the CreateWindow() function call
   creates objects in tandem with WM_CREATE handler code, the switch construct to map incomming
   Windows messages to the code which handles that message, and the way child window controls are
   created and how interactions with them are reported to a program through WM_COMMAND messages.
*/

Code: [Select]
// cl Form2.cpp /O1 /Os /link Kernel32.lib User32.lib                                // 91,648 Bytes VC19, x64, UNICODE LibCmt.lib
// g++ Form2.cpp -luser32 -lkernel32 -oForm2_gcc.exe -mwindows -m64 -s -Os           // 19,968 Bytes GCC 9.2.0, x64, UNICODE
// cl Form2.cpp /O1 /Os /GS- /Gy /link TCLib.lib user32.lib                          //  4,608 Bytes VC19, x64, UNICODE, TCLib.lib
#ifndef UNICODE
   #define UNICODE
#endif
#ifndef _UNICODE
   #define _UNICODE
#endif   
#include <windows.h>
#define  IDC_BUTTON 1500   // Constant Numeric Identifier For Child Window "Retrieve Text" Button
#define  IDC_EDIT   1505   // Constant Numeric Identifier For Child Window Edit Control


LRESULT CALLBACK fnWndProc(HWND hWnd, unsigned int msg, WPARAM wParam, LPARAM lParam)
{
 switch(msg)                                               
 {                                                         
   case WM_CREATE: // WM_CREATE Message Received One Time and Is A C Based Object Constructor Call.  This code Actually Runs
    {              // 'Inside' The CreateWindowEx() Call Down In WinMain() Which Creates The Main App Window.  When Zero Is                                                       
        HINSTANCE hIns = NULL;                     // Returned Just Below, The CreateWindowEx() Call In WinMain() terminates,
        hIns=((LPCREATESTRUCT)lParam)->hInstance;  // and Execution Of ShowWindow() Occurs.
        CreateWindowEx(WS_EX_CLIENTEDGE,L"edit",L"",WS_CHILD|WS_VISIBLE,50,40,200,25,hWnd,(HMENU)IDC_EDIT,hIns,0);
        CreateWindowEx(0,L"button",L"Retrieve Text",WS_CHILD|WS_VISIBLE,95,90,120,30,hWnd,(HMENU)IDC_BUTTON,hIns,0);
        return 0;
    }   
   case WM_COMMAND:  // Child Windows (edit and button control in this app) Communicate With Their Parent (Main App Window) By                                     
    {                // Sending WM_COMMAND Messages To Their Parent.  Message Specifics (What The User Did With The Control)
        wchar_t szBuffer[256];   // Are Packaged Within The WPARAM And LPARAM Parameters Of The Window Procedure.                                       
        if(LOWORD(wParam)==IDC_BUTTON && HIWORD(wParam)==BN_CLICKED) 
        {                                                             
           GetWindowText(GetDlgItem(hWnd,IDC_EDIT),szBuffer,256); // GetDlgItem() Retrieves The HWND Of A Child Window Control
           MessageBox(hWnd,szBuffer,L"Button Click",MB_OK);       // Given Its Parent And Constant Numeric Define.  GetWindowText()   
        }                                                         // Retrieves The Text From A Control.   
        return 0;  
    }
   case WM_DESTROY:                                         
    {                          // When PostQuitMessage() Just Left Executes, The Message Pump Down In WinMain()
        PostQuitMessage(0);    // Terminates And The App Ends.                                                   
        return 0;   
    }
 }                                                           

 return (DefWindowProc(hWnd, msg, wParam, lParam)); // Default Processing For Messages Not Handled Here.
}


int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevIns, LPSTR lpszArgument, int iShow)
{
 wchar_t szClassName[] = L"Form2";             
 HWND hWnd             = NULL;                             
 WNDCLASSEX wc;                                 
 MSG messages;                                 
                                               
 memset(&wc,0,sizeof(wc));                                         
 wc.lpszClassName = szClassName;               
 wc.lpfnWndProc   = fnWndProc;                 
 wc.cbSize        = sizeof(WNDCLASSEX);         
 wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);   
 wc.hInstance     = hInstance;                 
 RegisterClassEx(&wc);                         
 hWnd=CreateWindowEx(0,szClassName,szClassName,WS_OVERLAPPEDWINDOW,200,175,320,200,HWND_DESKTOP,0,hInstance,0);
 ShowWindow(hWnd,iShow);                       
 while(GetMessage(&messages,NULL,0,0))         
 {                                       
    TranslateMessage(&messages);         
    DispatchMessage(&messages);         
 }
                                         
 return (int)messages.wParam;
}
« Last Edit: October 14, 2021, 09:51:47 pm by Frederick Harris »

Frederick Harris

  • Newbie
  • *
  • Posts: 47
Re: Win32 SDK Tutorial - Form2 - Child Window Controls And Window Procedures
« Reply #1 on: November 28, 2021, 03:59:52 am »
/*
   Form2a.cpp
   
   In Form1.cpp we registered a Window Class in WinMain() with the RegisterClassEx() Windows Api
   function, which takes a single argument of a WNDCLASSEX object.  Immediately afterward we
   instantiated an object of the Class with the CreateWindowEx() function, and that function call
   created our main window.  In Form2a.cpp we'll do the same to create our main application window,
   but we'll make additional CreateWindowEx() calls to illustrate the creation of 'child window
   controls'.  Specifically, we'll create an "edit" control and a "button" control as childs of
   our main program window.  The user can enter text in the edit control, or not, and when the
   button is pressed, we'll retrieve the text, if any, from the edit control, and display it in a
   Message Box.

   Two things.  First, note I have a #Debug preprocessor directive in the code.  If #Debug isn't
   commented out, an Output.txt log file is created in the app's directory.  Here is that output
   from a program run...   

   Entering WinMain()
     Right Before CreateWindow() Call In WinMain() - << Text From WinMain()...

     Entering fnWndProc_OnCreate()
       hWnd = 0x00000000000904E6
     Leaving fnWndProc_OnCreate()

     hWnd = 0x00000000000904E6 - << Text From WinMain()
     Right After CreateWindow() Call In WinMain() - << Text From WinMain()

     Entering fnWndProc_OnCommand()
       LOWORD(wParam) = 1500
     Leaving fnWndProc_OnCreate()

     Entering fnWndProc_OnClose()
       hWnd = 0x00000000000904E6
       Entering fnWndProc_OnDestroy()
         hWnd = 0x00000000000904E6
       Leaving fnWndProc_OnDestroy()
     Leaving fnWndProc_OnClose()
   Leaving WinMain()

  Note carefully that at the point of the CreateWindowEx() call in WinMain() to create the app's
  main program window, we get the debug output that code execution is in fnWndProc_OnCreate(). 
  That function call is actually occurring 'inside' the CreateWindowEx() call!  What you are seeing
  here is a C based object Constructor call.  In fnWndProc_OnCreate() we get the main app's HWND
  before it is revealed to us by the #Debug output statement in WinMain().  With this logic we see
  the symmetric C based object Destructor call in fnWndProc_OnDestroy().
 
  Second, you are seeing a truely elegant code design here in terms of the Windows Api.  One
  function - CreateWindowEx(), is used to create every type of window Windows supports.  In
  WinMain() we Register a 'custom' Window Class named "Form2a" with which we base our main program
  window.  We feed that name into the CreateWindowEx() call in its second parameter - szClassName.
  In fnWndProc_OnCreate() we see two more CreateWindowEx() calls, but neither of those calls
  contain in the second parameter a Class Name we Registered in the app.  The 1st CreateWindowEx()
  call contains in its 2nd parameter the string "edit".  The 2nd CreateWindowEx() call asks for
  the instantiation of an object of class "button".  We never Registered an "edit" or "button"
  object in the "Form2" app.  Think about it.  What's going on here?

  What's going on here is that Windows the operating system has predefined classes of specialized
  'window' objects that can be instantiated in application programs by CreateWindow() or
  CreateWindowEx() function calls.  There are several categories of these objects or 'controls'.
  Edit Controls and button controls are in the category known as the 'Standard Controls'.  Then
  there are the 'Common Controls'.  These are a bit more complex to use and they are quite
  sophisticated.  Then there are ActiveX Controls.  With those the CreateWindow() calls are actually
  encapsulated within the control and the user creates them indirectly through the COM (Component
  Object Model) subsystem.  Finally, one can create controls oneself and these would be 'Custom
  Controls' - a somewhat advanced topic, as are ActiveX Controls. But the truly elegant part of
  this code design of the Windows Api is that one function call - CreateWindow(), is used to create
  all these very different types of windows.  Below is Form2a.cpp....   
*/

Code: [Select]
// cl Form2a.cpp /O1 /Os /link Kernel32.lib User32.lib                       // 91,648 Bytes
// g++ Form2a.cpp -luser32 -lkernel32 -oForm2_gcc.exe -mwindows -m64 -s -Os  // 17,920 Bytes;
// cl Form2a.cpp /O1 /Os /GS- /Gy /link TCLib.lib kernel32.lib user32.lib    //  4,608 Bytes
//#define TCLib
//#define Debug
#include <windows.h>
#ifdef TCLib
  #include "stdio.h"
  #include "tchar.h"
#else
  #include <cstdio>
  #include <tchar.h>
#endif
#define  IDC_BUTTON 1500
#define  IDC_EDIT   1505
#ifdef Debug
  FILE* fp=NULL;
#endif


LRESULT CALLBACK fnWndProc_OnCreate(HWND hWnd, unsigned int msg, WPARAM wParam, LPARAM lParam)
{
 HINSTANCE hIns = NULL;    // This is a C - based Constructor Function whose code execution will be occurring 
                           // 'inside' the CreateWindowEx() call down in WinMain().  Unlike a C++ language
 #ifdef Debug              // Constructor, failure is a possibility.  If -1 is returned by this function, the
 fprintf(fp,"  Entering fnWndProc_OnCreate()\n");  // CreateWindowEx() function in WinMain() will fail and return
 fprintf(fp,"    hWnd   = 0x%p\n",hWnd);           // a NULL for its HWND.  Just below we'll make two CreateWindowEx()
 #endif                                            // calls for two 'Child Window Controls'.  The code just at left
 hIns=((LPCREATESTRUCT)lParam)->hInstance;         // retrieves the HINSTANCE from the CREATESTRUCT pointed to by LPARAM.
 CreateWindowEx(WS_EX_CLIENTEDGE,_T("edit"),_T(""),WS_CHILD|WS_VISIBLE,50,40,200,25,hWnd,(HMENU)IDC_EDIT,hIns,0);
 CreateWindowEx(0,_T("button"),_T("Retrieve Text"),WS_CHILD|WS_VISIBLE,95,90,120,30,hWnd,(HMENU)IDC_BUTTON,hIns,0);
 #ifdef Debug
 fprintf(fp,"  Leaving fnWndProc_OnCreate()\n\n");
 #endif

 return 0;
}


LRESULT CALLBACK fnWndProc_OnCommand(HWND hWnd, unsigned int msg, WPARAM wParam, LPARAM lParam)
{
 TCHAR szBuffer[256];                                          // Child Window Controls communicate with their parent
                                                               // by sending WM_COMMAND messages.  Packed tightly within
 if(LOWORD(wParam)==IDC_BUTTON && HIWORD(wParam)==BN_CLICKED)  // the WPARAM and LPAREM parameters is information the
 {                                                             // parent (our main app window) can use to determine which
    #ifdef Debug                                               // child window control is sending the message, and what
    fprintf(fp,"  Entering fnWndProc_OnCommand()\n");          // the nature of the user's interaction with the child
    fprintf(fp,"    LOWORD(wParam) = %u\n",LOWORD(wParam));    // window control was that triggered the sending of the
    #endif                                                     // message, e.g., the user clicked a button or typed a
    GetWindowText(GetDlgItem(hWnd,IDC_EDIT),szBuffer,256);     // character in an edit control.  Note that the GetDlgItem()
    MessageBox(hWnd,szBuffer,_T("Button Click"),MB_OK);        // function returns the HWND of a child window given the
    #ifdef Debug                                               // HWND of the parent and the Control ID of the child window
    fprintf(fp,"  Leaving fnWndProc_OnCommand()\n\n");         // control as specified in the HMENU parameter of the
    #endif                                                     // CreateWindow() call that created the control.  Its never
 }                                                             // necessary to use global variables in Windows programs to
                                                               // persist HWNDs because Windows will always be able to
 return 0;                                                // return these to us through a GetDlgItem() function call.
}                                                              // Windows keeps track of all sorts of stuff like this.


LRESULT CALLBACK fnWndProc_OnClose(HWND hWnd, unsigned int msg, WPARAM wParam, LPARAM lParam)
{
 #ifdef Debug                                                  // The WM_CLOSE message provides the application the opportunity
 fprintf(fp,"  Entering fnWndProc_OnClose()\n");               // of querying the user for confirmation that the user indeed
 fprintf(fp,"    hWnd = 0x%p\n",hWnd);                         // wants to exit the application.  In this app all I did with the
 #endif                                                        // message was call DestroyWindow().  Note the Window Procedure
 DestroyWindow(hWnd);                                          // is 'reentrant'.  fnWndProc_OnDestroy() will execute within
 #ifdef Debug                                                  // fnWndProc_OnClose().  This is clearly seen in the Output.txt
 fprintf(fp,"  Leaving fnWndProc_OnClose()\n");                // log file if #Debug is active/defined, i.e., not commented out.
 #endif 
 
 return 0;   
}


LRESULT CALLBACK fnWndProc_OnDestroy(HWND hWnd, unsigned int msg, WPARAM wParam, LPARAM lParam)
{
 #ifdef Debug                                                  // This is a C - Based Destructor Call for our main program
 fprintf(fp,"    Entering fnWndProc_OnDestroy()\n");           // window.  In more complex programs than this, it is a good
 fprintf(fp,"      hWnd = 0x%p\n",hWnd);                       // place to make sure memory allocations are released, databases
 #endif                                                        // or files are closed, etc.  Here, all we need to do is call
 PostQuitMessage(0);                                           // PostQuitMessage() to stop the 'message pump' in WinMain()
 #ifdef Debug                                                  // from running so that the program can end.
 fprintf(fp,"    Leaving fnWndProc_OnDestroy()\n");
 #endif 
 
 return 0;   
}


LRESULT CALLBACK fnWndProc(HWND hWnd, unsigned int msg, WPARAM wParam, LPARAM lParam)
{
 switch(msg)                                                // I don't personally much care for placing a lot of code
 {                                                          // in 'case' constructs under a switch statement.  So at
   case WM_CREATE:                                          // left you see I created seperate 'message handling functions'
     return fnWndProc_OnCreate(hWnd, msg, wParam, lParam);  // to put actual code to be executed for each message to be
   case WM_COMMAND:                                         // handled.  The idea illustrated here is pretty simple; in  my
     return fnWndProc_OnCommand(hWnd, msg, wParam, lParam); // actual production code I don't use switch logic but rather
   case WM_CLOSE:                                           // for loop logic to iterate through a struct array containing
     return fnWndProc_OnClose(hWnd, msg, wParam, lParam);   // the message and a function pointer to the message handling
   case WM_DESTROY:                                         // function that handles that specific message. We'll illustrate
     return fnWndProc_OnDestroy(hWnd, msg, wParam, lParam); // that technique in Form2b.cpp and Form2b.h (Next Program).
 }                                                         

 return (DefWindowProc(hWnd, msg, wParam, lParam));
}


int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevIns, LPSTR lpszArgument, int iShow)
{
 TCHAR szClassName[] =_T("Form2a");             // Program Entry Point for Windows GUI app.  I personally like to keep my
 HWND hWnd           = NULL;                    // WinMain() functions short.  The only code I put in them is code to
 WNDCLASSEX wc       = {0};                     // Register my main app window, the CreateWindowEx() call to create that window,
 MSG messages;                                  // and the 'Message Pump' that keeps the program alive and functioning.  If
                                                // the app instantiates multiple top level windows, I put the code to register
 #ifdef Debug                                   // those additional classes in the WM_CREATE handler of the main app's window.
 fp=fopen("Output.txt","w");                    // After all, that is the 'Constructor' for the entire app. 
 fprintf(fp,"Entering WinMain()\n");
 #endif
 wc.lpszClassName = szClassName;                // Start filling out WNDCLASSEX object....
 wc.lpfnWndProc   = fnWndProc;                  // Function Pointer to Window Procedure
 wc.cbSize        = sizeof(WNDCLASSEX);         // Size of WNDCLASSEX object
 wc.hbrBackground = (HBRUSH)COLOR_BTNSHADOW;    // Color for background of main app window
 wc.hInstance     = hInstance;                  // Virtual Memory Address Thing
 RegisterClassEx(&wc);                          // Register Custom Window Class
 #ifdef Debug
 fprintf(fp,"  Right Before CreateWindow() Call In WinMain() - << Text From WinMain()...\n\n");
 #endif
 hWnd=CreateWindowEx(0,szClassName,szClassName,WS_OVERLAPPEDWINDOW,200,175,320,200,HWND_DESKTOP,0,hInstance,0);  // C Based Object Constructor
 #ifdef Debug
 fprintf(fp,"  hWnd = 0x%p - << Text From WinMain()\n",hWnd);
 fprintf(fp,"  Right After CreateWindow() Call In WinMain() - << Text From WinMain()\n\n");
 #endif
 ShowWindow(hWnd,iShow);                        // Show Window
 while(GetMessage(&messages,NULL,0,0))          // Message Pump
 {                                       
    TranslateMessage(&messages);         
    DispatchMessage(&messages);         
 }
 #ifdef Debug
 fprintf(fp,"Leaving WinMain()\n");
 fclose(fp);
 #endif
                                         
 return messages.wParam;
}

Frederick Harris

  • Newbie
  • *
  • Posts: 47
Re: Win32 SDK Tutorial - Form2 - Child Window Controls And Window Procedures
« Reply #2 on: November 28, 2021, 04:12:10 am »
#if 0
Form2b.cpp – Child Window Controls

With this version of Form2 , first, we'll create an "edit" control and a "button" control as childs of our main
program window.  The user can enter text in the edit control, or not, and when the button is pressed, we'll retrieve
the text, if any, from the edit control, and display it in a Message Box.  Second, we'll implement a WM_CLOSE
handler function which queries the user with a MessageBox as to whether or not the application is to be terminated. 
Finally, we’ll implement a ‘message cracker’ scheme which shrinks our Window Procedure down to a constant 15 lines
of code - a number which will never change no matter how many Windows messages are handled, and no matter how much
code it takes to handle the messages.
#endif

Code: [Select]
// cl Form2b.cpp /O1 /Os /link Kernel32.lib User32.lib                       // 91,648 Bytes; VC19 LibCmt Linkage
// cl Form2b.cpp /O1 /Os /GS- /link TCLib.lib user32.lib                     //  5,120 Bytes; VC19 TCLib Linkage
// g++ Form2b.cpp -luser32 -lkernel32 -oForm2_gcc.exe -mwindows -m64 -s -Os  // 17,920 Bytes; GCC 9 Series
//#define TCLib
#ifndef UNICODE
   #define UNICODE
#endif
#ifndef _UNICODE
   #define _UNICODE
#endif
#include <windows.h>
#ifdef TCLib
  #include "stdio.h"
#else
  #include <cstdio>
#endif
#include "Form2b.h"

/*-------------------------LRESULT CALLBACK fnWndProc_OnCreate(WndEventArgs& Wea)----------------------------------
This is a C - based object constructor function whose code execution will be occurring 'inside' the CreateWindowEx()
function call down in WinMain().  As such, only upon successful return of this function (return 0) will a HWND be
assigned to the HWND variable in WinMain().  Unlike a C++ language constructor, failure is a possibility.  If -1 is
returned by this function, the CreateWindowEx() call down in WinMain() will fail and return  a NULL for it's HWND.
During a WM_CREATE message, the LPARAM parameter is a pointer to a CREATESTRUCT object.  The HINSTANCE of the module
can be retrieved from the object as shown in the code below. The two CreateWindowEx() calls below create 'sub-objects',
i.e., 'child window controls', whose parent is 'this', i.e., the main application window created in WinMain().  They
are, specifically, an "edit" control, and a "button" control.     
-------------------------------------------------------------------------------------------------------------------*/
LRESULT CALLBACK fnWndProc_OnCreate(WndEventArgs& Wea)
{                         
 CREATESTRUCT* pCreateStruct = NULL;
 HINSTANCE     hIns          = NULL;                 
 
 pCreateStruct = (CREATESTRUCT*)Wea.lParam; // LPARAM In WM_CREATE Message Handler Is Pointer To CREATESTRUCT Object                                     
 Wea.hIns=pCreateStruct->hInstance;         // HINSTANCE Is One Of The Members Of CREATESTRUCT Object
 CreateWindowEx(WS_EX_CLIENTEDGE,L"edit",L"",WS_CHILD|WS_VISIBLE,50,40,200,25,Wea.hWnd,(HMENU)IDC_EDIT,Wea.hIns,0);   
 CreateWindowEx(0,L"button",L"Retrieve Text",WS_CHILD|WS_VISIBLE,95,90,120,30,Wea.hWnd,(HMENU)IDC_BUTTON,Wea.hIns,0);

 return 0;
}


/*--------------------------LRESULT CALLBACK fnWndProc_OnCommand(WndEventArgs& Wea)-------------------------------
Child Window Controls communicate with their parent by sending WM_COMMAND messages.  Packed tightly within the
WPARAM and LPAREM parameters is information the parent (our main app window) can use to determine which child window
control is sending the message, and what the nature of the user's interaction with the child window control was that
triggered the sending of the message, e.g., the user clicked a button or typed a character in an edit control.  Note
that the GetDlgItem() function returns the HWND of a child window given the HWND of the parent and the Control ID of
the child window control as specified in the HMENU parameter of the CreateWindow() call that created the control.  I
realize that statement will take you some time to digest!  Its never necessary to use global variables in Windows
programs to persist HWNDs because Windows will always be able to return these to us through a GetDlgItem() function
call.  Windows keeps track of all sorts of stuff like this.
------------------------------------------------------------------------------------------------------------------*/
LRESULT CALLBACK fnWndProc_OnCommand(WndEventArgs& Wea)
{
 wchar_t szBuffer[256];                                         
                                                               
 if(LOWORD(Wea.wParam)==IDC_BUTTON && HIWORD(Wea.wParam)==BN_CLICKED) 
 {                                                             
    GetWindowText(GetDlgItem(Wea.hWnd,IDC_EDIT),szBuffer,256); // GetDlgItem() Returns HWND of edit control IDC_EDIT   
    MessageBox(Wea.hWnd,szBuffer,L"Button Click",MB_OK);       
 }                                                             
                                                               
 return 0;                                                    
}


/*-----------------------------LRESULT CALLBACK fnWndProc_OnClose(WndEventArgs& Wea)------------------------------
The WM_CLOSE message provides the application the opportunity of querying the user for confirmation that the user
indeed wants to exit the application.  In this app I put up a MessageBox() suitably configured to ask that question
and respond appropriately to a click of the 'Yes' button by calling DestroyWindow()  Note the Window Procedure is
'reentrant' - fnWndProc_OnDestroy() will execute within fnWndProc_OnClose().
-----------------------------------------------------------------------------------------------------------------*/
LRESULT CALLBACK fnWndProc_OnClose(WndEventArgs& Wea)
{
 if(MessageBox(Wea.hWnd,L"Do You Wish To Close The App?",L"Close Out?",MB_YESNO|MB_ICONQUESTION)==IDYES)
    DestroyWindow(Wea.hWnd);                                         
 return 0;   
}


/*------------------------------LRESULT CALLBACK fnWndProc_OnDestroy(WndEventArgs& Wea)---------------------------
This is a C - Based Destructor Call for our main program window.  In more complex programs than this, it is a good
place to make sure memory allocations are released, databases or files are closed, etc.  Here, all we need to do is
call PostQuitMessage() to stop the 'message pump' in WinMain() from running so that the program can drop out of the
message loop and end.
-----------------------------------------------------------------------------------------------------------------*/
LRESULT CALLBACK fnWndProc_OnDestroy(WndEventArgs& Wea)
{
 PostQuitMessage(0);                                           
 return 0;   
}


/*-----------LRESULT CALLBACK fnWndProc(HWND hWnd, unsigned int msg, WPARAM wParam, LPARAM lParam)-----------------
The Window Procedure is the most important concept in Windows SDK/API programming.  At the most fundamental level
it's purpose is to match the incomming unsigned int msg parameter with the code that is to handle that or each
respective message.  The most commonly seen logic construct for this is a switch construct where under each 'case'
block can be found the code which handles each message.  In this app, however, I use an array of EVENTHANDLER
objects...

struct EVENTHANDLER
{
 unsigned int                     iMsg;
 LRESULT                          (*fnPtr)(WndEventArgs&);
};

...which associate the incomming iMsg numeric identifier with the runtime address of a specific message or event
handling function written specifically to handle each respective message. Since this app handles WM_CREATE,
WM_COMMAND, WM_CLOSE, and WM_DESTROY messages, the EVENTHANDLER array is initialized like so in Form2.h....

const EVENTHANDLER                EventHandler[]=
{
 {WM_CREATE,                      fnWndProc_OnCreate},
 {WM_COMMAND,                     fnWndProc_OnCommand},
 {WM_CLOSE,                       fnWndProc_OnClose},
 {WM_DESTROY,                     fnWndProc_OnDestroy}
};

In the code below one can see a for loop iterates through the EventHandler array attempting to make a match between
the incomming msg parameter of the Window Procedure and the iMsg member of the EVENTHANDLER object.  If a match is
made te corresponding message/event handling function is called through the function pointer.  If no code has been
written to handle a specific message, then the message is passed on to DefWindowProc() in the usual manner.  In this
way the Window Procedure will never grow beyond the 15 lines seen below.   
-----------------------------------------------------------------------------------------------------------------*/
LRESULT CALLBACK fnWndProc(HWND hWnd, unsigned int msg, WPARAM wParam, LPARAM lParam)
{
 WndEventArgs Wea;

 for(size_t i=0; i<dim(EventHandler); i++)
 {
     if(EventHandler[i].iMsg==msg)
     {
        Wea.hWnd=hWnd, Wea.lParam=lParam, Wea.wParam=wParam;
        return (*EventHandler[i].fnPtr)(Wea);
     }
 }

 return (DefWindowProc(hWnd, msg, wParam, lParam));
}


/*-------------int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevIns, LPSTR lpszArgument, int iShow)---------
Program Entry Point for Windows GUI app.  I personally like to keep my WinMain() functions short.  The only code I
put in them is code to Register my main app window, the CreateWindowEx() call to create that window, and the
'Message Pump' that keeps the program alive and functioning.  If the app instantiates multiple top level windows, I
put the code to register those additional classes in the WM_CREATE handler of the main app's window.
----------------------------------------------------------------------------------------------------------------*/
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevIns, LPSTR lpszArgument, int iShow)
{
 wchar_t szClassName[] =L"Form2";               
 HWND hWnd = NULL;                             
 WNDCLASSEX wc{0};                                 
 MSG messages;                                 
   
 //memset(&wc,0,sizeof(wc));                     // Zero out WNDCLASSEX object                 
 wc.lpszClassName = szClassName;               // Name of Window Class
 wc.lpfnWndProc   = fnWndProc;                 // Function Pointer To Window Procedure For szClassName
 wc.cbSize        = sizeof(WNDCLASSEX);        // Size Of Window Class Object.  This Field Isn't Optional!
 wc.hbrBackground = (HBRUSH)COLOR_BTNSHADOW;   // Background Color Of Main App Window
 wc.hInstance     = hInstance;                 // Instance Handle Of App
 RegisterClassEx(&wc);                         // Register Window Class
 hWnd=CreateWindowEx(0,szClassName,szClassName,WS_OVERLAPPEDWINDOW,200,175,320,200,HWND_DESKTOP,0,hInstance,0);
 ShowWindow(hWnd,iShow);                       // Displays Window
 while(GetMessage(&messages,NULL,0,0))         // The Message Pump Runs For Duration Of App And Retrieves Messages
 {                                             // Windows Puts In It's 'Message Queue' Informing The App Of User
    TranslateMessage(&messages);               // Interactions With App, And System Notifications The App May Be
    DispatchMessage(&messages);                // Interested In Learning About, for Example, The User Might Be
 }                                             // Shuting Down The System, Changing Monitor Resolution, etc.
                                         
 return messages.wParam;
}

...and the header file, Form2b.h...

Code: [Select]
#ifndef Form2b_h
#define Form2b_h

#define                           IDC_BUTTON 1500          // Button On Main Window Identifier
#define                           IDC_EDIT   1505          // Edit Control On Main Window Identifier
#define                           dim(x) (sizeof(x) / sizeof(x[0]))  // Determine Number Elements of Array

struct                            WndEventArgs             // Amalgamate Window Procedure Parameters
{                                                          // Into Single Object.  .NET Framework Does This
 HWND                             hWnd;                    // With Virtually All Parameters Of Event/Message
 WPARAM                           wParam;                  // Handling Functions
 LPARAM                           lParam;
 HINSTANCE                        hIns;
};

struct EVENTHANDLER                                        // Associate Window Procedure unsigned int
{                                                          // Message Parameter With Runtime Address Of
 unsigned int                     iMsg;                    // Message Handling Function.  Note This Is
 LRESULT                          (*fnPtr)(WndEventArgs&); // The Function Signature Of A 'Function
};                                                         // Pointer'.

LRESULT fnWndProc_OnCreate        (WndEventArgs& Wea);     // Prototype Of Event Handler For WM_CREATE Message.
LRESULT fnWndProc_OnCommand       (WndEventArgs& Wea);     // Prototype Of Event Handler For WM_COMMAND Message.
LRESULT fnWndProc_OnClose         (WndEventArgs& Wea);     // Prototype Of Event Handler For WM_CLOSE Message.
LRESULT fnWndProc_OnDestroy       (WndEventArgs& Wea);     // Prototype Of Event Handler For WM_DESTROY Message.

const EVENTHANDLER                EventHandler[]=          // Initialize EVENTHANDLER array Named EventHandler[]
{                                                          // With Paired Message Equates/Defines And Runtime
 {WM_CREATE,                      fnWndProc_OnCreate},     // Address Of Message/Event Handling Functions.  Since
 {WM_COMMAND,                     fnWndProc_OnCommand},    // We Have 4 Here, The Upper Bound Of The Array Will
 {WM_CLOSE,                       fnWndProc_OnClose},      // Be 3, And A for loop Iterating Through The Array
 {WM_DESTROY,                     fnWndProc_OnDestroy}     // Will Traverse Elements 0 Through 3.
};
#endif