5. Introducing resources; menus
Resources are pre-defined bits of data stored in binary format inside your executable file. You create resources using a resources script, a file with an extension of ".rc". Essentially this helps you to gather together in one place the information about the visual appearence of your windows, and their use of controls, icons, and menus. You invent a set of id numbers to label everything in the resource file, and then you use these labels in your C++ code.
Mostly people create the text of the resource file by using a visual resources editor. This lets you set things up using the mouse and a set of menus and dialogs. However, you will naturally want to know how your program works, and therefore you will want to take a look at the resources file. Also, you may sometimes need to edit it by hand. Everything in this tutorial introduction can easily be written by hand.
Obviously, resources are not part of the C++ standard, and their definition has not yet completely settled down. The result is that different compilers may handle them differently. However, the main points are stable and that will be enough to get you started.
As our first example of the use of resources, we will add a simple menu to the main window of a program. Create a file called "tutorial.rc" containing the following text:
// This is tutorial.rc, the resource file for tutorial #include "tutorial_id.h" IDR_MYMENU MENU DISCARDABLE { POPUP "&File" { MENUITEM "&Load", IDM_FILE_LOAD MENUITEM "&Save", IDM_FILE_SAVE MENUITEM "E&xit", IDM_FILE_EXIT } POPUP "&Stuff" { MENUITEM "&Go", IDM_STUFF_GO MENUITEM "&New", IDM_STUFF_NEW } }
The & symbol in the menu item names identifies a keyboard letter that can be used (as an alternative to a mouse click) to select the item from the menu. My choice of id names is "IDR..." for a resource, "IDM..." for a menu item, and later we will use "IDC..." for a control. N.B. some compilers use BEGIN and END instead of { and } in resources files.
You also need to add the following to the header file tutorial_id.h:
#define IDR_MYMENU 2000 #define IDM_FILE_LOAD 2001 #define IDM_FILE_SAVE 2002 #define IDM_FILE_EXIT 2003 #define IDM_STUFF_GO 2004 #define IDM_STUFF_NEW 2005
Your code should now compile and run, but nothing new will appear in the executing program until we add some code to deal with the menus. The first thing you need is to go to WinMain and where up till now we had wc.lpszMenuName = NULL;, now you need
wc.lpszMenuName = MAKEINTRESOURCE(IDR_MYMENU);
switch(LOWORD(wParam)) { case IDM_FILE_EXIT: PostMessage(hwnd, WM_CLOSE, 0, 0); return 0; case IDM_FILE_LOAD: // ... here add a call to a function handling this case return 0; case IDM_FILE_SAVE: // ... here add a call to a function handling this case return 0; case IDM_STUFF_GO: // ... here add a call to a function handling this case return 0; case IDM_STUFF_NEW: // ... here add a call to a function handling this case return 0; }
6. The mighty dialog
Windows applications mostly work via a bunch of dialog boxes. Each dialog is a window, but Windows offers friendly features to make them easier to work with than the basic window. They are a slightly higher-level idea. For example, you typically define them to possess controls and windows takes care of setting up the controls.
There are two types of dialog:
- "Modal": hogs the program thread and does not return until the dialog is closed. This is called a modal dialog. Uses DialogBox(), EndDialog(). Implements its own message loop (you just handle the messages) and its own ShowWindow.
- "Modeless": sets itself up and returns immediately, sharing the processor with other windows. This is called a modeless dialog. Uses CreateDialog(), DestroyWIndow(). You adjust the messsage loop, e.g. using IsDialogMessage(), and you use ShowWindow.
In either case you provide a message handler, i.e. a function which behaves like WndProc. Its name is passed in the setting-up call to DialogBox or CreateDialog, so that Windows knows who you gonna call. Here are some guidelines:
- You handle WM_INITDIALOG instead of WM_CREATE. (You can handle WM_CREATE as well, but it is sent before any of the controls have been created. Usually WM_INITDIALOG is sufficient and more convenient).
- Return TRUE for messages you do process and FALSE for messages you don't, UNLESS the message specifies you return something else.
- DO NOT call DefWindowProc() for messages you don't handle. Just return FALSE and let windows take care of it.
- DialogBox() returns an integer, that you specify in the call to EndDialog(hwnd, returnvalue). CreateDialog() returns a window handle to the dialog.
Here are the steps you typically undertake in order to add a dialog to your program:
- Put a description of the dialog and its controls in your .rs file.
- The .rs file should #include "afxres.h" and also #include your header file where you #define your id numbers. (This may be compiler-specific. If "afxres.h" does not work, try "winres.h" or <winres.h>.)
- Add the relevant #defines to your resource id header file (e.g. resource.h or myprog_id.h)
- Write the dialog message handler, calling it something like mynameDlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam). Often it would have its own cpp file, but you might want to put a few small handlers in one file, or indeed write a generic dialog class.
- Make sure your code that refers to your dialog handler (mynameDlgProc) can see a declaration of it. I do this by putting such a declaration in my resources.h (or id.h) file.
- Decide where in your program execution you want the dialog to pop up. For example, it could be at WM_CREATE, or in response to a menu item selection. At that point insert a call to DialogBox or CreateDialog (see below for the syntax).
- In the case of a modal dialog (one that hogs the thread until you dismiss it), you probably want to write some code to react to the return value, just after the call to DialogBox. After you have done that the modal dialog is now finished and ready to roll.
- In the case of a modeless dialog (one that has an independent existence and runs alongside your other processes) you are not done yet. First, after the call to CreateDialog you probably want to call ShowWindow(ghMydlg, SW_SHOW), where ghMydlg is the handle to your dialog, given to you by CreateDialog. You may want to declare this global, or think about where it needs to be made available. You should of course check whether this handle is NULL, signalling that the creation failed. If somewhere else you want this dialog to disappear but you don't want to destroy it, then use ShowWindow(ghMydlg, SW_HIDE).
-
A good user will destroy anything he/she created, so you need a call to DestroyWindow(ghMydlg) somewhere.
One possible place is at the closure of the main program window. So in WinProc (NOT your ...DlgProc),
at the WM_DESTROY message, insert
This should be BEFORE PostQuitMessage(0). You should also consider whether the user can launch several copies of your dialog, and if so you should write the code so that you can keep track of them all and destroy them all.
DestroyWindow(ghMydlg);
-
Your program should now compile and run, and it should have most of its functionality.
Some features are not yet present however, such as tabbing
between buttons in the modeless dialog, or using keyboard shortcuts. This is because
the modeless dialog box is not yet fully "in the loop" to receive all messages. To finish,
go back to WinMain and modify the message loop there. The simplest thing to do is to
insert an if statement with a call to IsDialogMessage:
while ( GetMessage(&Msg, NULL, 0, 0) ) { if ( !IsDialogMessage(ghGo, &Msg) ) // give a modeless dialog a chance to react { TranslateMessage(&Msg); DispatchMessage(&Msg); } }
- If you have several modeless dialogs going at once, you can for example replace the if (!IsDialogMessage(...)) statement by a loop which sends the message to each, until one is found that processes it.
A simple example program should make all this clear.
Finally, your windows program can take advantage of the "higher level" nature of dialog boxes to allow a much shorter main program code. Here is a "shortest ever" windows program, to show you the essential ideas.
6.1 Summary of resource and dialog information
Menus:
// example menu: // in resource.h header file: #define IDR_MYMENU 100 #define IDM_FILE_LOAD 110 //... // in .rc resources file: #include "resource.h" IDR_MYMENU MENU DISCARDABLE { POPUP "&File" { MENUITEM "&Load", IDM_FILE_LOAD } } // the menu sends the message WM_COMMAND with LOWORD(wParam) = IDM_FILE_LOAD // in WinMain: wc.lpszMenuName = MAKEINTRESOURCE(IDR_MYMENU);
Dialogs:
// in resource.h, in addition to all the #defines: BOOL CALLBACK aboutDlgProc(HWND, UINT, WPARAM, LPARAM); BOOL CALLBACK goDlgProc(HWND, UINT, WPARAM, LPARAM); extern HWND ghGo; // global handle to dialog "Go" // in .rc resources file: #include "resource.h" #include "afxres.h" // ... and see example code for further information // setting up int ret = DialogBox(GetModuleHandle(NULL), MAKEINTRESOURCE(DLG_ABOUT), hwnd, aboutDlgProc); HWND ghGo; ghGo = CreateDialog(GetModuleHandle(NULL), MAKEINTRESOURCE(DLG_GO), hwnd, goDlgProc); if (ghGo) ShowWindow(ghGo, SW_SHOW); // running // DlgProc returns true for messages you do handle, false for those you do not. // For the other case (CreateDialog) you need: if( !IsDialogMessage(ghGo, &Msg) ) // in WinMain message loop // closing EndDialog(hwnd, return_value); return true; // to close a modal dialog DestroyWindow(ghGo); // to close a modeless, e.g. in WndProc, WM_DESTROY // if you created global or static things within your dialog code, you also need to // destroy them, and this could be done by processing the WM_DESTROY message in // your DlgProc // the shortest useful windows program? int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR lpCmdLine, int nCmdShow) { return DialogBox(hInst, MAKEINTRESOURCE(DLG_MAIN), NULL, myDlgProc); }