/*
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;
}