modula-2 home

  Home  
  Tutorial  
  Win32 API  
  Reference  
  Projects  
 

 

Menus and Icons

Example: menu_one

[images/menu_one.gif]

This is just a small section to show how to add basic menus to your window. We use the resource file created in the previous chapter.

For this example you can start with the window code from simple_window and add some new code.

By means of the following directive we instruct the environment to link the resource file with our program.

    <*/Resource:menu_one.res*>

We also import the identifiers from the resource definition file:

    FROM resource IMPORT IDR_MYMENU,IDI_MYICON,ID_FILE_EXIT,ID_STUFF_GO;

The easiest way to attach the menu and icon to your window is to specify them when you register the window class, like this:

    wc.lpszMenuName := MAKEINTRESOURCE(IDR_MYMENU);
    wc.hIcon        := LoadIconId(Instance,IDI_MYICON); (* LoadIconId from WINX *)

The lpszMenuName field of the Window class wc record is of type LPCSTR which basically is a POINTER to an array of characters. The MAKEINTRESOURCE procedure will convert our identifier IDR_MYMENU into a value of type LPCSTR so we can assign it to lpszMenuName.

Now compile the program and see what happens. Your window should now have a File and Stuff menu with the respective items underneath. That is assuming your .res file was properly compiled and linked into your program (again, see compiler notes).

The icon in the top left of the window and on the task bar should now display the custom icon that we specified.

Example: menu_two

An alternative to using a menu resource is to create one on the fly (or when your program runs). This is a bit more work programming wise, but adds flexibility and is sometimes necessary.

You can also use icons that aren't stored as resources, you could choose to store your icon as a seperate file and load it at runtime. This would also give you the option of allowing the user to select an icon of their choice with the common dialogs discussed later, or something to that effect.

Start again from simple_window without the .h or .rc added. Now we will handle the WM_CREATE message and add a menu to our window.

CONST
    ID_FILE_EXIT = 9001;
    ID_STUFF_GO  = 9002;

Next we add the following code into our WndProc procedure

VAR hMenu, hSubMenu : HMENU;
    hIcon           : HICON;
    | WM_CREATE  : hMenu := CreateMenu();

                   hSubMenu := CreatePopupMenu();
                   FUNC AppendMenu(hSubMenu, MF_STRING, ID_FILE_EXIT, "E&xit");
                   FUNC AppendMenu(hMenu, MF_STRING BOR MF_POPUP,
                                   CAST(UINT,hSubMenu), "&File");

                   hSubMenu := CreatePopupMenu();
                   FUNC AppendMenu(hSubMenu, MF_STRING, ID_STUFF_GO, "&Go");
                   FUNC AppendMenu(hMenu, MF_STRING BOR MF_POPUP,
                                   CAST(UINT,hSubMenu), "&Stuff");

                   FUNC SetMenu(hwnd, hMenu);

                   hIcon := LoadImage(NIL, "menu_two.ico",
                                      IMAGE_ICON, 32, 32, LR_LOADFROMFILE);
                   IF hIcon#NIL THEN
                     FUNC SendMessage(hwnd, WM_SETICON, ICON_BIG, CAST(LPARAM,hIcon));
                   ELSE
                     FUNC MessageBox(hwnd, "Could not load icon!",
                                     "Error", MB_OK BOR MB_ICONERROR);
                   END;

This creates a menu almost the same as the one we had in the resource and attaches it to our window. A menu that is assigned to a window is automatically removed when the program terminates, so we don't need to worry about getting rid of it later. If we did though, we could use GetMenu() and DestroyMenu().

The code for the icons is pretty simple, we call LoadImage() twice, to load the icon as both a 16x16 size and a 32x32 size. We can't use LoadIcon() at all because it will only load resources, not files. We specify NIL for the instance handle parameter because we aren't loading a resource from our module, and instead of a resource ID we pass in the name of the icon file we want to load. Finally, we pass in the LR_LOADFROMFILE flag to indicate that we want the function to treat the string we give it as a filename and not a resource name.

