C++/Win32: Window Dressing
March 2, 2008
When you learn how to program, you are inevitably going to have to learn to program the way whichever author or teacher you are receiving your information from does. Nine lessons into this tutorial, you should be pretty aware of the way I program.
My first priority, when working on a project, is getting the project to work. I do spend time in the beginning planning out what the GUI will look like, how the logic will flow, etc. But those things often change during the course of development, so I tend to not get too caught up in it. Priority number 1 is getting your app to do what it is supposed to do — worry about making it pretty later.
Well, it’s later. Our port scanner does scan ports, and we got it to do so in only 8 simple steps. When you consider that we took a detour to go over skinning, we got that thing working pretty fast (about 12 hours for me, inlcuding the time spent building up this tutorial as I went). But now we need to make it look a little (or a lot) better than it does now. It also could use a few more features, and we could definitely tweak it for performance. So let’s do some of that now.
Icons
One thing that bothers us is that our app is using the Windows default icon. It’s so easy to add an icon to our app that we might as well get it over with.
You should have a pretty good understanding by now of how we work with resources. An icon is itself a resource, and Visual Studio has a nifty icon editor we can use that’s as good as any. To add an icon to our project, do the following:
1. From the Solution Explorer open the Resource Files folder and double-click your .rc file.
2. Right-click your .rc folder and select Add Resource.
3. Select Icon and click New. Your icon resource will be created and the icon editor will open.
4. Edit your icon, and save your project when done to update everything.
There, now we have an icon. The resource ID given to us by Visual Studio is IDI_ICON1. How do we get it into our application? Remember when we filled in our WNDCLASSEX structure, there were two structure members in which we can specify an icon resource — one for the small icon, and one for the regular icon. We could use two separate icons for each, but we won’t. So where did we fill in the WNDCLASSEX?
All the way back in the Create function in Win32WrapperClass. So we’ll have to modify that class a bit, in order to get it to actually pass an icon resource. That also means we’ll need to modify the parameter list to receive two icon resouces. Remember that Create originally took no arguments. We’d kind of like to keep that option, in case the user of the class doesn’t want to add an icon to their app. So we’ll overload this function in the definition of Win32WrapperClass. Overloading a function means creating two or more functions with identical names but with different parameter lists. The compiler will know which function we mean to call by analyzing the parameter list during the call. If it finds a matching function, it will call that one.
Remember that PortScannerWindow extends (inherits from) Win32WrapperClass, so by modifying the Create function in Win32WrapperClass, we are affecting PortScannerWindow as well, which is the desired outcome in this case.
So what we need to do is go back to Win32WrapperClass.h and add the new version of Create after the old:
BOOL Create(); BOOL Create(int icon, int iconSm);
And we’ll need to define our new Create function in Win32WrapperClass.cpp after the old one:
BOOL Win32WrapperClass::Create(int icon, int iconSm) {
// Create the window
WNDCLASSEX WndCls;
// Create the application window
WndCls.cbSize = sizeof(WNDCLASSEX);
WndCls.style = 0;
WndCls.lpfnWndProc = stWinProc;
WndCls.cbClsExtra = 0;
WndCls.cbWndExtra = 0;
WndCls.hIcon = LoadIcon(m_hInstance, MAKEINTRESOURCE(icon));
WndCls.hCursor = LoadCursor(NULL, IDC_ARROW);
WndCls.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
WndCls.lpszMenuName = NULL;
WndCls.lpszClassName = m_pClassName;
WndCls.hInstance = m_hInstance;
WndCls.hIconSm = LoadIcon(m_hInstance, MAKEINTRESOURCE(iconSm));
// Register the application
RegisterClassEx(&WndCls);
// 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,NULL,
m_hInstance,
(void *)this);
return (m_hWnd != NULL);
}
Now, just go to WinMain, and change the call to Create to look like this:
pWindow->Create(IDI_ICON1, IDI_ICON1);
All we’re doing is passing the Create function two icon resource IDs. Or, as in our case, the same icon resource ID twice. Fire it up, and you’ll notice that the icons are in fact there, and our application looks a bit more pro now. If you hit alt-tab while the app is running, you’ll see your icon among the listed task icons. Neat.
You should probably make the background color of your icon the system color (it’s the little teal monitor in the palette) so it doesn’t stick out like a sore thumb.
Unskinning with an icon button
That was fun… what next? Well, this will be less about Windows programming and more about program logic, but shouldn’t we let the user decide if he/she wants the app skinned or not? Maybe you don’t care, but it will make it convenient for us while we cover the other stuff I want to get to, namely menus and advanced GUI design. Because as long as the app is skinned, we won’t stick a menu in there, and we don’t care really how the GUI is drawn, since we’re so distracted by our fancy bitmap skin. So let’s put a button on the skinned app that will take us out of skin mode, and return to the boring Windows style app. Unless we want a basic text button that says “unskin,” we’ll need to know how to make buttons that hold bitmaps. Then we’ll create a small icon to apply to the button, and we’ll look super cool.
Here’s how to create an icon button:
HWND m_hUnskinButton;
m_hUnskinButton = CreateWindow("BUTTON",
"Unskin",
WS_VISIBLE | WS_CHILD | BS_PUSHBUTTON | BS_ICON,
10, 465, 30, 30,
m_hWnd,
(HMENU)IDB_UNSKIN,
m_hInstance,
NULL);
SendDlgItemMessage(m_hWnd, IDB_UNSKIN, BM_SETIMAGE, (WPARAM)IMAGE_ICON,
(LPARAM)LoadIcon(m_hInstance, MAKEINTRESOURCE(IDI_ICON2)));
In our Skin function, we’ll wrap that code in an if statement checking on a flag, a new member variable called m_bSkinned of type BOOL that will be true if we are in skinned mode, false if not.
Tooltips
So we got an icon up on the skinned interface, but lo! Our icon, while pretty, doesn’t really tell the user what it does! We should apply a tooltip to this button, don’t you think? So when the user hovers his mouse over it, it will show a floating text label that says “Switch to unskinned mode” or something similar. Tooltips, in my opinion, are the greatest addition to the windows object set ever — from a user’s point of view.
Here’s the code to add a tooltip:
// Create the tooltip for the unskin button
INITCOMMONCONTROLSEX iccex;
// struct specifying info about tool in ToolTip control
TOOLINFO ti;
unsigned int uid = 0; // for ti initialization
char strTT[30] = "Switch To Unskinned Mode";
LPTSTR lptstr = strTT;
RECT rect; // for client area coordinates
/* INITIALIZE COMMON CONTROLS */
iccex.dwICC = ICC_WIN95_CLASSES;
iccex.dwSize = sizeof(INITCOMMONCONTROLSEX);
InitCommonControlsEx(&iccex);
/* CREATE A TOOLTIP WINDOW */
m_hUnskinButtonTT = CreateWindowEx(WS_EX_TOPMOST,
TOOLTIPS_CLASS,
NULL,
WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
m_hUnskinButton,
NULL,
m_hInstance,
NULL
);
SetWindowPos(m_hUnskinButtonTT,
HWND_TOPMOST,
0,
0,
0,
0,
SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE
);
/* GET COORDINATES OF THE MAIN CLIENT AREA */
GetClientRect (m_hUnskinButton, &rect);
/* INITIALIZE MEMBERS OF THE TOOLINFO STRUCTURE */
ti.cbSize = sizeof(TOOLINFO);
ti.uFlags = TTF_SUBCLASS;
ti.hwnd = m_hUnskinButton;
ti.hinst = m_hInstance;
ti.uId = uid;
ti.lpszText = lptstr;
// ToolTip control will cover the whole window
ti.rect.left = rect.left;
ti.rect.top = rect.top;
ti.rect.right = rect.right;
ti.rect.bottom = rect.bottom;
/* SEND AN ADDTOOL MESSAGE TO THE TOOLTIP CONTROL WINDOW */
SendMessage(m_hUnskinButtonTT, TTM_ADDTOOL, 0, (LPARAM) (LPTOOLINFO) &ti);
Sure seems like a lot of work just to get a stupid string of text to hover over your button, doesn’t it? Definitely.
Well, first of all, you’ll have to #include
So how does it work? First, we need an instance of each of two structures: INITCOMMONCONTROLSEX and TOOLINFO. We’ll fill the first in with the details of which of the common controls should be registered (in our case, we ask for ICC_WIN95_CLASSES), and pass it to InitCommonControlsEx. It’s left to you to read the docs about this structure and function call — and I suggest you do, because there is a lot of cool stuff you can do when you master them.
The second structure contains information about our tool tip control. This is information we’ll be sending to the control when we activate it (such as the parent window’s HWND, our app’s HINSTANCE, where it should be positioned, what it’s text should be, etc.).
Before we activate the tool tip, however, we need to create it. Surprise! A tooltip is just another window! We know how to create windows now, I hope. We’ll create this one with CreateWindowEx so that we can specify WS_EX_TOPMOST (to keep it always on top so other windows (buttons, statics, etc) won’t clip it. We specify the pre-registered class TOOLTIPS_CLASS, and assign the returned HWND to our member HWND value for this tooltip. Finally, we activate the tooltip by sending the TTM_ADDTOOL message to the tooltip.
Seems like a lot, but most of that work only has to be done once (like the call to InitCommonControls), and after that you’ll just be creating new tooltip “windows”, filling in TOOLINFO structures, and activating tooltips. A lot of applications allow you to turn tooltips on and off, and if you want yours to do the same, this code should probably be in its own function, and you should have a flag set to TRUE when tooltips are on. Our tooltip creation code goes in BuildGUI after the creation of the m_hUnskinButton button, and we will only activate the tooltip if the m_bSkinned button is set to TRUE.
So now we’ve got a icon button, and a tooltip telling the user what it does. Now let’s make it do something.
Removing the skin
To do this, we’ll want to create an Unskin function that removes the skin bitmap, removes the region from the window, and restores the title bar of the window. It should also deactivate our tooltip, and hide both the unskin button and the close button we created earlier. Finally, it should set our m_bSkinned value to FALSE indicating that we are no longer in skinned mode.
First, we declare the function in PortScannerWindow.h:
BOOL Unskin();
Then we define the function in PortScannerWindow.cpp:
BOOL PortScannerWindow::Unskin() {
// unassign the region
SetWindowRgn(m_hWnd, NULL, false);
DWORD dwStyle;
// change window style (show caption bar again)
dwStyle = GetWindowLong(m_hWnd, GWL_STYLE);
dwStyle |= WS_CAPTION | WS_SIZEBOX;
SetWindowLong(m_hWnd, GWL_STYLE, dwStyle);
// hide the unskin button
dwStyle = GetWindowLong(GetDlgItem(m_hWnd,IDB_UNSKIN),GWL_STYLE);
dwStyle &= ~(WS_VISIBLE);
SetWindowLong(GetDlgItem(m_hWnd,IDB_UNSKIN), GWL_STYLE, dwStyle);
// hide the close button
dwStyle = GetWindowLong(GetDlgItem(m_hWnd,IDB_CLOSEWINDOW),GWL_STYLE);
dwStyle &= ~(WS_VISIBLE);
SetWindowLong(GetDlgItem(m_hWnd,IDB_CLOSEWINDOW), GWL_STYLE, dwStyle);
// flag just to make sure our app knows we unskinned
m_bSkinned = FALSE;
// force a window repainting
InvalidateRect(m_hWnd, NULL, TRUE);
SetWindowPos(m_hWnd,
NULL,
0, 0, 1, 1,
SWP_NOMOVE|SWP_NOZORDER);
SetWindowPos(m_hWnd,
NULL,
0, 0, m_pRect->right-m_pRect->left,m_pRect->bottom-m_pRect->top,
SWP_NOMOVE|SWP_NOZORDER);
return TRUE;
}
Nothing too complicated. We set the region to NULL (effectively removing the region settings done in Regionize), restore the WS_CAPTION and WS_SIZEBOX styles, and hide the close button and unskin button that only make sense when we’re in skinned mode.
What if we want to go back to skinned mode? I’m not going to tackle that here, because I’d like to cover menus in the next section, and I want the option to enter skinned mode to be a menu option. So this is good for now.
Instead, let’s clean up the non-skinned interface a little bit by introducing some new elements and cleaning up the ones we’ve already seen.
Tabbing between edit fields - pretending we’re a dialog
First of all, I mentioned several lessons ago that we’d like the TAB key to actually do what we expect it to do — namely, move us from one edit box to the next.
If our main app window were a dialog, we could create our edit controls with the WS_TABSTOP style flag set indicating that this control is a destination when tabbing through controls in a group. We aren’t in a dialog, however, so setting WS_TABSTOP has no effect. But we can make our controls behave as if they were in a dialog.
I previously stated that you can think of the message pump — the main loop that keeps the app running in WinMain — as your last chance to process messages before they get sent off to your windows’ own message handlers. That’s exactly what we’re going to do here. If we insert a call to IsDialogMessage in our message pump to check if the message we are receiving is a dialog message (and more importantly, a request to switch focus) we will not send it on to the control.
Our new message pump looks like this:
while( (hRet = GetMessage( &Msg, NULL, 0, 0 )) != 0)
{
if (hRet == -1) {
// handle the error and possibly exit
MessageBox(pWindow->getHWND(), "An unknown error has occurred!\n
The application will now terminate.", "Error!", MB_OK);
PostQuitMessage(1);
}
else
{
if (!IsDialogMessage(pWindow->getHWND(), &Msg)) {
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
}
}
And now TAB keystrokes inside of controls with the WS_TABSTOP style flag set will behave as expected.
We might also like to make our SCAN/STOP button behave like a default button — in other words, we would like it if pressing the ENTER key activated this button, no matter where we are in our sea of controls. We have already given this button the BS_DEFPUSHBUTTON style, and consequently it is drawn with a heavier border, but if our main app window were a dialog, it would also automatically behave like a dialog (hopefully you’re beginning to see that dialog windows are pretty useful — we’ll cover them in a later lesson). Again we’d like our non-dialog window to have this dialog behavior, and again we’ll have to ensure that it does ourselves. We’ll need to capture any ENTER key hits meant for the application window, and decide what to do with them from there.
To make the concepts easier, let’s try something else first. Let’s assign the key F4 to our SCAN/STOP button. Shortcuts are really wonderful things that you should add to all of your applications. You have a 100+ key keyboard at your disposal, and combinations of keys are possible, so take advantage of them! To do this, we need to add a case to our message handler to handle the WM_KEYDOWN message. Inside that case, we’ll need a switch on the wParam parameter which is where the actual key that was pressed is stored. We’ll be checking for the VK_F4 key inside our switch. If we find it, we’ll send a WM_COMMAND message to our main app window indicating that the IDB_GOSCAN button was hit. Sound confusing? Its not. Here’s the case we’ll add to our WinProc function:
case WM_KEYDOWN:
switch (wParam) {
case VK_F4:
SendMessage(m_hWnd,WM_COMMAND,(WPARAM)IDB_GOSCAN,0);
break;
}
break;
As you can see, we are merely responding to the WM_KEYDOWN message by firing a WM_COMMAND message, making it seem like the button was hit.
Ah, but there’s a problem. Hitting F4 does, in fact, activate the SCAN/STOP button, but what happens if the focus changes to one of our edit controls? F4 is rendered useless. If we are in one of our edit controls, our message handler doesn’t receive the F4 hit, because the edit control is receiving it. So what can we do?
The hard way is to subclass the edit control window class. In the new class, then, you would create your own message handler that would intercept the keystrokes we need, and then send them to the message handler of the parent class (our main app window). And then you would have to repeat the subclassing for each edit control or button or whatever that we might be in. That would definitely be a lot of work for a simple shortcut, wouldn’t it? Well fortunately, there is an easier way.
Accelerators
I call them “shortcuts,” but Windows calls them “accelerators”. An accelerator is a key stroke, or combination of keys pressed simultaneously, that should produce a specific result. For me, the best way to think of accelerators is that they are basically buttons (i.e. windows) that don’t have to be created, and don’t really exist. The reason I say that is because when the user hits the specified keystroke combination which defines your accelerator, a special function called TranslateAcellerator will fire a WM_COMMAND message at your message handler. WM_COMMAND, remember, is what gets fired when a button is hit (and when a menu item is selected, as you’ll see later), so in a sense, hitting a certain key combination is identical to hitting an invisible button. We will handle the message exactly the same.
But before we handle anything, we’ll have to let Windows know what specific keystroke combinations (shortcuts) are important to us. We do this by creating an accelerator resource in our application, and then we define our key combos in that resource.
To create an accelerator resource, do the following:
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 Accelerator and then New. Your resource will be created, and the accelerator editor will open.
You should now have an accelerator resource called IDR_ACCELERATOR1.
You are given a default accelerator called ID_ACCELERATOR40005 (or something similar) with Modifier set to Ctrl, no Key defined, and the type VIRTKEY. Let’s modify this one. Set the ID to ID_ACC_F4. Set the Modifier to nothing (leave it blank), and set the Key to VK_F4. Leave the Type as is. While the type is set to VIRTKEY we will have to define our Key field in terms of the virtual keyboard codes, which begin with VK_. If we changed Type to ASCII, we would have to define our Key field in terms of its ASCII value (e.g. “a”, “A”, etc.)
Now that we’ve defined an accelerator, close the accelerator editor and save all. Now we have to make it do something.
Go to your WinMain function. We will need to load our accelerator resource before we use it, and we’ll do that with a call to LoadAccelerators like so:
// declare a handle to an accelerator table HACCEL hAccel; // load our accelerator table hAccel = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDR_ACCELERATOR1));
You can have several loaded accelerator tables in one application, allowing you to switch between shortcut layouts for different users. You can also allow your users to create their own accelerator tables (although you’ll never tell them that’s what they’re doing) so they can customize the app to suit their specific wants. We will not be going into that though, at least not anytime soon.
Now, we need to catch these keystroke combinations and fire WM_COMMAND messages to our app window when they are received. We do this by calling TranslateAccelerator from inside our message pump. It is important that we catch these accelerators first, before Windows can decide to send them to their windows. Our new message pump will look like this:
// our message pump
while( (hRet = GetMessage( &Msg, NULL, 0, 0 )) != 0)
{
if (hRet == -1) {
// handle the error and possibly exit
MessageBox(pWindow->getHWND(), "An unknown error has occurred!\n
The application will now terminate.", "Error!", MB_OK);
PostQuitMessage(1);
}
else
{
if ( (!TranslateAccelerator(pWindow->getHWND(),hAccel,&Msg))
&& (!IsDialogMessage(pWindow->getHWND(), &Msg)) ) {
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
}
}
Notice that TranslateAccelerator is called before IsDialogMessage. If they are called in the reverse order, and the focus is on one of our edit controls, IsDialogMessage will take our shortcut combo and process it as a dialog message. We don’t want that, obviously. We need to fire that WM_COMMAND message. This new message pump will get that done.
So now, we can be sure that hitting F4 from anywhere in the app will generate the WM_COMMAND message. We just need to catch it in our message handler and then send another WM_COMMAND message to make it look like IDB_GOSCAN was hit. Here’s the case we need to add to our message handler:
case WM_COMMAND:
switch(LOWORD(wParam)) {
case ID_ACC_F4:
SendMessage(m_hWnd,WM_COMMAND,(WPARAM)IDB_GOSCAN,0);
break;
// ........
// other button cases go here
}
break;
// ........
// other message cases go here
That’s all there is to it.
Now let’s get back to making the ENTER key work like we want it to. First, we have to decide when we want hitting enter to activate the button. Always? Only when inside an edit control? Will we have other uses for the enter key later? mapping F4 to a button is not a big deal, but the enter key is quite a common key. We might have a use for it later if we extend this application further, and we probably don’t want to capture every hit of the enter key and activate our scan button, so let’s only do that if we are inside one of our edit controls. In other words, hitting the enter key will activate the SCAN/STOP button if one of our three edit controls has focus.
The process will be exactly the same as before, only this time we’ll check to see if one of the edit controls has focus before calling SendMessage. If none of them do, we’ll call DefWindowProc to handle the keystroke. First, set up the accelerator in the accelerator table, giving the accelerator the ID ID_ACC_ENTER and the key VK_RETURN. Then save all.
Here’s the case (this case goes right after the ID_ACC_F4 case above):
ccase ID_ACC_ENTER:
HWND testFocus;
// get a handle to the window with focus
testFocus = GetFocus();
// pass WM_COMMAND to our SCAN button if focus is on one of our edit controls
if ( (testFocus == m_hIPAddressEdit)
|| (testFocus == m_hStartPortEdit)
|| (testFocus == m_hEndPortEdit)
|| (testFocus == m_hGoButton) ) {
SendMessage(m_hWnd,WM_COMMAND,(WPARAM)IDB_GOSCAN,0);
} else if (testFocus == m_hUnskinButton ) {
SendMessage(m_hWnd,WM_COMMAND,(WPARAM)IDB_UNSKIN,0);
} else if (testFocus == m_hCloseButton ) {
SendMessage(m_hWnd,WM_COMMAND,(WPARAM)IDB_CLOSEWINDOW,0);
} else if (testFocus == m_hDisplayArea) {
SendMessage(m_hDisplayArea,EM_REPLACESEL,0,(LPARAM)(LPSTR)"\r\n");
} else {
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
break;
Notice we added else if clauses to check for enter key hits when the focus is on our buttons, also. If so, we generate a WM_COMMAND message for the button, as if it were pressed with the mouse. We can now go and add the WS_TABSTOP property to each of our buttons, since they now react to keyboard input.
We also have to define the behavior that should occur in the event that enter is hit while the user is in the display area edit control. Since we’re grabbing all of the enter key strokes, we need to make sure we cover all the behaviors we lost by doing so. In this case, we send a EM_REPLACESEL message to the edit control, which inserts the text pointed to by the pointer stored in lParam whereever the cursor is (or replaces a selection of text if a selection is active).
This lesson has grown rather massive, and it isn’t my aim to give too much information at once. So we’ll deal with some more GUI issues in the next lesson.
Wow! This project file has grown to almost 5 MB zipped, and almost 8 MB uncompressed! It’s just a port scanner! Part 1 was only about 480k. It’s almost like this thing actually does something now! But don’t worry, the application itself is only a little more than 700k, so no one will complain that we’ve made the most bloated port scanner ever — yet
Next: Menus.
Source Code:
win32tut_part9.zip [4.48MB 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?