Home | Email 

Windows Programming Pages
 

Prev | Up | Next

Lesson 3 - Introducing Windows UI Elements

Click here to download the source files to this tutorial.

This tutorial will teach you to create standard UI elements like a menu, toolbar and a status bar. You will also learn to separate your code into many files so that you can manage your code better.

Setting up your workspace

Until now, you wrote all your code in a single file. This is alright for smaller projects but as the size and complexity of your projects increase, dividing your code into smaller files makes it easier to maintain your code.

  1. Create a new blank Windows project and call it Draw. From now on we will work on this project, adding stuff as we move along.
  2. Add the following files to the project (Press Ctrl+N to create a new file and Ctrl+S to save it).
    1. main.c - This file will contain the WinMain function and will be the entry point to our application.
    2. interface.c - This file will contain interface related stuff like creating windows and other elements.
    3. resource.h - This file contains the definitions required for resources.
    4. resource.rc - This is the resource script file which defines resources like icons and dialog boxes.

    Your screen should now look something like this,

  3. Now paste the following code in main.c
    /* Lesson 3 - Introducing UI Elements
     * Pravin Paratey (May 06, 2003)
    **/
    #include <windows.h>
    HWND hwndMain;	//Main window handle
    // Windows entry point
    int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, 
    	LPSTR lpCmdLine, INT nCmdShow)
    {
    	MSG msg; // MSG structure to store messages
    	// Create the Main Window
    	hwndMain = (HWND)CreateMainWindow(hInstance);
    	if (!hwndMain)
    		return 0;
    	// Make the window visible
    	ShowWindow(hwndMain,SW_SHOW);
    	// Process messages coming to this window
    	while (GetMessage(&msg,NULL,0,0))
    	{
    		TranslateMessage(&msg);
    		DispatchMessage(&msg);
    	}
    	// return value to the system
    	return msg.wParam;
    }

    And paste this in interface.c

    /* interface.c
     * Lesson 3 - Introducing UI Elements
     * Pravin Paratey (May 06, 2003)
    **/
    #include <windows.h>
    // Callback procedure
    LRESULT CALLBACK MainWndProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)
    {
    	switch (msg)
    	{
    		case WM_DESTROY:
    			// User closed the window
    			PostQuitMessage(0);
    			break;
    		default:
    			// Call the default window handler
    			return DefWindowProc(hwnd,msg,wParam,lParam);
    	}
    	return 0;
    }
    // Creates main window
    HWND CreateMainWindow(HINSTANCE hInstance)
    {
    	HWND hTmp; // Temporary handle to a window
    	WNDCLASSEX wcx; // WINDOW class information
    	// Initialize the struct to zero
    	ZeroMemory(&wcx,sizeof(WNDCLASSEX));
    	wcx.cbSize = sizeof(WNDCLASSEX); 
    	wcx.style = CS_HREDRAW|CS_VREDRAW |CS_DBLCLKS ; // Class styles
    	wcx.lpfnWndProc = (WNDPROC)MainWndProc; // Pointer to the callback proc
    	wcx.cbClsExtra = 0; 
    	wcx.cbWndExtra = 0;
    	wcx.hInstance = hInstance; // Instance of the application
    	wcx.hIcon = NULL; // Class Icon
    	wcx.hCursor = LoadCursor(NULL, IDC_ARROW); // Class Cursor
    	wcx.hbrBackground = (HBRUSH)(COLOR_WINDOW); // Background brush
    	wcx.lpszMenuName = NULL; // Menu resource
    	wcx.lpszClassName = "Draw"; // Name of this class
    	wcx.hIconSm = NULL; // Small icon for this class
    	// Register this window class with MS-Windows
    	if (!RegisterClassEx(&wcx))
    		return 0;
    	// Create the window
    	hTmp = CreateWindowEx(0, //Extended window style
    			"Draw", // Window class name
    			"Draw v0.1", // Window title
    			WS_OVERLAPPEDWINDOW, // Window style
    			CW_USEDEFAULT,CW_USEDEFAULT, // (x,y) pos of window
    			CW_USEDEFAULT,CW_USEDEFAULT, // Width, height of window
    			HWND_DESKTOP, // HWND of the parent
    			NULL, // Handle to menu
    			hInstance, // Handle to application instance
    			NULL); // Pointer to window creation data
    	
    	// TIP : We dont want to make the window visible yet so that all the 
    	// controls can be placed and sized
    	
    	// Create Toolbar
    	// Create Statusbar
    	
    	// return value
    	return hTmp;
    }

    Notice that this code is the same as that in Lesson 2, split into two files.