If each call succeeds we assign the icon handle to our window with WM_SETICON, and if it fails we pop up a message box letting us know something went wrong.

NOTE: that the LoadImage() calls will fail if the icon file isn't in the current working directory of the program.

OK now that we have our menu, we need to make it do something. This is pretty simple, all we need to do is handle the WM_COMMAND message. Also we'll need to check which command we are getting and act accordingly. Now our WndProc() should look something like this.

PROCEDURE WndProc(hwnd : HWND; msg : UINT;
                  wParam : WPARAM;  lParam : LPARAM): LRESULT [EXPORT, OSCall];
VAR hMenu, hSubMenu : HMENU;
    hIcon           : HICON;
BEGIN
    CASE msg OF
    | WM_CREATE  : hMenu := CreateMenu();

                   hSubMenu := CreatePopupMenu();
                   FUNC AppendMenu(hSubMenu, MF_STRING, ID_FILE_EXIT, "E&xit");
                   FUNC AppendMenu(hMenu, MF_STRING BOR MF_POPUP,
                                   CAST(UINT,hSubMenu), "&File");

                   hSubMenu := CreatePopupMenu();
                   FUNC AppendMenu(hSubMenu, MF_STRING, ID_STUFF_GO, "&Go");
                   FUNC AppendMenu(hMenu, MF_STRING BOR MF_POPUP,
                                   CAST(UINT,hSubMenu), "&Stuff");

                   FUNC SetMenu(hwnd, hMenu);

                   hIcon := LoadImage(NIL, "menu_two.ico",
                                      IMAGE_ICON, 32, 32, LR_LOADFROMFILE);
                   IF hIcon#NIL THEN
                     FUNC SendMessage(hwnd, WM_SETICON, ICON_BIG, CAST(LPARAM,hIcon));
                   ELSE
                     FUNC MessageBox(hwnd, "Could not load icon!",
                                     "Error", MB_OK BOR MB_ICONERROR);
                   END;

    | WM_CLOSE   : FUNC DestroyWindow(hwnd);
    | WM_DESTROY : PostQuitMessage(0);
    | WM_COMMAND :
      CASE LOWORD(wParam) OF
      | ID_FILE_EXIT :
      | ID_STUFF_GO  :
      ELSE RETURN DefWindowProc(hwnd, msg, wParam, lParam);
      END;
    ELSE RETURN DefWindowProc(hwnd, msg, wParam, lParam);
    END;
    RETURN 0;
END WndProc;

As you can see we've got our WM_COMMAND all set up, and it even has another CASE in it. This CASE's on the value of the low word of wParam, which in the case of WM_COMMAND contains the control or menu id that sent the message.

We obviously want the Exit menu item to close the program. So in the WM_COMMAND, ID_FILE_EXIT handler you can use the following code to do just that.

    FUNC PostMessage(hwnd, WM_CLOSE, 0, 0);

And we want the menu command ID_STUFF_GO to result in a message:

    FUNC MessageBox(hwnd, "You clicked Go!", "Woo!", MB_OK);

Your WM_COMMAND handler should now look like this:

    | WM_COMMAND :
      CASE LOWORD(wParam) OF
      | ID_FILE_EXIT : FUNC PostMessage(hwnd, WM_CLOSE, 0, 0);
      | ID_STUFF_GO  : FUNC MessageBox(hwnd, "You clicked Go!", "Woo!", MB_OK);
      ELSE RETURN DefWindowProc(hwnd, msg, wParam, lParam);
      END;

The program file icon

You may have noticed that the menu_one.exe file now shows up as the custom icon we added as a resource, whereas the menu_two.exe file does not, since we are loading an external file. Windows Explorer simply displays the first icon (numerically by ID) in the program files resources, so since we only have one icon, that's what it is displaying. If you want to be sure that a certain icon is displayed with your program file, simply add it as a resource and assign it a very low ID... like 1. You don't even need to refer to the file in your program, and you can load completely different icons for your windows if you choose.

 


Copyright © 1998-2011, Brook Miles. All rights reserved. Adapted for Modula-2 by Frank Schoonjans, with permission.