Author Topic: Win32 SDK Tutorial - Form5 - Multiple Application Windows  (Read 2726 times)

Frederick Harris

  • Newbie
  • *
  • Posts: 47
Win32 SDK Tutorial - Form5 - Multiple Application Windows
« on: October 14, 2021, 09:40:58 pm »
Form5 -- Multiple Application Windows
 
     A common GUI application architecture is to have a startup form which contains various GUI
elements/controls such as buttons or menu selections which then launch other top level dialogs or
windows.  Form5 shows one such possible architecture where we have a startup form with three
buttons on it, i.e., "Option 1", "Option 2", and "Option 3".  The start up form is of Window Class
"Multiple Forms".

     When the user clicks the "Option 1" button the program disables the main startup form and
presents a modal dialog containing some text.  The Window Class Name of the Dialog/Window/Form
presented is "Option1".

     When the user clicks the "Option 2" button on the start-up form the program renders the
startup form invisible and presents a window/dialog/form of Window Class "Option2".

     When the user clicks the "Option 3" button on the startup form the program neither disables
or renders invisible the main startup form, and presents a window/dialog/form of Window Class
"Option 3".  Since the start-up form is still visible and enabled, the user can click the
"Option 3" button as many times as he or she likes, and each click will cause another
instantiation of an "Option 3" object, and they will show up on the screen in cascaded fashion. 

     This app shows how to use the last parameter of the CreateWindow() or CreateWindowEx() call,
which hasn't been used in this series of programs up until now.  Note my use of it in the code's
btnOption1_Click() procedure, where I placed the main window's HWND in the last parameter of the
CreateWindowEx() call that creates an Option1 window (12th parameter of call).... 

long btnOption1_Click(WndEventArgs& Wea)
{                                       
 HWND hWnd;                             
                                         
 hWnd=CreateWindowEx(0,L"Option1",L"Option1",WS_OVERLAPPEDWINDOW,50,25,310,185,Wea.hWnd,(HMENU)0,GetModuleHandle(0),Wea.hWnd);
 ShowWindow(hWnd,SW_SHOWNORMAL);
 UpdateWindow(hWnd);

 return 0;
}

It is retrieved like so in the WM_CREATE handler fnOption1_OnCreate() in Option1.cpp like so....

Code: [Select]
LRESULT fnOption1_OnCreate(WndEventArgs& Wea)                 // During a WM_CREATE message the LPARAM is a pointer to a
{                                                             // CREATESTRUCT object which holds all the parameters of the
 CREATESTRUCT* pCreateStruct = NULL;                          // CreateWindow() or CreateWindowEx() call that prompted the
 HWND          hMain         = NULL;                          // WM_CREATE message.  See CREATESTRUCT in MS documentation.
                                                              // The CREATESTRUCT::lpCreateParams parameter, which is the
 pCreateStruct=(CREATESTRUCT*)Wea.lParam;                     // first member of the CREATESTRUCT struct, holds whatever
 hMain=(HWND)pCreateStruct->lpCreateParams;                   // number, object, or entity was placed in the last parameter
 SetWindowLongPtr(Wea.hWnd,GWLP_USERDATA,(LONG_PTR)hMain);    // of the CreateWindowEx() call that generates a WM_CREATE
 EnableWindow(hMain,FALSE);                                   // message.  So in the code left we create a CREATESTRUCT*
                                                              // variable pCreateStruct.  Next we cast the Wea.lParam to a
   return 0;                                                  // CREATESTRUCT* object.  Finally, we retrieve the HWND of the
}                                                             // main startup form from the CREATESTRUCT::lpCreateParams.

Here is what MSDN says about the last parameter....

Type: LPVOID

Pointer to a value to be passed to the window through the CREATESTRUCT structure (lpCreateParams
member) pointed to by the lParam param of the WM_CREATE message. This message is sent to the created
window by this function before it returns.

Here is the full description of CREATESTRUCTW....

Code: [Select]
struct CREATESTRUCTW
{
 LPVOID    lpCreateParams;
 HINSTANCE hInstance;
 HMENU     hMenu;
 HWND      hwndParent;
 int       cy;
 int       cx;
 int       y;
 int       x;
 LONG      style;
 LPCWSTR   lpszName;
 LPCWSTR   lpszClass;
 DWORD     dwExStyle;
};

     This technique is another of my 'Global Variable Killers', in that it allows the passing