Now that you've set up your workspace, its time to learn some stuff.

Creating Menus

Creating static menus (i.e. menus that do not dynamically change during the life of the application) is amazingly simple. But before we go into that, I'll have to tell you a bit about resources. I'll not go in depth here because its something we cover in a tutorial later.

Resources

Resources are data that you can add to the applications executable file. Resources can be:

  • standard - icon, cursor, menu, dialog box, bitmap, enhanced metafile, font, accelerator table, message-table entry, string-table entry, or version
  • custom - any kind of data that doesn't fall into the previous category (for example a mp3 file or a dictionary database).

Creating a menu

Creating a static menu involves creating a menu resource and adding the menu resource to the window's menu. Add the following code to resource.rc:

/* resource.rc
**/
#include <windows.h>
#include "resource.h"

IDMAINMENU MENU DISCARDABLE LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
BEGIN
	POPUP "&File"
	BEGIN
		MENUITEM "&New\tCtrl+N", IDM_FILE_NEW
		MENUITEM "&Open\tCtrl+O", IDM_FILE_OPEN
		MENUITEM "&Save\tCtrl+S", IDM_FILE_SAVE
		MENUITEM SEPARATOR
		MENUITEM "E&xit", IDM_FILE_EXIT
	END
	POPUP "&Help"
	BEGIN
		MENUITEM "&About\tF1", IDM_HELP_ABOUT
	END
END

Look at the structure of a menu resource. IDMAINMENU, IDM_FILE_NEW, IDM_FILE_OPEN, etc are numeric identifiers (or numbers). They will be defined in the file resource.h. MENU, POPUP and MENUITEM are keywords. To learn more about them, look them up in your Win32 docs or on MSDN under Resources.

i Numeric Identifiers

Terms like IDMAINMENU, IDM_FILE_NEW, IDM_FILE_OPEN, etc are called numeric identifiers. You are free to give them any name you please. For instance, IDMAINMENU could have very well been called RUBBER_DUCKY. Numeric Identifiers are used to make it easier to code and understand. Besides, it is much easier to write IDMAINMENU instead of memorizing it as the number 300 (or looking it up every time you need it).

Switch to the file resource.h. This is where we'll give numbers to the constant identifiers. You are free to give any number you want. I have numbered them 300 onwards. Add the code that follows:

/* resource.h
**/

#define IDMAINMENU 	300
#define IDM_FILE_NEW    301
#define IDM_FILE_OPEN   302
#define IDM_FILE_SAVE	303
#define IDM_FILE_EXIT	304
#define IDM_HELP_ABOUT	305

That done, all we've got to do is tell our main window to use this menu:

/* interface.c
**/
	...
	wcx.hbrBackground = (HBRUSH)(COLOR_WINDOW); // Background brush
	wcx.lpszMenuName = MAKEINTRESOURCE(IDMAINMENU); // Menu resource
	wcx.lpszClassName = "Draw"; // Name of this class
	...

Compile and run your application. You should see your menu :-).

i Info: Compiler *.res Errors

Did you ever get an error like

"[Build Error] [Draw_private.res] Error 1" on the Compiler Tab

followed by

"Line 20 in file resource.rc : parse error"  on the Resources tab?

It happens if you forget to define a constant identifier.

 

Exercise

Now that you've learnt how a menu is added, add the following menu items. You are free to number them as you wish -

Edit ID Help ID
Undo
Redo
---
Cut
Copy
Paste
---
Select All
ID_EDIT_UNDO
ID_EDIT_REDO

ID_EDIT_CUT
ID_EDIT_COPY
ID_EDIT_PASTE

ID_EDIT_SELECTALL
Contents
Index
---
About

 

ID_HELP_CONTENTS
ID_HELP_INDEX

ID_HELP_ABOUT

Common Controls

Common controls are classes provided by Windows to let you create and use elements like toolbars and status bars easily. You interact with these controls by sending messages.

The following table lists the Windows controls. You can use these controls in your applications.

