ObjReader Community
Frederick Harris section => TCLib => Topic started by: Frederick Harris on October 14, 2021, 09:48:54 pm
-
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....
// Resources.rc
#include "Form6.h"
MainMenu MENU DISCARDABLE
BEGIN
POPUP "&File"
BEGIN
MENUITEM "&Open...", IDM_FILE_OPEN
MENUITEM "&Save...", IDM_FILE_SAVE
MENUITEM SEPARATOR
MENUITEM "E&xit", IDM_FILE_EXIT
END
POPUP "O&ptions"
BEGIN
MENUITEM "&Explorer", IDM_OPTIONS_EXPLORER
END
POPUP "&Help"
BEGIN
MENUITEM "&About", IDM_ABOUT
END
END
dlgAbout DIALOG DISCARDABLE 100, 100, 239, 66
STYLE 0x80L | 0x80000000L | 0x00C00000L | 0x00080000L
CAPTION "My About Box"
FONT 8, "MS Sans Serif"
BEGIN
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
// 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
Resources.rc.
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
C:\Code\Forms\Form6>
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...
Object.Property_1
Object.Property_2
Object.Property_3
.....
Object.Priperty_n
In Form6.h you'll find this...
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()...
pFileData=(FileData*)HeapAlloc(hHeap,HEAP_ZERO_MEMORY,sizeof(FileData));
...I allocate memory for a FileData object, and with this line immediately afterward....
SetWindowLongPtr(Wea.hWnd,1*sizeof(void*),(LONG_PTR)pFileData);
...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)....
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...
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.
// 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
#endif
#ifndef _UNICODE
#define _UNICODE
#endif
#include <windows.h>
#include <commdlg.h>
#ifdef TCLib
#include "string.h"
#else
#include <string.h>
#endif
#include "Form6.h"
#pragma warning(disable:4996)
INT_PTR CALLBACK AboutDlgProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam)
{
switch(Message) // Dialog Procedure
{
case WM_INITDIALOG:
return TRUE;
case WM_COMMAND:
switch(LOWORD(wParam))
{
case IDOK:
EndDialog(hwnd, IDOK);
break;
case IDCANCEL:
EndDialog(hwnd, IDCANCEL);
break;
}
break;
default:
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.
pFileData->pFile=(wchar_t*)HeapAlloc(hHeap,HEAP_ZERO_MEMORY,512);
if(!pFileData->pFile)
goto Bad_Ending;
pFileData->pDefExt=(wchar_t*)HeapAlloc(hHeap,HEAP_ZERO_MEMORY,16);
if(!pFileData->pDefExt)
goto Bad_Ending;
pFileData->pFileTitle=(wchar_t*)HeapAlloc(hHeap,HEAP_ZERO_MEMORY,128);
if(!pFileData->pFileTitle)
goto Bad_Ending;
pFileData->pFilter=(wchar_t*)HeapAlloc(hHeap,HEAP_ZERO_MEMORY,128);
if(!pFileData->pFilter)
goto Bad_Ending;
pFileData->pIniDir=(wchar_t*)HeapAlloc(hHeap,HEAP_ZERO_MEMORY,256);
if(!pFileData->pIniDir)
goto Bad_Ending;
return true;
Bad_Ending:
if(hHeap)
{
if(pFileData->pFile)
HeapFree(hHeap,0,pFileData->pFile);
if(pFileData->pDefExt)
HeapFree(hHeap,0,pFileData->pDefExt);
if(pFileData->pFileTitle)
HeapFree(hHeap,0,pFileData->pFileTitle);
if(pFileData->pFilter)
HeapFree(hHeap,0,pFileData->pFilter);
if(pFileData->pIniDir)
HeapFree(hHeap,0,pFileData->pIniDir);
}
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)];
OPENFILENAME ofn;
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...
GetOpenFileName(&ofn);
else // ...otherwise, a GetSaveFileName() call.
GetSaveFileName(&ofn);
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)
hHeap=GetProcessHeap();
if(!hHeap)
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(hHeap)
{
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);
if(pReturn==IDOK)
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];
GetCurrentDirectory(512,szBuffer);
ShellExecute(Wea.hWnd,NULL,szBuffer,NULL,NULL,SW_SHOWNORMAL);
return 0;
}
LRESULT fnWndProc_OnExit(WndEventArgs& Wea)
{
SendMessage(Wea.hWnd,WM_CLOSE,0,0);
return 0;
}
LRESULT fnWndProc_OnCommand(WndEventArgs& Wea)
{
switch(LOWORD(Wea.wParam))
{
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);
case IDM_OPTIONS_EXPLORER:
return fnWndProc_OnOptions(Wea);
case IDM_FILE_EXIT:
return fnWndProc_OnExit(Wea);
}
return 0;
}
LRESULT fnWndProc_OnPaint(WndEventArgs& Wea)
{
wchar_t* pOpenFileName=NULL;
PAINTSTRUCT ps;
HDC hDC=NULL;
hDC=BeginPaint(Wea.hWnd,&ps);
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;
hHeap=GetProcessHeap();
pOpenFileName=(wchar_t*)GetWindowLongPtr(Wea.hWnd,0*sizeof(void*));
if(pOpenFileName)
HeapFree(hHeap,0,pOpenFileName);
pFileData=(FileData*)GetWindowLongPtr(Wea.hWnd,1*sizeof(void*));
if(pFileData)
{
if(pFileData->pDefExt)
HeapFree(hHeap,0,pFileData->pDefExt);
if(pFileData->pFile)
HeapFree(hHeap,0,pFileData->pFile);
if(pFileData->pFileTitle)
HeapFree(hHeap,0,pFileData->pFileTitle);
if(pFileData->pFilter)
HeapFree(hHeap,0,pFileData->pFilter);
if(pFileData->pIniDir)
HeapFree(hHeap,0,pFileData->pIniDir);
HeapFree(hHeap,0,pFileData);
}
PostQuitMessage(0);
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++)
{
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 CALLBACK WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
wchar_t szClass[] = L"Resources";
wchar_t szMenuName[] = L"MainMenu";
WNDCLASSEX wc;
HWND hWnd;
MSG Msg;
wc.lpszClassName = szClass, wc.lpfnWndProc = WndProc;;
wc.cbSize = sizeof(WNDCLASSEX), wc.style = 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);
RegisterClassEx(&wc);
hWnd=CreateWindow(szClass,L"Dialog Engine Dialog Boxes",WS_OVERLAPPEDWINDOW,350,350,425,200,NULL,NULL,hInstance,NULL);
ShowWindow(hWnd,nCmdShow);
while(GetMessage(&Msg, NULL, 0, 0))
{
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
return (int)Msg.wParam;
}
//Form6.h
#ifndef FORM6_H_INCLUDED
#define FORM6_H_INCLUDED
#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_OPTIONS_EXPLORER 1600
#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