ObjReader Community
Frederick Harris section => TCLib => Topic started by: Frederick Harris on October 14, 2021, 09:21:52 pm
-
/*
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.
*/
// 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;
}
-
/*
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....
*/
// 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;
}
-
#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
// 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...
#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