Control Description
Animation An animation control is a window that displays an Audio-Video Interleaved (AVI) clip.
Button Button controls typically notify the parent window when the user chooses the control.
Combo Box Combo box controls are a combination of list boxes and edit controls, letting the user choose and edit items.
ComboBoxEx ComboBoxEx Controls are an extension of the combo box control that provides native support for item images.
Date and Time Picker A date and time picker (DTP) control provides a simple and intuitive interface through which to exchange date and time information with a user.
Drag List Box Drag List Boxes are a special type of list box that enables the user to drag items from one position to another.
Edit Edit controls let the user view and edit text.
Flat Scroll Bar Flat scroll bars behave just like standard scroll bars except that you can customize their appearance to a greater extent than standard scroll bars.
Header A header control is a window that is usually positioned above columns of text or numbers. It contains a title for each column, and it can be divided into parts.
Hot Key A hot key control is a window that enables the user to enter a combination of keystrokes to be used as a hot key.
Image Lists An image list is a collection of images of the same size, each of which can be referred to by its index.
IP Address Controls An Internet Protocol (IP) address control allows the user to enter an IP address in an easily understood format.
List Box List box controls display a list from which the user can select one or more items.
List-View A list-view control is a window that displays a collection of items. The control provides several ways to arrange and display the items.
Month Calendar A month calendar control implements a calendar-like user interface.
Pager A pager control is a window container that is used with a window that does not have enough display area to show all of its content.
Progress Bar A progress bar is a window that an application can use to indicate the progress of a lengthy operation.
Property Sheets A property sheet is a window that allows the user to view and edit the properties of an item.
ReBar Rebar controls act as containers for child windows. An application assigns child windows, which are often other controls, to a rebar control band.
Rich Edit Rich Edit controls let the user view and edit text with character and paragraph formatting, and can include embedded COM objects.
Scroll Bars Scroll bars let the user choose the direction and distance to scroll information in a related window.
Static Static controls often act as labels for other controls.
Status Bars A status bar is a horizontal window at the bottom of a parent window in which an application can display various kinds of status information.
SysLink A SysLink control provides a convenient way to embed hypertext links in a window.
Tab A tab control is analogous to the dividers in a notebook or the labels in a file cabinet. By using a tab control, an application can define multiple pages for the same area of a window or dialog box.
Toolbar A toolbar is a control window that contains one or more buttons. Each button, when clicked by a user, sends a command message to the parent window.
ToolTip ToolTips are hidden most of the time. They appear automatically, or pop up, when the user pauses the mouse pointer over a tool.
Trackbar A trackbar is a window that contains a slider and optional tick marks. When the user moves the slider, using either the mouse or the direction keys, the trackbar sends notification messages to indicate the change.
Tree-View A tree-view control is a window that displays a hierarchical list of items, such as the headings in a document, the entries in an index, or the files and directories on a disk.
Up-Down An up-down control is a pair of arrow buttons that the user can click to increment or decrement a value, such as a scroll position or a number displayed in a companion control.

Creating Toolbars

Toolbars are part of Windows common controls. A toolbar is created by calling the CreateWindow() function and specifying the classname as TOOLBARCLASSNAME. Before we do this, we have to ensure that the Windows common controls dll is loaded and that the TOOLBAR class is loaded.

Add an empty function named HWND CreateMainWindowToolbar(HWND hParent) in interface.c and add the following code.

HWND CreateMainWindowToolbar(HWND hParent)
{   
    HWND hTmp; // Temporary HWND
    INITCOMMONCONTROLSEX icx;
    
    // Ensure common control DLL is loaded
    icx.dwSize = sizeof(INITCOMMONCONTROLSEX);
    icx.dwICC = ICC_BAR_CLASSES; // Specify BAR classes
    InitCommonControlsEx(&icx); // Load the common control DLL
    
    // Create the toolbar window
    hTmp = CreateWindowEx(0, TOOLBARCLASSNAME, (LPSTR) NULL, 
            WS_CHILD | WS_VISIBLE, 0, 0, 0, 0, hParent, 
            (HMENU) NULL, NULL, NULL);
}

