Author Topic: Win32 SDK Tutorial - Form6 - Resources, Resource Script Dialog Boxes, Menus  (Read 5556 times)

Frederick Harris

  • Newbie
  • *
  • Posts: 47
Form6 -- Resources, Resource Script Dialog Boxes, Open And Save File Dialog Boxes And Menus

     This program shows how to create menus and dialog boxes from within a CreateWindow() based
application using resource files, i.e., *.rc files, which are compiled using a resource compiler -
rc.exe for Microsoft Build Chain, and WindRers.exe for the GCC Build Chain.  So in this Form6
project the three source code files are Form6.cpp, Form6.h, and Resources.rc.  Resources.rc looks
like this....

Code: [Select]
// Resources.rc
#include "Form6.h"

    POPUP "&File"
        MENUITEM "&Open...",      IDM_FILE_OPEN
        MENUITEM "&Save...",      IDM_FILE_SAVE
        MENUITEM "E&xit",         IDM_FILE_EXIT
    POPUP "O&ptions"
        MENUITEM "&Explorer",     IDM_OPTIONS_EXPLORER
    POPUP "&Help"
        MENUITEM "&About",        IDM_ABOUT

dlgAbout DIALOG DISCARDABLE  100, 100, 239, 66
STYLE 0x80L | 0x80000000L | 0x00C00000L | 0x00080000L
CAPTION "My About Box"
FONT 8, "MS Sans Serif"
    DEFPUSHBUTTON   "&OK",1,174,18,50,14
    PUSHBUTTON      "&Cancel",2,174,35,50,14
    GROUPBOX        "About this program...",-1,7,7,225,52
    CTEXT           "An example program showing how to use Dialog Boxes\r\n\r\nBy Fred",-1,16,18,144,33
// End Resources.rc       

     If you are using Visual Studio's IDE, or, likely any other IDE, the IDE will likely invoke the
necessary binaries within it's 'Build Chain' to incorporate these dialog and menu resources within
the outputted executable file.  For it to do this it is important for the reader here to include
these three files in the IDE's 'Project'.  Since I usually build from the command line here is my
console output after invoking rc.exe to first create Resources.res (if you are using an IDE you can
ignore this)....
C:\Code\Forms\Form6>rc.exe /v /foResources.res Resources.rc
Microsoft (R) Windows (R) Resource Compiler Version 6.0.5724.0
Copyright (C) Microsoft Corporation.  All rights reserved.

Using codepage 1252 as default
Creating Resources.res

Writing MENU:MAINMENU,  lang:0x409,     size 160.
Writing DIALOG:DLGABOUT,        lang:0x409,     size 368

Next one invokes CvtRes.exe to convert the Resources.res file to Resources.obj....

C:\Code\Forms\Form6>cvtres.exe /MACHINE:X64 /v Resources.res
Microsoft (R) Windows Resource To Object Converter Version 9.00.21022.08
Copyright (C) Microsoft Corporation.  All rights reserved.

adding resource. type:MENU, name:MAINMENU, language:0x0409, flags:0x1030, size:160
adding resource. type:DIALOG, name:DLGABOUT, language:0x0409, flags:0x1030, size:368


     Continuing the discussion, this Form6 program creates a small main window with menus 'File',
'Options' and 'Help'.  Under the File menu are the selections 'Open...", 'Save...', and 'Exit'.
If one chooses 'Open' an Open File Dialog Box presents itself.  If you choose 'Save...', a Save
File Dialog Box appears.  If you choose 'Exit', the program ends.

     Under the 'Options' menu is the single choice 'Explorer'.  If you choose to execute that a
Windows Explorer window opens up in whatever folder is current.

     Under the 'Help' menu is an 'About...' selection, and if you execute that the resource script
Dialog Box presents itself that is defined in Resources.rc.

     I didn't mention above that if you choose 'Open File' or 'Save File' under the 'File' menu,
