C++/Win32: Dialogs
March 2, 2008
Dialogs are wonderful things because they are easy to create, and contain some default behavior that you can avoid coding on your own. They are especially useful for retrieving input from the user.
In this lesson, we’ll create a dialog that will pop up when the user selects the About->About Glowdot Port Scanner menu option. Right now, that causes a message box. So when our dialog is finished, we will call it when the WM_COMMAND message associated with ID_ABOUT_ABOUTGLOWDOTPORTSCANNER is received by our message handler. The dialog will show the author information, an icon, and a link back to glowdot.com.
Fortunately, dialogs are resources, and we’ve seen how simple resource creation is in Visual Studio.
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 Dialog and then New. Your resource will be created, and the dialog editor will open.
Your resource is given the name IDD_DIALOG1 as a default.
I won’t go into detail about how to use the editor — by now you should be pretty comfortable with editing any kind of resource in Visual Studio. But you should be made aware of the Toolbox toolbar on the left (if you don’t see the Toolbox, select View->Toolbox on the menu, or hit ctrl-alt-x to make it appear). The Toolbox contains all the elements you can stick on your dialog by dragging the element and dropping it on the form. We will need only a few of them for our About box, but you should experiment with the Toolbox and the Dialog Editor to learn what else is possible.
For our About box, we will need an OK button, a group box, some static text, and a picture control. Don’t worry about dragging them onto the form just yet.
First, we can size the dialog. Click on the bottom right corner and drag it to a comfortable size. Now look at the dialog properties for this window. Right-click the window and select properties to view the property sheet for this dialog.
Set Caption to false (to hide the title bar) and set Border to thin. Now we just have a grey box that we can fill in with content. The OK button is all the interactivity we will need, so keep it, and delete the Cancel button.
Next, drag a Group Box control onto the window, and size it so that it is centered, and takes up nearly the whole window. This is where our information will go. Move the OK button to the bottom center of the group box.
Now drag a picture control onto the form. If we have a bitmap image loaded in as a resouce, we can set the Type value in the picture control’s property sheet to bitmap, and then specify that the bitmap resource is to be used by selecting the bitmap’s resource ID in the Image property.
Next, drag in a static text control, and position it in the window. Insert some text. Drag in a few more static text controls, and round out the text info. Make one of your static controls contain an URL — we’ll make this one into a hyperlink a little later.
And, well, that’s about all there is to creating an About dialog. It was so easy, that we’ll spend some time doing some more advanced things in a bit. But first, let’s get it to appear when the user selects About->About Glowdot Port Scanner on the menu.
Obviously, the dialog window will need to be created inside our WinProc function, since it will be created in response to a WM_COMMAND message. And creating it will be a piece of cake. The dialog is created with a call to DialogBox. Here’s the case in WinProc:
case ID_ABOUT_ABOUTGLOWDOTPORTSCANNER: // Display a dialog box with program info DialogBox(m_hInstance, MAKEINTRESOURCE(IDD_DIALOG1), m_hWnd, AboutDlgProc); break;
Simple, yes? DialogBox actually returns a value (true or false) that we can capture and use for error checking. Where does this value come from? And while we’re at it, what is AboutDlgProc?
Well, a dialog is a window (surprise!), and as such it must have a Proc function to handle its messages. Unlike the window controls, however, you don’t get one automatically. You’ll have to make your own, and pass it to DialogBox. This shouldn’t be too complicated, as we’ve already created two Proc functions in this app — three if you count the static one for our window wrapper class. What’s one more?
Here’s our AboutDlgProc function, which I’ve included at the top of PortScannerWindow.cpp. I didn’t make it a class member, because I didn’t see any point, but it very well could be a class member.
BOOL CALLBACK AboutDlgProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam)
{
switch(Message)
{
case WM_INITDIALOG:
// Get the desktop window's client rect
HWND desktopHWND;
desktopHWND = GetDesktopWindow();
RECT desktopRect;
GetClientRect(desktopHWND,&desktopRect);
// Move the dialog to a position above and
// to the left of the bottom right corner of the screen
MoveWindow(hwnd, desktopRect.right-450, desktopRect.bottom-325, 380, 255, TRUE);
return TRUE;
case WM_COMMAND:
switch(LOWORD(wParam))
{
case IDOK:
EndDialog(hwnd, IDOK);
break;
}
break;
case WM_LBUTTONDOWN:
SendMessage(hwnd, WM_NCLBUTTONDOWN, HTCAPTION,NULL);
return TRUE;
break;
default:
return FALSE;
}
return TRUE;
}
It should all look pretty familiar. I have made a call to MoveWindow when the WM_INITDIALOG message is received, so that we can dynamically position the dialog box whereever we want. As a style trademark, I like to put my about dialogs at the bottom left of the screen, but you can put it anywhere you want. You could call GetClientRect, for example, to find out the dimensions of the app window, and use those dimensions to calculate the coordinates of the dialog in order to center it inside the window. Whatever, make up your own mind. You could also skip this step completely, and just define the X Pos and Y Pos properties in the dialog’s property sheet in the dialog editor. If you do this, X Pos and Y Pos will define the top left corner of the dialog relative to the top left corner of the parent window.
Also notice that we have handled a WM_COMMAND message for the IDOK button. This handles pressing the OK button by calling EndDialog to destroy the dialog and return the value TRUE.
I’ve also stuck in the same WM_LBUTTONDOWN hack we used for our skinned mode window to allow the about box to be repositioned by the user — necessary since we removed the title bar in the dialog editor.
So you’re probably saying to yourself: and??? Why did we go through all that work just to create a glorified message box? Well, remember that dialogs are best used for receiving input from the user. You can really quickly whip up a GUI using the resource editor, write a simple message handler for the dialog, and then pop up the dialog whenever you need to get a particular piece of information from the user. The most common use of dialogs is opening and saving files — so common, in fact, that Microsoft has already written those dialogs for you. All you have to do is call them up and then process the string that gets returned. We’ll see the file dialogs when we cover file handling in a later lesson.
And, dialogs allow us to do some groovy stylistic stuff that a message box can’t. For example, let’s say I want to put the URL to Glowdot in the about box. I want the user to be able to click on the URL and be taken to his/her default browser, which will automatically load Glowdot. Wouldn’t that be cool? This isn’t easy, but we have a Proc function for our dialog, so we could definitely do it.
And since everyone always wants to do this, let’s do it.
The first question is, how will we let the user know that http://www.glowdot.com is clickable? An obvious answer would be to make it a button, but that’s too easy. My favorite option would be to make it a different color than all the other text. We could also underline it, but underlining links is so 1990s.
We can change the color of the text by catching the WM_CTLCOLORSTATIC message in AboutDlgProc. Static controls send this message to their parent window when they are about to be drawn. The parameters sent with this message are:
wParam: Handle to the device context for the static control window.
lParam: Handle to the static control.
So we’ll have everything we need. We just need to set the font color of the HDC in wParam to some other color (blue, maybe) and that’s it. The text will be drawn blue.
But we will need to assign a resource ID to our static control first. If you look at the property sheet for the static control you want to URLize, you’ll see it has the generic ID IDC_STATIC. That’s the same ID all the static controls get by default, so we’ll be turning the text color of all the statics blue if we use that. So we will need to request a unique ID from Visual Studio, call it IDC_GLOWDOTURL, and assign it to our static control.
So, here’s the case in AboutDlgProc that handles the WM_CTLCOLORSTATIC message:
case WM_CTLCOLORSTATIC:
// Set the color of the text for our URL
if ((HWND)lParam == GetDlgItem(hwnd,IDC_GLOWDOTURL)) {
// we're about to draw the static
// set the text color in (HDC)lParam
SetBkMode((HDC)wParam,TRANSPARENT);
SetTextColor((HDC)wParam, RGB(0,0,255));
return (BOOL)CreateSolidBrush (GetSysColor(COLOR_MENU));
}
return TRUE;
First, we check to see if the static about to be drawn is our URL static control (by comparing lParam to the HWND of IDC_GLOWDOTURL). If so, we know that wParam contains the HDC of the control. So we modify it by calling SetBkMode to set the text background color to TRANSPARENT, and SetTextColor to set the text color to blue (aka RGB 0,0,255). Before we are done, however, we are required to return a brush with which the background color of the static will be drawn. This is tricky, and doing it the wrong way will cause this not to work. First of all, our AboutDlgProc function returns a BOOL, so whatever brush we return will have to be cast to BOOL. That’s easy enough, but what brush do we return? All I did here was return a brush with the COLOR_MENU system color. Why? Because my dialog is the same color as my menu. Getting the brush of the dialog makes sense, but its not as easy as it seems. For now, this works fine.
So, now our URL text looks like a URL. What next? How about changing the cursor to a little hand when the mouse is over it? That’s what happens in your browser, and we are trying to emulate a browser here, so that seems reasonable. To do it, we will need to intercept the WM_SETCURSOR message, determine if the mouse is over the static control, and if so, load a new cursor in. If it isn’t, we’ll have to load the default (arrow) cursor again.
The code:
case WM_SETCURSOR:
POINT pt;
RECT rect;
GetCursorPos(&pt);
GetWindowRect(GetDlgItem(hwnd,IDC_GLOWDOTURL), &rect);
if (PtInRect(&rect, pt)) {
SetCursor(LoadCursor(NULL,IDC_HAND));
} else {
SetCursor(LoadCursor(NULL,IDC_ARROW));
}
return TRUE;
This code should be pretty straightforward. On receiving the WM_SETCURSOR message, we declare a POINT and a RECT structure. The POINT structure will contain the coordinates of the mouse (received by a call to GetCursorPos) and the RECT will contain the window area of IDC_GLOWDOTURL (found by calling GetWindowRect). Then we simply check to see if the point is inside the rect. I love when functions have such obvious names as PtInRect. If it is in the rect, we load the hand cursor (IDC_HAND), and if not, we load the arrow cursor (IDC_ARROW). The reason we have to load the arrow cursor is because if we load the hand cursor, then move off the static, we need to tell Windows to undo the cursor load. If we leave this step out, the cursor will remain a hand until we hover over another window (or the desktop) that has its own method of handling WM_SETCURSOR. We return TRUE to indicate that we don’t need to receive anymore WM_SETCURSOR messages until the mouse enters a new window.
Okay! Now our URL looks like a URL, the cursor changes to indicate this thing is clickable. Now all we have to do is catch clicks over the URL and launch the browser. We handle this by modifying the WM_LBUTTONDOWN case. Currently, it is just relaying all messages to the hidden title bar. We’ll modify it to first check to see if the mouse is over our static, and if so, we will launch the browser and point it to http://www.glowdot.com. Otherwise, the message will be passed along to the title bar.
Here’s the new case:
case WM_SETCURSOR:
POINT pt2;
RECT rect2;
GetCursorPos(&pt2);
GetWindowRect(GetDlgItem(hwnd,IDC_GLOWDOTURL), &rect2);
if (PtInRect(&rect2, pt2)) {
SetCursor(LoadCursor(NULL,IDC_HAND));
} else {
SetCursor(LoadCursor(NULL,IDC_ARROW));
}
return TRUE;
This is one of those things that, while kind of fun, is really such a pain. And the user won’t know what kind of work went into it. Oh well, its a personal victory for the programmer. Good enough for me
Note: when you look over the source code for this lesson, you’ll notice I made one final change to AboutDlgProc to launch glowdot.com when the user clicks the Glowdot logo, as well.
Next: Printing.
Source Code:
win32tut_part13.zip [2.31MB zipped]
Additional Information:
· TheForger’s Win32 API Tutorial
· FunctionX - Win32 Tutorial - Dialogs
Further Reading:
· Nitty Gritty Windows Programming with C++ by Henning Hansen.
· Thinking in C++ by Bruce Eckel.




Comments
Got something to say?