about, storage, and persistance of necessary data that will be needed at different points in an
application's code.  Note in fnOption1_OnCreate() of Option1.cpp that after retrieving the HWND
of the main start up form from the Wea.lParam pointer (pointer to CREATESTRUCT), I store the
value within the "Option1" Window Class's instantiated Window Class structure using
SetWindowLongPtr and the parameter GWLP_USERDATA.  See MS documentation on this.  Another common
way I do this is using WNDCLASSEX::cbWndExtra bytes, where one can specify as many bytes as are
needed (in multiples of 4 bytes in x86 and 8 bytes in x64) to store whatever information is needed
by the application.  Yet another way is to use the SetProp()/GetProp() functions of Windows API.
This is a C based way of creating 'member variables' of a class.  It's a bit more awkward than
member variables of a Class in C++, but conceptually it accomplishes the same thing.  And note in
this app the HWND of the main program window or start up form is necessary, because when the user
dismisses either the Option1 form or the Option2 form, the main start up form needs to be re-
enabled or rendered visible again, as the case may be, and for those function calls that do that,
the HWND of the startup form is necessary.


Code: [Select]
// g++ Form5.cpp Option1.cpp Option2.cpp Option3.cpp -luser32 -lkernel32 -lgdi32 -oForm5_gcc.exe -mwindows -m64 -s -Os
// cl Form5.cpp Option1.cpp Option2.cpp Option3.cpp /O1 /Os /MT kernel32.lib user32.lib gdi32.lib /FeForm5_MSVC.exe                 
// cl Form5.cpp Option1.cpp Option2.cpp Option3.cpp /O1 /Os /W3 /GS- /Gy TCLib.lib kernel32.lib user32.lib gdi32.lib /FeForm5_TCLib.exe 
//  9,728 Bytes VC15, x64, UNICODE, TCLib Linkage
// 45,056 Bytes VC15, x64, UNICODE, MSVC Runtime Linkage
// 70,656 Bytes TDM-GCC 10.3, x64, UNICODE
// Form5.cpp
//#define TCLib
#ifndef UNICODE
   #define UNICODE
#endif
#ifndef _UNICODE
   #define _UNICODE
#endif
#include <windows.h>
#ifdef TCLib
   #include "string.h"
#else
   #include <string.h>
#endif
#pragma warning(disable:4996)
#include "Form5.h"
#include "Option1.h"
#include "Option2.h"
#include "Option3.h"


LRESULT fnWndProc_OnCreate(WndEventArgs& Wea)
{
 DWORD dwStyle=WS_CHILD|WS_VISIBLE;
 wchar_t szClassName[16];
 WNDCLASSEX wc;

 Wea.hIns=((LPCREATESTRUCT)Wea.lParam)->hInstance;
 CreateWindow(L"button",L"Option #1",dwStyle,65,15,120,25,Wea.hWnd,(HMENU)IDC_BUTTON_OPTION1,Wea.hIns,0); // Create Option #1 Button
 CreateWindow(L"button",L"Option #2",dwStyle,65,55,120,25,Wea.hWnd,(HMENU)IDC_BUTTON_OPTION2,Wea.hIns,0); // Create Option #2 Button
 CreateWindow(L"button",L"Option #3",dwStyle,65,95,120,25,Wea.hWnd,(HMENU)IDC_BUTTON_OPTION3,Wea.hIns,0); // Create Option #3 Button

 //Register Window Classes For Option1, Option2 and Option3
 wcscpy(szClassName,L"Option1");   
 wc.lpszClassName = szClassName,                           wc.lpfnWndProc   = fnOption1_WndProc;
 wc.cbSize        = sizeof(WNDCLASSEX),                    wc.style         = CS_HREDRAW | CS_VREDRAW;
 wc.cbClsExtra    = 0,                                     wc.cbWndExtra    = 0;
 wc.hInstance     = Wea.hIns,                              wc.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
 wc.hIconSm       = 0,                                     wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
 wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH),   wc.lpszMenuName  = NULL;
 RegisterClassEx(&wc);

 wcscpy(szClassName,L"Option2");         // Note that a WM_CREATE call is akin to a constructor call in typical
 wc.lpszClassName = szClassName;         // C++ class architecture.  When you receive this call/message Windows
 wc.lpfnWndProc   = fnOption2_WndProc;   // has finished doing what it needs to support the Window object, and   
 RegisterClassEx(&wc);                   // is 'passing the ball' to you.  In my apps with multiple windows I
                                         // typically use the WM_CREATE handler to register any window classes
 wcscpy(szClassName,L"Option3");         // I need in the app, so that I can make CreateWindow() calls when I
 wc.lpszClassName = szClassName;         // need to instantiate a window of some type.
 wc.lpfnWndProc   = fnOption3_WndProc;   
 RegisterClassEx(&wc);                   
                                         
 return 0;
}