your choice after selecting a file from the dialog is written by TextOut() to the main program
window.  Due to the architecture of Windows with regard to GDI drawing functions, this feat can seem
to be a bit convoluted to newcommers.  For you see, executing a menu selection gets one inside some
WM_COMMAND handler function - similiar to the situation of a button click message/event procedure.
Menus work exactly the same.  So inside this message procedure one creates local stack based
variables necessary to get the machinery of Open and Save File Dialog Boxes working.  This involves
an object known as the OPENFILENAME struct defined in ComDlg.h and the object code is in
comdlg32.lib.  Where the plot thickens is that once one chooses a file name and exits that function,
it becomes necessary to draw the text of the chosen file to the main application window.  But that
drawing will necessarily be done in a WM_PAINT handler function, and any local stack based buffers
holding the chosen file name will no longer be valid!  Unless one uses static or global variables!
So in my case - where I'm philosophically opposed to using these, what is to be done????

     There are ways to 'persist' data in a program - 'persist' data across function calls, that
don't rely on global or static data storage in the program's 'data segment'.  One does this through
dynamic memory allocations, where one then stores pointers to memory buffers within program objects
themselves, for example, in the case of Windows, in 'Window Properties (GetProp()/SetProp()), or in
allocated WNDCLASSEX::cbWndExtra Bytes (I bet you wondered what those were - you surely saw them in
filling out WNDCLASSEX structs in WinMain()). 

     Let's look at Object Oriented Programming's underlying structure and philosophy.  One has an
object with properties...

Code: [Select]

     In Form6.h you'll find this...

Code: [Select]
                                     struct FileData
                                      wchar_t*  pFile;
                                      wchar_t*  pFileTitle;
                                      wchar_t*  pFilter;
                                      wchar_t*  pIniDir;
                                      wchar_t*  pDefExt;

     These are all entities that are either _IN or _OUT parameters of GetOpenFileName() or
GetSaveFileName()'s OPENFILENAME object.  With this line in fnWndProc_OnCreate()...


...I allocate memory for a FileData object, and with this line immediately afterward....

...I attach this FileData object to the WNDCLASSEX object through the WNDCLASSEX::cbWndExtra bytes.
Note that I'm aware of no limit to the number of bytes one may request Windows to allocate in the
cbWndExtra bytes, but the allocation and access must be in terms of 4 or 8 bytes depending on
whether a 32 bit or 64 bit operasting system is being used.  In this particular Form6 app, I
requested 16 bytes (2*sizeoof(void*) in filling out the WNDCLASSEX object in WinMain().  That would
be exactly the amount of room needed to store two pointers at 8 bytes each for x64.  So bytes 0
through 7 in the cbWndExtra bytes holds a pointer to an allocated buffer to return the file string
returned through either GetOpenFileName() or GetSaveFileName(), and bytes 8 through 15 hold a
pointer to a FileData object.  In that way, these 'objects' are attached to an instantiated
WNDCLASSEX object in the Object.Property_n conceptualization as described above, and in that way
further 'persisted' for use at any time during a program run, for example, the string data to be
output to a window through a TextOut() call during a WM_PAINT handler function.

     And like I said, I'm aware of no limit to the amount of cbWndExtra bytes one may request. 
However, I can't recall requesting more than 100 or so bytes in any of my major apps.  Since one
needs this information constantly on how data is stored in one's app, I frequently do something like
so to help keep track of what's stored where (from one of my actual work apps)....   

Code: [Select]
   What's Stored Where In WNDCLASSEX::cbWndExtra Bytes (x64)

   Offset       Slot        Description
   0  -  7      Zeroth      hMain, i.e., HWND of Main Startup Program Window
   8  -  15     First       pTrtGrid, i.e., IGrid* For Top Grid = Treatments
   16 -  23     Second      dwCookie For pTrtGrid, i.e., Connection Id For Event Sink
   24 -  31     Third       pCruGrid, i.e., IGrid* For Bottom Cruise Id Grid
   32 -  39     Fourth      dwCookie For pCruGrid, i.e., Connection Id For Event Sink
   40 -  47     Fifth       pStuGrid, i.e., IGrid* For Right Most Stumpage Prices Grid
   48 -  55     Sixth       pPtGrid, i.e., IGrid* For Prism Cruise Points Grid
   56 -  63     Seventh     dwCookie For Prism Cruise Grid's Events
   64 -  71     Eighth      pBkGrid For 100% Tally Grid
   72 -  79     Nineth      dwCookie For Events For pBkGrid
   80 -  87     Tenth       Edit bools for PtGrid
   88 -  95     Eleventh    Edit bools for Mrk (bk) Grid

     For example, according to the above table, bytes 64 - 71 contain a pointer to a 'Block Grid' (a
COM entitry - I have an ActiveX Grid Control in the app), and that pointer would be returned by a
GetWindowLongPtr() function call requesting the parent window return the pointer at 8*sizeof(void*)
or 64 bytes in for x64.

     Making a table such as above for this Form6 app would produce something like so...

Code: [Select]
   What's Stored Where In WNDCLASSEX::cbWndExtra Bytes (x64) For Form6.cpp

   Offset       Slot        Description
   0  -  7      Zeroth      pOpenFileName (From Open File Dialog)
   8  -  15     First       FileData* (Buffers For Open File Dialog)

...which I see I already have at the top of my fnWndProc_OnCreate() code.

Code: [Select]
// cl Form6.cpp /O1 /Os /W3 /GS- /Gy /FeForm6_TCLib.exe /link TCLib.lib Resources.obj kernel32.lib user32.lib gdi32.lib comdlg32.lib shell32.lib
// cl Form6.cpp /O1 /Os /W3 /GS- /Gy /FeForm6_MSVC.exe /link Resources.obj kernel32.lib user32.lib gdi32.lib comdlg32.lib shell32.lib
// cl @App.txt
// 95,744 Bytes, VC++ 19.25,  x64, UNICODE, LibCmt
// 23,552 Bytes, TDM-C++ 9.2, x64, UNICODE, LibCmt
//  8,704 Bytes, VC++ 19.25,  x64, UNICODE, TCLib
//#define TCLib  // Uncomment this line if you are building with my TCLib.lib
#ifndef UNICODE
   #define UNICODE
#ifndef _UNICODE
   #define _UNICODE
#include <windows.h>
#include <commdlg.h>
#ifdef TCLib
   #include "string.h"
   #include <string.h>
#include "Form6.h"
#pragma warning(disable:4996)

INT_PTR CALLBACK AboutDlgProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam)
 switch(Message)                      // Dialog Procedure
      return TRUE;
    case WM_COMMAND:
         case IDOK:
           EndDialog(hwnd, IDOK);
         case IDCANCEL:
           EndDialog(hwnd, IDCANCEL);
      return FALSE;

 return TRUE;