Before you hit compile, there are a few things that must be done,

  1. Add the following lines at the top of interface.c
    1  /* interface.c
    2   * Lesson 3 - Introducing UI Elements
    3   * Pravin Paratey (May 06, 2003)
    4  **/
    5  #define _WIN32_IE 0x0500 // To support INITCOMMONCONTROLSEX
    7  #include <windows.h>
    8  #include <commctrl.h>

    Note: If you are running Windows 9x with IE 4, define _WIN32_IE as 0x0400 and for plain old Windows 95 comment it out.

  2. Add libcomctl32.a to the linker libraries so that the linker knows where to find them. To do this, select Project->Project Options (or press Alt+P) and then on the tab marked Parameters, click on the Add Library button.

    On the dialog that appears, navigate to your Dev-Cpp/lib folder and select libcomctl32.a.

  3. That done, declare hToolbar as a global variable of type HWND and call CreateMainWindowToolbar() from CreateMainWindow().
    /* interface.c
    **/
    #include ...
    HWND hToolbar;
    ...
    HWND CreateMainWindow(HINSTANCE hInstance)
    {	...
    	// Create Toolbar
    	hToolbar = CreateMainWindowToolbar(hTmp);
    	...
  4. If you press F9 now, you'll see a toolbar. However, when you resize the window, the toolbar will not resize with it. Getting the toolbar to resize itself is a simple matter of handling the WM_SIZE message and passing this message onto the toolbar:
    // Callback procedure
    LRESULT CALLBACK MainWndProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)
    {
    	switch (msg)
    	{
    	case WM_DESTROY:
    		// User closed the window
    		PostQuitMessage(0);
    		break;
    	case WM_SIZE:
    		// Resize the toolbar
    		SendMessage(hToolbar,msg,wParam,lParam);
    		break;
    	default:
    		// Call the default window handler
    		return DefWindowProc(hwnd,msg,wParam,lParam);
    }

Compile and run your application. You should see a band for the toolbar. The toolbar doesn't have any buttons yet, and that's what we are going to do next.

Adding buttons to the toolbar

Add the following to interface.c (Yeah, I know it looks scary):

#define NUM_BUTTONS 14
HWND CreateMainWindowToolbar(HWND hParent)
{
	HWND hTmp; // Temporary HWND
	INITCOMMONCONTROLSEX icx;
	TBADDBITMAP tbab;
	TBBUTTON tbb[NUM_BUTTONS];
	int i;

	// Ensure common control DLL is loaded
	icx.dwSize = sizeof(INITCOMMONCONTROLSEX);
	icx.dwICC = ICC_BAR_CLASSES; // Specify BAR classes
	InitCommonControlsEx(&icx); // Load the common control DLL

	// Create the toolbar window
	hTmp = CreateWindowEx(0, TOOLBARCLASSNAME, (LPSTR) NULL, 
		WS_CHILD | WS_VISIBLE | TBSTYLE_FLAT | WS_BORDER, 
		0, 0, 0, 0, hParent, (HMENU) NULL, NULL, NULL);

	// Send the TB_BUTTONSTRUCTSIZE message, which is required for 
	// backward compatibility. 
	SendMessage(hTmp, TB_BUTTONSTRUCTSIZE, (WPARAM) sizeof(TBBUTTON), 0);

	// Add the bitmap containing button images to the toolbar.
	tbab.hInst = HINST_COMMCTRL;
	tbab.nID = IDB_STD_SMALL_COLOR;
	SendMessage(hTmp, TB_ADDBITMAP, (WPARAM) NUM_BUTTONS,(LPARAM) &tbab);

	// Add buttons
	ZeroMemory(&tbb, sizeof(TBBUTTON)*NUM_BUTTONS);

	for(i=0;i<14;i++)
	{
		tbb[i].fsState = TBSTATE_ENABLED;
		tbb[i].fsStyle = TBSTYLE_BUTTON;
	}

	tbb[0].iBitmap = STD_FILENEW;
	tbb[0].idCommand = IDS_NEW;

	tbb[1].iBitmap = STD_FILEOPEN;
	tbb[1].idCommand = IDS_OPEN;

	tbb[2].iBitmap = STD_FILESAVE;
	tbb[2].idCommand = IDS_SAVE;

	tbb[3].iBitmap = 0;
	tbb[3].idCommand = 0;
	tbb[3].fsStyle = TBSTYLE_SEP;

	tbb[4].iBitmap = STD_PRINTPRE;
	tbb[4].idCommand = IDS_PRINTPRE;

	tbb[5].iBitmap = STD_PRINT;
	tbb[5].idCommand = IDS_PRINT;

	tbb[6].iBitmap = 0;
	tbb[6].idCommand = 0;
	tbb[6].fsStyle = TBSTYLE_SEP;

	tbb[7].iBitmap = STD_CUT;
	tbb[7].idCommand = IDS_CUT;

	tbb[8].iBitmap = STD_COPY;
	tbb[8].idCommand = IDS_COPY;

	tbb[9].iBitmap = STD_PASTE;
	tbb[9].idCommand = IDS_PASTE;

	tbb[10].iBitmap = 0;
	tbb[10].idCommand = 0;
	tbb[10].fsStyle = TBSTYLE_SEP;

	tbb[11].iBitmap = STD_UNDO;
	tbb[11].idCommand = IDS_UNDO;

	tbb[12].iBitmap = STD_REDOW;
	tbb[12].idCommand = IDS_REDO;

	tbb[13].iBitmap = 0;
	tbb[13].idCommand = 0;
	tbb[13].fsStyle = TBSTYLE_SEP;

	// Add buttons
	SendMessage(hTmp,TB_ADDBUTTONS,NUM_BUTTONS,(LPARAM)&tbb);

	return hTmp;
}

And the following to resource.h:

#define IDS_NEW		200
#define IDS_OPEN 	201
#define IDS_SAVE 	202
#define IDS_PRINTPRE 	203
#define IDS_PRINT 	204
#define IDS_CUT 	205
#define IDS_COPY 	206
#define IDS_PASTE 	207
#define IDS_UNDO 	208
#define IDS_REDO 	209

Hit F9. Your app should look something like this:

Creating a status bar

Creating a status bar doesn't involve as much code as a toolbar. We can make a simple status bar with just one line of code:

HWND CreateStatusWindow(
	LONG style,
	LPCTSTR lpszText,
	HWND hwndParent,
	UINT wID
); 

Notice the word simple. Ok, I'll not scare you just yet. We're going to make a simple status bar for now. Enter the following code in interface.c:

HWND hToolbar;
HWND hStatusbar;

	...

// Callback procedure
LRESULT CALLBACK MainWndProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)
{
	...
	case WM_SIZE:
		// Resize the toolbar
		SendMessage(hToolbar,msg,wParam,lParam);
		// Resize the statusbar;
		SendMessage(hStatusbar,msg,wParam,lParam);
	...
}

	...

