Modifying the System Menu in MFC

Introduction

The system menu is a standard feature of every Windows application. It is managed by Windows so normally we don’t have to worry about it at all. However sometimes it is nice to be able to modify that menu according to our own program with things that Windows can’t automatically do for us.

As my main example I will be using a tool I’ve created called BabelOn. This is a C++/MFC program that accesses a web-service to translate text into foreign languages. It uses a toolbox window, so we don’t see the system menu icon in the upper-left corner, however the menu can still be accessed with Ctrl-Space or by right-clicking on the title bar. The menu has been modified to contain two extra commands: About BabelOn, and Exit. Exit is needed because the default action for Close (Alt-F4) has been overridden in the program to hide the window and allow tray access.

Adding Commands

First we need to define a unique variable to represent each menu item. This can be done in the Resource.h file, or in any standard header file. If the commands already exist as part of another standard or context menu, then this step can be skipped because the definitions already exist. However, it’s important to note that even if we use the pre-existing definitions, the message handlers for the commands on the regular menu will NOT be automatically called. System commands are routed differently. So, in the end, it doesn’t matter if we use the existing or define our own.

For our example, we’ll define two:

[code]
#define IDM_ABOUT 16
#define IDM_EXIT 17
[/code]

The IDM just means this is a menu-item ID.

We add these commands in our window’s initializing function ( OnInitDialog(), OnCreate() ). My example is in a dialog class, so this is what the function looks like:

[code lang=”cpp”]
BOOL CBabelOnDlg::OnInitDialog()
{
CDialog::OnInitDialog();
// Add “About…” and “Exit” menu items to system menu.
// Command IDs must be in system range
// the ANDing is because of a bug in Windows 95
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX < 0xF000);
ASSERT((IDM_EXIT & 0xFFF0) == IDM_EXIT);
ASSERT(IDM_EXIT < 0xF000);
CMenu* pSysMenu = GetSystemMenu(FALSE);
if (pSysMenu != NULL) {
pSysMenu->AppendMenu(MF_STRING,IDM_EXIT,”E&xit Program”);
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, “A&bout BabelOn”);

}
. . .
//other initialization
[/code]

The first thing you should notice is a couple of ASSERT statements for each command. The first one deals with a bug present in Windows 95 and the second ensures the custom command is below the range used by pre-defined system commands. From the MFC documentation: “All predefined Control-menu items have ID numbers greater than 0xF000. If an application adds items to the Control menu, it should use ID numbers less than F000.”

Next we get a pointer to the system menu with the GetSystemMenu. We call it with an argument of FALSE to get the pointer. If we call it with TRUE, then it will reset the menu to its default state.

If the pointer is valid, we call some commands to add to the bottom of the menu, passing the IDs and the string we want to show up when the menu is viewed.

Simple, isn’t it?

Processing Custom Commands

In order to have those commands do anything, we can’t rely on the normal message-handling mechanism, even if we have handlers for the same items in other menus. We have to handle the WM_SYSCOMMAND message in our dialog/window class.

[code lang=”cpp”]
void CBabelOnDlg::OnSysCommand(UINT nID, LPARAM lParam)
{
//trap our own system menu messages
if ((nID & 0xFFF0) == IDM_ABOUTBOX)
{
CAboutDlg dlgAbout; dlgAbout.DoModal();
} else if ((nID & 0xFFF0)==SC_CLOSE)
{
OnClose();
} else if ((nID & 0xFFF0)==IDM_EXIT)
{
::PostQuitMessage(0);
} else {
CDialog::OnSysCommand(nID, lParam);
}
}
[/code]

This is the height of simplicity herself. We compare the code that was passed in with the system to our commands, again ANDing them to account for the Windows 95 bug. Then call whatever code we want to handle it.

If we selected Exit, then we post a message to quit the application.

Take a look at the second test: SC_CLOSE is a predefined menu constant. It’s one normally handled by Windows, but we can still add custom processing to it if we want. In this application, I didn’t want it to exit, but to merely hide the application (with an icon in the tray). So I just call a handler that I wrote that does that. If the ID doesn’t equal anything we want to custom-process, we just pass it on up the hierarchy for default processing.

The IDs for the most common system commands are:

SC_CLOSE – Close the CWnd object.
SC_MAXIMIZE (or SC_ZOOM) – Maximize the CWnd object.
SC_MINIMIZE (or SC_ICON) – Minimize the CWnd object.
SC_MOVE – Move the CWnd object.
SC_RESTORE – Restore window to normal position and size.
SC_SIZE – Size the CWnd object.

There are others in special situations that you can learn about in the documentation for WM_SYSCOMMAND.

Modifying Existing Commands

We just saw that it’s possible to change the default handling of built-in system commands, but it’s also possible to modify existing menu items by changing their text, or to entirely remove them.

To modify the text of a command, use the ModifyMenu() function on pSysMenu in the above example. For example, to change “Close” to “Hide”, I could do this:
[code lang=”cpp”]
pSysMenu->ModifyMenu(SC_CLOSE, MF_BYCOMMAND,IDM_HIDE, “&Hide”);
[/code]

The MF_BYCOMMAND argument tells the function to interpret SC_CLOSE as a command ID. IDM_HIDE is a new command ID, and then comes the text we want to show.

Alternatively, we can call ModifyMenu() on the ith menu item:

[code lang=”cpp”]
pSysMenu->ModifyMenu(0,MF_BYPOSITION,IDM_HIDE,”&Hide”);
[/code]

This changes the first menu item to Hide.

Removing commands

Don’t want a close command at all on the window? Not sure if this is a good idea or not, but you can do it.

Use this method:

[code lang=”cpp”]
pSysMenu->RemoveMenu(SC_CLOSE,MF_BYCOMMMAND); pSysMenu->RemoveMenu(0,MF_BYPOSITION);
[/code]

The first one removes the command associated with SC_CLOSE, while the second removes the first item on the menu.

Conclusion

Modifying the system menu in this way has limited application but it is sometimes useful for small applications and utilities that have a limited menu structure otherwise. It can be useful for commands you want to be easily accessible from the task bar–when a program is is in the background or minimized, right-clicking on it’s icon on the task bar will bring up the system menu.

Something should be said, however, about the wisdom of certain modifications. Removing or modifying functionality that user expects is usually a bad idea. It makes your program less usable and friendly compared to other applications. UI guidelines and standard look and feel exist for a good reason!

Enjoy playing with this!

©2004 Ben Watson

2 thoughts on “Modifying the System Menu in MFC

  1. Pingback: Dialog Minimize button ? | keyongtech

Comments are closed.