long btnOption1_Click(WndEventArgs& Wea)
{                                       
 HWND hWnd;                             
                                         
 hWnd=CreateWindowEx(0,L"Option1",L"Option1",WS_OVERLAPPEDWINDOW,50,25,310,185,Wea.hWnd,(HMENU)0,GetModuleHandle(0),Wea.hWnd);
 ShowWindow(hWnd,SW_SHOWNORMAL);
 
 return 0;
}


long btnOption2_Click(WndEventArgs& Wea)
{
 HWND hWnd;

 hWnd=CreateWindowEx(0,L"Option2",L"Option2",WS_OVERLAPPEDWINDOW,200,250,310,185,Wea.hWnd,(HMENU)0,GetModuleHandle(0),Wea.hWnd);
 ShowWindow(hWnd,SW_SHOWNORMAL);
 
 return 0;
}


long btnOption3_Click(WndEventArgs& Wea)
{
 HWND hWnd;

 hWnd=CreateWindowEx(0,L"Option3",L"Option3",WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,CW_USEDEFAULT,300,260,0,(HMENU)0,GetModuleHandle(0),Wea.hWnd);
 ShowWindow(hWnd,SW_SHOWNORMAL);
 
 return 0;
}


LRESULT fnWndProc_OnCommand(WndEventArgs& Wea)
{
 switch(LOWORD(Wea.wParam))
 {
    case IDC_BUTTON_OPTION1:
      return btnOption1_Click(Wea);
    case IDC_BUTTON_OPTION2:
      return btnOption2_Click(Wea);
    case IDC_BUTTON_OPTION3:
      return btnOption3_Click(Wea);
 }

 return 0;
}


LRESULT fnWndProc_OnClose(WndEventArgs& Wea)     //Search And Destroy Mission For Any Form3
{                                                //Windows Hanging Around.
 HWND hForm;

 if(MessageBox(Wea.hWnd,L"Do You Wish To Exit?",L"Exit App?",MB_YESNO)==IDYES)
 {
    do                                           //If FindWindow() returns something other
    {                                            //than zero, it found a window matching
      hForm=FindWindow(L"Option3",L"Option3");   //the description of what you are looking
      if(hForm)                                  //for.  In that case, send a WM_CLOSE
         SendMessage(hForm,WM_CLOSE,0,0);        //message.  If NULL is returned then just
      else                                       //break out of the loop and terminate the
         break;                                  //app.
    } while(true);
    DestroyWindow(Wea.hWnd);
    PostQuitMessage(0);
 }

 return 0;
}