// Creates main window
HWND CreateMainWindow(HINSTANCE hInstance)
{
	...
	// Create Toolbar
	hToolbar = CreateMainWindowToolbar(hTmp);
	// Create Statusbar
	hStatusbar = CreateMainWindowStatusbar(hTmp);
	// return value
	return hTmp;
}

	...

HWND CreateMainWindowStatusbar(HWND hParent)
{
	return CreateStatusWindow(
		WS_CHILD|WS_VISIBLE,"Ready",hParent,IDM_STATUSBAR);
}

There's one last thing you need to do. Can you guess what it is? I'll give you a hint. It's marked in RED. Yep! Add a #define IDM_STATUSBAR to resource.h. Give it any number you want. I have given it 299.

So you've got a pretty toolbar, a menu and a status bar. All you need to do now is add code to perform an action when the user interacts with them.

Handling user interactions

Every time a user clicks on a menu item or a button on the toolbar, a WM_COMMAND message is sent to your application. The WM_COMMAND message looks like:

WM_COMMAND 
wNotifyCode = HIWORD(wParam); // notification code 
wID = LOWORD(wParam); // item, control, or accelerator identifier 
hwndCtl = (HWND) lParam; // handle of control 

Parameters

wNotifyCode
Value of the high-order word of wParam. Specifies the notification code if the message is from a control. If the message is from an accelerator, this parameter is 1. If the message is from a menu, this parameter is 0.
wID
Value of the low-order word of wParam. Specifies the identifier of the menu item, control, or accelerator.
hwndCtl
Value of lParam. Identifies the control sending the message if the message is from a control. Otherwise, this parameter is NULL.

While we could work with wParam and lParam ourselves, Windows provides a better mechanism for handling WM_ messages - through the HANDLE_WM_ macro.

For WM_COMMAND, we'll use the HANDLE_WM_COMMAND macro defined as

HANDLE_WM_COMMAND(HWND hwnd, WPARAM wParam, LPARAM lParam, 
    (void *)OnCommand (HWND hwnd, int id, HWND hCtl, UINT codeNotify))

Paste the code that follows in interface.c:

#include <windowsx.h>

	...

// Handler for WM_COMMAND
void OnCommand(HWND hwnd, int id, HWND hCtl, UINT codeNotify)
{
	switch(id)
	{
	case IDM_FILE_EXIT:
		PostQuitMessage(0);
		break;
	}
}

// Callback procedure
LRESULT CALLBACK MainWndProc(HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)
{
	switch (msg)
	{
	case WM_COMMAND: // Handles user input
		HANDLE_WM_COMMAND(hwnd, wParam, lParam, OnCommand);
		break;
	case WM_DESTROY:
	...

Hit F9 to compile and run. You'll see that clicking on File -> Exit now quits the application.

windowsx.h

This header files contains many macros designed to make coding easier. Take a look at it. Some of the stuff in there is really interesting. You'll find it in the include folder of your development environment.

This concludes this lesson. You've learnt a lot today. You can now create basic Windows UI elements and can write code to perform an action when the user interacts with them. In the next lesson, we'll take a look at resources and how they can make certain aspects of programming easier. We'll also take a look at internationalization issues and how to write code that's easy to port to other languages and locales.

Next Lesson4

Last Updated: May 14, 2004 • This page and its contents are copyright © 2002-2004 Pravin Paratey • Copyright NoticeDisclaimer
1