<-- [prev] . [next] -->
Quick introduction to Windows API

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
Note, these have to be #define, unfortunately const int does not work (well it doesn't on my compiler anyway). You can pick id numbers between 1 and 65535 or some such high number, so there is plenty of room. The file with the id #defines in it is often called resource.h.

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);
Run the program, and now, as if by magic, you have a couple of drop-down menus! You can point and click in them, but of course nothing happens yet. Your clicks generate the message WM_COMMAND, with wParam identifying the menu and item. For example, in your code that responds to WM_COMMAND, you should add
      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;
      }
If you created a function atcommand for WM_COMMAND message handling, it is fine to place this code there.

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:

  1. "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.
  2. "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:

Here are the steps you typically undertake in order to add a dialog to your program:

  1. Put a description of the dialog and its controls in your .rs file.
  2. 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>.)
  3. Add the relevant #defines to your resource id header file (e.g. resource.h or myprog_id.h)
  4. 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.
  5. 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.
  6. 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).
  7. 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.
  8. 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).
  9. 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
    DestroyWindow(ghMydlg); 
    
    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.
  10. 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);
          }
       }
    
  11. 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);
}
<-- [prev] . [this] . [next] -->