bool AllocateOpenFileBuffers(FileData* pFileData)
 HANDLE hHeap = NULL;       // Necessary to do something like this if one wishes to avoid static
                            // or global buffers to persist OPENFILENAME information from
 hHeap=GetProcessHeap();    // GetOpenFileName() or GetSaveFileName(), because, when the function
 if(!hHeap)                 // exits where these calls are made, locally allocated buffers become
    goto Bad_Ending;        // invalid. 
    goto Bad_Ending;
    goto Bad_Ending;
    goto Bad_Ending;
    goto Bad_Ending;
    goto Bad_Ending;
 return true;


 return false;

LRESULT ShowOpenSaveFile(WndEventArgs& Wea, BOOL bOpen)
 wchar_t      szFilterString[] = L"C Files (*.C),CPP Files (*.cpp)\0*.c;*.cpp\0\0";  // 43 chars
 FileData*    pFileData        = NULL;
 wchar_t*     pOpenFileName    = NULL;
 wchar_t      lpszBuffer[256*sizeof(wchar_t)];
 pFileData=(FileData*)GetWindowLongPtr(Wea.hWnd,1*sizeof(void*)); // The way Open and Save File
 CopyMemory(pFileData->pFilter,szFilterString,86);                // Dialog Boxes work is that one
 GetCurrentDirectory(256*sizeof(wchar_t),lpszBuffer);             // must fill out an OPENFILENAME
 memset(&ofn,L'\0',sizeof(OPENFILENAME));                         // struct/object which becomes the
 ofn.lStructSize     = sizeof(OPENFILENAME);                      // sole parameter of either
 ofn.lpstrFilter     = pFileData->pFilter;                        // GetOpenFileName() or
 ofn.nMaxFile        = _MAX_PATH;                                 // GetSaveFileName() calls.  It
 ofn.nMaxFileTitle   =_MAX_FNAME+_MAX_EXT;                        // gets a bit complicated because
 ofn.lpstrInitialDir = lpszBuffer;                                // a lot of the fields require
 ofn.lpstrDefExt     = L"cpp";                                    // pointers to allocated buffers
 ofn.hInstance       = GetModuleHandle(L"");                      // which is where these functions
 ofn.hwndOwner       = Wea.hWnd;                                  // return the character strings
 ofn.Flags           = OFN_HIDEREADONLY | OFN_CREATEPROMPT;       // they obtain from the user's
 ofn.lpstrFile       = pFileData->pFile;                          // selected file
 ofn.lpstrFileTitle  = pFileData->pFileTitle;
 if(bOpen)                    // If bOpen is true a GetOpenFileName() call is made...
 else                         // ...otherwise, a GetSaveFileName() call.
 pOpenFileName       = (wchar_t*)GetWindowLongPtr(Wea.hWnd,0*sizeof(void*)); // Get pointer to
 wcscpy(pOpenFileName,ofn.lpstrFile);  // allocated buffer, and copy returned string to it.
 InvalidateRect(Wea.hWnd,NULL,TRUE);   // Force WM_PAINT message to TextOut() FileName String.

 return 1L;