LRESULT CALLBACK fnWndProc(HWND hwnd, unsigned int msg, WPARAM wParam,LPARAM lParam)
{
 WndEventArgs Wea;

 for(unsigned int 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)
{
 wchar_t szClassName[]=L"Multiple Forms";
 WNDCLASSEX wc={};
 MSG messages;
 HWND hWnd;

 wc.lpszClassName = szClassName;               wc.lpfnWndProc = fnWndProc;
 wc.cbSize        = sizeof (WNDCLASSEX);       wc.hIcon       = LoadIcon(NULL,IDI_APPLICATION);
 wc.hInstance     = hInstance,                 wc.hCursor     = LoadCursor(NULL,IDC_ARROW);
 wc.hbrBackground = (HBRUSH)COLOR_BTNSHADOW;
 RegisterClassEx(&wc);
 hWnd=CreateWindowEx(0,szClassName,szClassName,WS_OVERLAPPEDWINDOW,250,500,260,180,HWND_DESKTOP,0,hInstance,0);
 ShowWindow(hWnd,iShow);
 while(GetMessage(&messages,NULL,0,0))
 {
    TranslateMessage(&messages);
    DispatchMessage(&messages);
 }
 
 return (int)messages.wParam;
}

Code: [Select]
//Form5.h
#ifndef FORM5_H
#define FORM5_H

#define dim(x)                (sizeof(x) / sizeof(x[0]))  // Used In For Loops Of Window Procedures To Determine Upper Bound
#define IDC_BUTTON_OPTION1    1600                        // Control Id Of Option1 Button On Main Or Start Up Form/Window/Dialog
#define IDC_BUTTON_OPTION2    1605                        // Control Id Of Option2 Button On Main Or Start Up Form/Window/Dialog
#define IDC_BUTTON_OPTION3    1610                        // Control Id Of Option3 Button On Main Or Start Up Form/Window/Dialog

struct                        WndEventArgs                // Amalgamate Parameters Of Window Procedure Into a Struct/Class Object
{
 HWND                         hWnd;
 WPARAM                       wParam;
 LPARAM                       lParam;
 HINSTANCE                    hIns;
};

struct EVENTHANDLER                                       // Associate Message/Event Parameter Of Window Procedure In Object With
{                                                         // Virtual Runtime Address Of Specific Function To Handle That Message
 unsigned int                 iMsg;                       // Or Event.  The '*' symbol right before the fnPtr symbol in parentheses
 LRESULT                      (*fnPtr)(WndEventArgs&);    // Is A Dead Give-Away For A C/C++ Function Pointer Declaration.
};

LRESULT fnWndProc_OnCreate    (WndEventArgs&);            //We need foreward declarations of
LRESULT fnWndProc_OnCommand   (WndEventArgs&);            //the various functions in a main
LRESULT fnWndProc_OnClose     (WndEventArgs&);            //header file so that the event
                                                          //handlers can be attached below.
LRESULT fnOption1_OnCreate    (WndEventArgs&);
LRESULT fnOption1_OnPaint     (WndEventArgs&);
LRESULT fnOption1_OnDestroy   (WndEventArgs&);

LRESULT fnOption2_OnCreate    (WndEventArgs&);
LRESULT fnOption2_OnPaint     (WndEventArgs&);
LRESULT fnOption2_OnDestroy   (WndEventArgs&);

LRESULT fnOption3_OnPaint     (WndEventArgs&);

const EVENTHANDLER            EventHandler[]=             // All These Entities Left And Below Are Array Initializations Of const     
{                                                         // EVENTHANDLER Objects.  For Example, Our Main Or Start-Up Form/Window/Dialog
 {WM_CREATE,                  fnWndProc_OnCreate},        // Handles Three Messages, i.e., WM_CREATE, WM_COMMAND, And WM_CLOSE. 
 {WM_COMMAND,                 fnWndProc_OnCommand},       // EventHandler[0].iMsg will be set to the value of WM_CREATE - Which I Believe
 {WM_CLOSE,                   fnWndProc_OnClose}          // Is 1 (would have to double check), And EventHandler[0].fnPtr Would Be Set
};                                                        // Equal To The Virtual Runtime Address Of fnWndProc_OnCreate() At Program Load.

const EVENTHANDLER            Option1EventHandler[]=      // Likewise With All The Other Array Elements Of EventHandler.  And Likewise With
{                                                         // Option1EventHandler[], Option2EventHandler[], and Option3EventHandler[].
 {WM_CREATE,                  fnOption1_OnCreate},
 {WM_PAINT,                   fnOption1_OnPaint},
 {WM_DESTROY,                 fnOption1_OnDestroy}
};

const EVENTHANDLER            Option2EventHandler[]=      // In The Window Procedures Where these EventHandler Objects Are Used, A For
{                                                         // Loop Will Iterate Through These Arrays Of Objects Seeking To Make A Match
 {WM_CREATE,                  fnOption2_OnCreate},        // Between the EVENTHANDLER::iMsg member, And The unsigned int msg parameter
 {WM_PAINT,                   fnOption2_OnPaint},         // Of The Window Procedure.  If A Match Is Made, That Means An Event Handling
 {WM_DESTROY,                 fnOption2_OnDestroy}        // Procedure Has Been Written For That Message, And That Procedure Is Called
};                                                        // Through It's Address As Contained In EVENTHANDLER::fnPtr.  If The Entire
                                                          // Array Is Transversed And No Match Was Found, Then That msg Is Passed On To
const EVENTHANDLER            Option3EventHandler[]=      // DefWindowProc() In The Usual Manner.
{
 {WM_PAINT,                   fnOption3_OnPaint}
};
#endif

Code: [Select]
//Option1.cpp
#ifndef UNICODE
   #define UNICODE
#endif
#ifndef _UNICODE
   #define _UNICODE
#endif
#include  <Windows.h>
#include  "Option1.h"
#include  "Form5.h"


LRESULT fnOption1_OnCreate(WndEventArgs& Wea)                 // During a WM_CREATE message the LPARAM is a pointer to a
{                                                             // CREATESTRUCT object which holds all the parameters of the
 CREATESTRUCT* pCreateStruct = NULL;                          // CreateWindow() or CreateWindowEx() call that prompted the
 HWND          hMain         = NULL;                          // WM_CREATE message.  See CREATESTRUCT in MS documentation.
                                                              // The CREATESTRUCT::lpCreateParams parameter, which is the
 pCreateStruct=(CREATESTRUCT*)Wea.lParam;                     // first member of the CREATESTRUCT struct, holds whatever
 hMain=(HWND)pCreateStruct->lpCreateParams;                   // number, object, or entity was placed in the last parameter
 SetWindowLongPtr(Wea.hWnd,GWLP_USERDATA,(LONG_PTR)hMain);    // of the CreateWindowEx() call that generates a WM_CREATE
 EnableWindow(hMain,FALSE);                                   // message.  So in the code left we create a CREATESTRUCT*
                                                              // variable pCreateStruct.  Next we cast the Wea.lParam to a
 return 0;                                                    // CREATESTRUCT* object.  Finally, we retrieve the HWND of the
}                                                             // main startup form from the CREATESTRUCT::lpCreateParams.


LRESULT fnOption1_OnPaint(WndEventArgs& Wea)
{
 PAINTSTRUCT ps;
 HDC hDC=NULL;

 hDC=BeginPaint(Wea.hWnd,&ps);
 TextOut(hDC,0,0,L"This Is Option1.  It Disables The Main",36);
 TextOut(hDC,0,16,L"Window, And That Makes It Modal.  Note",38);
 TextOut(hDC,0,32,L"That We Passed The Handle Of The Main",37);
 TextOut(hDC,0,48,L"Window In The Last Parameter Of The",35);
 TextOut(hDC,0,64,L"CreateWindow() Call, And Retrieved It In",40);
 TextOut(hDC,0,80,L"fnOption1_OnCreate().  We Then Stored It So",41);
 TextOut(hDC,0,96,L"We Could EnableWindow(TRUE) The Main",36);
 TextOut(hDC,0,112,L"Window When This Modal Form Is",30);
 TextOut(hDC,0,128,L"Dismissed.",10);
 EndPaint(Wea.hWnd,&ps);

 return 0;
}


LRESULT fnOption1_OnDestroy(WndEventArgs& Wea)
{
 HWND hMain=NULL;

 hMain=(HWND)GetWindowLongPtr(Wea.hWnd,GWLP_USERDATA);
 EnableWindow(hMain,TRUE);
 
 return 0;
}


LRESULT CALLBACK fnOption1_WndProc(HWND hWnd, unsigned int msg, WPARAM wParam, LPARAM lParam)
{
 WndEventArgs Wea;

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

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

Code: [Select]
//Option1.h         //Needs to be included in Main.cpp because the WM_CREATE handler there
#ifndef OPTION1_H   //references fnForm1_WndProc as the Window Procedure for the Option1 Class.
#define OPTION1_H   //This would be needed to register the class.
LRESULT CALLBACK fnOption1_WndProc(HWND, unsigned int, WPARAM, LPARAM);
#endif

Code: [Select]
//Option2.cpp
#ifndef UNICODE
   #define UNICODE
#endif
#ifndef _UNICODE
   #define _UNICODE
#endif
#include  <Windows.h>
#include  "Form5.h"
#include  "Option2.h"


LRESULT fnOption2_OnCreate(WndEventArgs& Wea)
{
 CREATESTRUCT* pCreateStruct = NULL;
 HWND          hMain         = NULL;

 pCreateStruct=(CREATESTRUCT*)Wea.lParam;
 hMain=(HWND)pCreateStruct->lpCreateParams;
 SetWindowLongPtr(Wea.hWnd,GWLP_USERDATA,(LONG_PTR)hMain);
 ShowWindow(hMain,SW_HIDE);

 return 0;
}


LRESULT fnOption2_OnPaint(WndEventArgs& Wea)
{
 PAINTSTRUCT ps;
 HDC hDC=NULL;

 hDC=BeginPaint(Wea.hWnd,&ps);
 TextOut(hDC,0,0,L"This Is Option2.  It SW_HIDEs The Main",36);
 TextOut(hDC,0,16,L"Window, And SW_SHOWs It Upon Closing.",37);
 TextOut(hDC,0,32,L"This Technique Can Be Used Similiarly",37);
 TextOut(hDC,0,48,L"To A Modal Dialog If It Isn't Necessary To",42);
 TextOut(hDC,0,64,L"View Simultaneously A Form Underneath The",41);
 TextOut(hDC,0,80,L"Dialog With Which You Can't Interact",36);
 TextOut(hDC,0,96,L"Anyway",6);
 EndPaint(Wea.hWnd,&ps);

 return 0;
}


LRESULT fnOption2_OnDestroy(WndEventArgs& Wea)
{
 HWND hMain;

 hMain=(HWND)GetWindowLongPtr(Wea.hWnd,GWLP_USERDATA);
 ShowWindow(hMain,TRUE);

 return 0;
}


LRESULT CALLBACK fnOption2_WndProc(HWND hWnd, unsigned int msg, WPARAM wParam, LPARAM lParam)
{
 WndEventArgs Wea;

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

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

Code: [Select]
//Option2.h        //Needs to be included in Main.cpp because the WM_CREATE handler there
#ifndef OPTION2_H  //references fnOption2_WndProc as the Window Procedure for the Option2 Class.
#define OPTION2_H  //This would be needed to register the class.
LRESULT CALLBACK fnOption2_WndProc(HWND, unsigned int, WPARAM, LPARAM);
#endif

Code: [Select]
//Option3.cpp
#ifndef UNICODE
   #define UNICODE
#endif
#ifndef _UNICODE
   #define _UNICODE
#endif
#include  <Windows.h>
#include  "Form5.h"
#include  "Option3.h"


LRESULT fnOption3_OnPaint(WndEventArgs& Wea)
{
 PAINTSTRUCT ps;
 HDC hDC=NULL;

 hDC=BeginPaint(Wea.hWnd,&ps);
 TextOut(hDC,0,0,L"This Is Option3.  Not Only Does It Neither",40);
 TextOut(hDC,0,16,L"Hide Nor Disable The Main Window, But",37);
 TextOut(hDC,0,32,L"You'll Find That You Can Create As Many",39);
 TextOut(hDC,0,48,L"Of These As You Like By Continually",35);
 TextOut(hDC,0,64,L"Clicking The Bottom Button On The Main",38);
 TextOut(hDC,0,80,L"Form.  However, You'll Have To Drag One",39);
 TextOut(hDC,0,96,L"From On Top Of The Other Because They",37);
 TextOut(hDC,0,112,L"All Appear In The Same Location (I",34);
 TextOut(hDC,0,128,L"Changed That).  You May Further Note",36);
 TextOut(hDC,0,144,L"That Since These Windows Are Neither",36);
 TextOut(hDC,0,160,L"Disabled Nor Hidden At Any Time, You",36);
 TextOut(hDC,0,176,L"May Interact With Them Irregardless Of",38);
 TextOut(hDC,0,192,L"The State Of Form1 Or Form2. Pretty",35);
 TextOut(hDC,0,208,L"Neat, Don't You Think?",22);
 EndPaint(Wea.hWnd,&ps);

 return 0;
}


LRESULT CALLBACK fnOption3_WndProc(HWND hWnd, unsigned int msg, WPARAM wParam, LPARAM lParam)
{
 WndEventArgs Wea;

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

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

Code: [Select]
//Option3.h        //Needs to be included in Main.cpp because the WM_CREATE handler there
#ifndef OPTION3_H  //references fnOption3_WndProc as the Window Procedure for the Option3 Class.
#define OPTION3_H  //This would be needed to register the class.
LRESULT CALLBACK fnOption3_WndProc(HWND, unsigned int, WPARAM, LPARAM);
#endif
« Last Edit: December 10, 2021, 03:43:32 pm by Frederick Harris »