C++/Win32: Menus
March 2, 2008
Let’s take a detour here and talk about menus. If you are new to Windows and you’re learning this all for the first time here, you have no doubt been rather impatient with me for taking so long to get to menus. After all, there’s hardly a Windows app out there that doesn’t have a menu, right?
Right. So let’s stick a menu in here.
Menus are resources, and as such, we get to use the resource editor to create them.
1. From the Solution Explorer, double click your .rc file.
2. Now in the Resource View, right-click your .rc folder, and select Add Resource.
3. Select Menu and then New. Your resource will be created, and the menu editor will open.
Your resource is given the name IDR_MENU1 as a default.
Creating a menu is ridiculously simple. You are immediately presented with your first top-level menu, which should be highlighted, containing the text Type Here. Let’s start by creating a File menu, since every application known to man has one (well, pretty much). Notice that as soon as you begin typing in your text, a submenu appears, as well as one more top-level menu. You should have an idea of what to do now. Create a Close option under File, and three more menus: Output, Appearance, and About. Then create the submenus Output->Show Open, Output->Show Closed, Appearance->Skinned Mode, and About->About Glowdot Port Scanner.
If you want certain letters in the menu text to be underlined (indicating a shortcut is available), precede the letter with an ampersand character. For example, if you make the text menu have the text &File, it will read File.
Close up your menu and save all.
Now, we have to add the menu to our window. To do this, we’ll have to modify the call to CreateWindow in Win32WrapperClass. Of course, we don’t want to add our menu there, because we want to keep the class reusable. Instead, we will have to figure out a way to get the menu handle to our menu into the inner workings of the class. This is a design decision — the only requirement is we have to get it in there before the call to CreateWindow (or RegisterClass, as there are two places you can add your menu). Here’s an example of why it would have been a good idea to have two separate functions, Register and Create to handle window registration and creation, because we could just overload the Register function by adding a Register function that takes an HMENU as an argument.
We could also further override our Create function, adding two more versions, to accept an HMENU.
And still a third option, and the one we’ll go with, is to modify our constructor to take an HMENU. If the user doesn’t want a menu, he should pass in NULL. We’ll need a handle to a menu as a member variable to keep track of the menu (or NULL, as the case may be) and we can reference this member HMENU when Create is called to assign the menu during the call to RegisterClassEx.
Keep in mind that when we add this new parameter to the Win32WrapperClass constructor, we will also have to add it to the PortScannerWindow constructor as well.
Here are the new declarations:
In Win32WrapperClass.h:
Win32WrapperClass(char *ClassName, char *WindowTitle, DWORD dwStyles, RECT *rect, HMENU hMenu, HINSTANCE hInstance);
In PortScannerWindow.h:
PortScannerWindow(char *ClassName, char *WindowTitle, DWORD dwStyles, RECT *rect, HMENU hMenu, HINSTANCE hInstance);
In both cases, don’t forget to also update the function definitions in the corresponding .cpp files!
We will also need to add the following variable to the protected members of Win32WrapperClass:
HMENU m_hMenu;
We also need to update the code in each constructor by adding the following line:
m_hMenu = hMenu;
It will then be the class user’s responsibility to load the menu into a local HMENU handle and pass that handle to our constructor. This is done in WinMain:
HMENU hMenu=LoadMenu(hInstance, MAKEINTRESOURCE(IDR_MENU1));
pWindow = new PortScannerWindow("GDPSv1","Glowdot Port Scanner",WS_OVERLAPPEDWINDOW|WS_VISIBLE,
&dimensions, hMenu, hInstance);
And finally, we need to modify the Create functions of the Win32WrapperClass class to include the menu, if we have one:
// send the this pointer as the window creation parameter m_hWnd = CreateWindowEx (WS_EX_CLIENTEDGE, m_pClassName,m_pWindowTitle, m_dwStyles, m_pRect->left, m_pRect->top, m_pRect->right-m_pRect->left, m_pRect->bottom-m_pRect->top, NULL, m_hMenu, m_hInstance, (void *)this);
If you compile and run now, you’ll be disappointed to see the menu at the top of your skinned app. Unskin it, and it will look fine. But we need to add something to our Skin function to hide the menu. We’ll then need to add something to our Unskin function to show the menu.
To hide the menu:
SetMenu (m_hWnd, NULL);
To unhide the menu:
SetMenu (m_hWnd, m_hMenu);
Take a look at the Regionize and Unskin functions in this lesson’s code to see it in place.
Now we only need to know how to handle menu selections. We’ll first handle the Appearance->Skinned Mode menu option, so we can finally be done with this skinning nonsense.
As it turns out, menus, like buttons, send a WM_COMMAND message to your window’s message handler when clicked. You will need to analyze the low-order word of the wParam parameter to find the identifier of the menu option selected. Visual Studio has a convenient way of assigning default resource IDs to your menu options when you create your menus in the resource editor. The convention is ID_MENU_SUBMENU_SUBSUBMENU_…. Look at resource.h and you’ll see our Appearance->Skinned Mode option was given the ID ID_APPEARANCE_SKINNEDMODE. That’s the first value we’ll look for in our message handler.
case ID_APPEARANCE_SKINNEDMODE:
if (m_bSkinned) {
m_bSkinned=FALSE;
Unskin();
} else {
m_bSkinned=TRUE;
Regionize();
}
break;
I’ve also made a quick change to BuildGUI that you might want to look at. I’ve resized the IDT_OUTPUT edit control to account for the space the menu takes up in the window. I made it 30 units shorter.
Now let’s handle checking and unchecking menu options. We have Output->Show Closed and Output->Show Open, which we would like to have checked (indicating active) or unchecked (indicating inactive) so the user can consult the menu to see what option he/she has set. We will set the checked/unchecked value when we capture each menu option’s WM_COMMAND message, at the same time that we set the output flags on or off.
First, we’ll need to add two BOOL values to our PortScannerWindow class: m_bShowOpen and m_bShowClosed. We will also need to initialize them to TRUE in our constructor.
Next, we’ll need to alter the scan function, goScan, to only show the results we desire. The relevant section of goScan becomes:
if(lConnect != 0)
{
// On connect failure:
// Output result of this scan
if (bShowClosed) {
sprintf(buffer,"%s:%d - Closed\r\n",IPAddress,i);
nTxtLen = GetWindowTextLength(GetDlgItem(m_hWnd,IDT_OUTPUT));
SendMessage(GetDlgItem(m_hWnd,IDT_OUTPUT), EM_SETSEL, nTxtLen, nTxtLen); // move caret to end
SendMessage(GetDlgItem(m_hWnd,IDT_OUTPUT), EM_REPLACESEL, 0, (LPARAM)buffer); // append text
SendMessage(GetDlgItem(m_hWnd,IDT_OUTPUT), EM_SCROLLCARET, 0, 0); // scroll to caret
}
} else {
// On connect success:
// Output result of this scan
if (bShowOpen) {
sprintf(buffer,"%s:%d - Open\r\n",IPAddress,i);
SendMessage(GetDlgItem(m_hWnd,IDT_OUTPUT), EM_SETSEL, -1, -1); // move caret to end
SendMessage(GetDlgItem(m_hWnd,IDT_OUTPUT), EM_REPLACESEL, 0, (LPARAM)buffer); // append text
SendMessage(GetDlgItem(m_hWnd,IDT_OUTPUT), EM_SCROLLCARET, 0, 0); // scroll to caret
}
}
We must also initialize each menu option to checked, probably in our constructor, using the CheckMenuItem function as follows:
CheckMenuItem(m_hMenu, ID_OUTPUT_SHOWOPEN, MF_CHECKED); CheckMenuItem(m_hMenu, ID_OUTPUT_SHOWCLOSED, MF_CHECKED);
And now we can finally handle the menu events. Again, we are just waiting for WM_COMMAND messages, where the low-order word of wParam is either ID_OUTPUT_SHOWOPEN or ID_OUTPUT_SHOWCLOSED.
Here are our two new cases:
case ID_OUTPUT_SHOWOPEN:
if (!m_bShowOpen) {
CheckMenuItem(m_hMenu,ID_OUTPUT_SHOWOPEN,MF_CHECKED);
m_bShowOpen = TRUE;
} else {
CheckMenuItem(m_hMenu,ID_OUTPUT_SHOWOPEN,MF_UNCHECKED);
m_bShowOpen = FALSE;
}
break;
case ID_OUTPUT_SHOWCLOSED:
if (!m_bShowClosed) {
CheckMenuItem(m_hMenu,ID_OUTPUT_SHOWCLOSED,MF_CHECKED);
m_bShowClosed = TRUE;
} else {
CheckMenuItem(m_hMenu,ID_OUTPUT_SHOWCLOSED,MF_UNCHECKED);
m_bShowClosed = FALSE;
}
break;
Now, compile and run this thing. Flip back and forth between skinned and unskinned mode. Do it while it’s scanning, even. Notice also that when you disable output of either open or closed results, the effect is immediate, even while scanning.
You have to admit we’ve already got a pretty hectic little application here, without too much effort. Just handle the rest of the existing menu options — pop up a MessageBox on About->About Glowdot Port Scanner — and we can move on to making our unskinned mode look just a little bit better.
Next: Polishing the GUI.
Source Code:
win32tut_part10.zip[4.66MB zipped]
Additional Information:
· TheForger’s Win32 API Tutorial
Further Reading:
· Nitty Gritty Windows Programming with C++ by Henning Hansen.
· Thinking in C++ by Bruce Eckel.




Comments
Got something to say?