LRESULT fnWndProc_OnCreate(WndEventArgs& Wea)  // WNDCLASSEX::cbWndExtra Bytes
{                                              //
 FileData* pFileData     = NULL;               // Offset   What's Stored There (64 Bit)
 wchar_t*  pOpenFileName = NULL;               // =================================================
 HANDLE    hHeap         = NULL;               // 0  -  7  pOpenFileName (From Open File Dialog)
                                               // 8  - 15  FileData* (Buffers For Open File Dialog)
    goto Sad_Ending;                 
 pOpenFileName=(wchar_t*)HeapAlloc(hHeap,HEAP_ZERO_MEMORY,256*sizeof(wchar_t) ); // Buffer For File-
 if(!pOpenFileName)                                                              // Name
    goto Sad_Ending;
 SetWindowLongPtr(Wea.hWnd,0*sizeof(void*),(LONG_PTR)pOpenFileName);     // Store In           
 pFileData=(FileData*)HeapAlloc(hHeap,HEAP_ZERO_MEMORY,sizeof(FileData));// WndClass::cbWndExtra       
 if(!pFileData)                                                          // Bytes.  Then Allocate
    goto Sad_Ending;                                                     // FileData Object (See
 SetWindowLongPtr(Wea.hWnd,1*sizeof(void*),(LONG_PTR)pFileData);         // Form6.h).  Also Store
 if(!AllocateOpenFileBuffers(pFileData))                                 // In WndClass::cbWndExtra
    goto Sad_Ending;                                                     // Bytes
 return 0;            // If We Got To Here, Everything Has Worked.  Return Zero And CreateWindowEx()
                      // Call In WinMain() Will Succeed.
 Sad_Ending:          // Try To Unravel Memory Allocations If Something Went Wrong!
    if(pOpenFileName)                     // If we end up down in this area something went wrong -
       HeapFree(hHeap,0,pOpenFileName);   // likely a memory allocation failure, and this function
    if(pFileData)                         // will return a -1, which will cause the CreateWindow()
       HeapFree(hHeap,0,pFileData);       // call in WinMain() to fail and the app to terminate.

 return -1;

LRESULT fnWndProc_OnAbout(WndEventArgs& Wea)
 INT_PTR pReturn=DialogBoxParam(GetModuleHandle(L""),L"dlgAbout", Wea.hWnd, AboutDlgProc, 0);
    MessageBox(Wea.hWnd,L"Dialog exited with IDOK.",L"Notice",MB_OK|MB_ICONINFORMATION);
 else if(pReturn==IDCANCEL)
    MessageBox(Wea.hWnd,L"Dialog exited with IDCANCEL.",L"Notice",MB_OK|MB_ICONINFORMATION);
 else if(pReturn==-1)
    MessageBox(Wea.hWnd,L"Dialog failed!",L"Error",MB_OK|MB_ICONINFORMATION);

 return 0;

LRESULT fnWndProc_OnOptions(WndEventArgs& Wea)
 wchar_t szBuffer[512];

 return 0;

LRESULT fnWndProc_OnExit(WndEventArgs& Wea)
 return 0;

LRESULT fnWndProc_OnCommand(WndEventArgs& Wea)
    case IDM_FILE_OPEN:
      return ShowOpenSaveFile(Wea,true);  // true  cause Open File Dialog To Show
    case IDM_FILE_SAVE:
      return ShowOpenSaveFile(Wea,false); // false cause Save File Dialog To Show
    case IDM_ABOUT:
      return fnWndProc_OnAbout(Wea);
      return fnWndProc_OnOptions(Wea);
    case IDM_FILE_EXIT:
      return fnWndProc_OnExit(Wea);

 return 0;

LRESULT fnWndProc_OnPaint(WndEventArgs& Wea)
 wchar_t* pOpenFileName=NULL;

 pOpenFileName=(wchar_t*)GetWindowLongPtr(Wea.hWnd,0*sizeof(void*)); // Get FileName String From
 TextOut(hDC,2,2,pOpenFileName,(int)wcslen(pOpenFileName));          // Open/Save Dialog Box and
 EndPaint(Wea.hWnd,&ps);                                             // Draw It To Window

 return 0;

LRESULT fnWndProc_OnDestroy(WndEventArgs& Wea)   // Free piles of memory allocations made
{                                                // in this program for string buffers.
 wchar_t*  pOpenFileName = NULL;
 FileData* pFileData     = NULL;
 HANDLE    hHeap         = NULL;

 return 0;

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

 for(size_t i=0; i<dim(EventHandler); i++)
        Wea.hWnd=hwnd, Wea.lParam=lParam, Wea.wParam=wParam;
        return (*EventHandler[i].fnPtr)(Wea);

 return (DefWindowProc(hwnd, msg, wParam, lParam));

int CALLBACK WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
 wchar_t szClass[]    = L"Resources";
 wchar_t szMenuName[] = L"MainMenu";
 HWND hWnd;
 MSG Msg;

 wc.lpszClassName = szClass,                    wc.lpfnWndProc   = WndProc;;
 wc.cbSize        = sizeof(WNDCLASSEX),         = 0;
 wc.cbClsExtra    = 0,                          wc.cbWndExtra    = 2*sizeof(void*);
 wc.hInstance     = hInstance,                  wc.hIcon         = LoadIcon(NULL,IDI_APPLICATION);
 wc.hCursor       = LoadCursor(NULL,IDC_ARROW), wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
 wc.lpszMenuName  = szMenuName,                 wc.hIconSm       = LoadIcon(NULL, IDI_APPLICATION);
 hWnd=CreateWindow(szClass,L"Dialog Engine Dialog Boxes",WS_OVERLAPPEDWINDOW,350,350,425,200,NULL,NULL,hInstance,NULL);
 while(GetMessage(&Msg, NULL, 0, 0))

 return (int)Msg.wParam;

Code: [Select]

#define dim(x)                (sizeof(x) / sizeof(x[0]))
#define IDC_STATIC            -1
#define IDM_FILE_OPEN         1500
#define IDM_FILE_SAVE         1505
#define IDM_FILE_EXIT         1510
#define IDM_ABOUT             1700
#define IDD_DIALOGABOUT       1800
#define IDD_GROUP             1805
#define IDD_OK                1810
#define IDD_CANCEL            1815

struct FileData
 wchar_t*                     pFile;
 wchar_t*                     pFileTitle;
 wchar_t*                     pFilter;
 wchar_t*                     pIniDir;
 wchar_t*                     pDefExt;

struct                        WndEventArgs
 HWND                         hWnd;
 WPARAM                       wParam;
 LPARAM                       lParam;
 HINSTANCE                    hIns;

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

LRESULT fnWndProc_OnCreate    (WndEventArgs&);
LRESULT fnWndProc_OnCommand   (WndEventArgs&);
LRESULT fnWndProc_OnPaint     (WndEventArgs&);
LRESULT fnWndProc_OnDestroy   (WndEventArgs&);

const EVENTHANDLER            EventHandler[]=
 {WM_CREATE,                  fnWndProc_OnCreate},
 {WM_COMMAND,                 fnWndProc_OnCommand},
 {WM_PAINT,                   fnWndProc_OnPaint},
 {WM_DESTROY,                 fnWndProc_OnDestroy}

#endif // FORM6_H_INCLUDED