Windows Programming/User Interface Controls

From Wikibooks, open books for an open world
Jump to navigation Jump to search

Some predefined window classes are intended for use as user interface controls. They're commonly known as "standard Windows controls" and "common controls".

Usages of these UI controls should be documented in task-oriented categories, not on an API-oriented basis.

Standard Windows Controls[edit | edit source]

Standard Windows controls are implemented in USER32.DLL (former USER.EXE), and later (Windows XP+) subclassified by COMCTL32.DLL for Luna look&feel.

These are always present. No initialization is necessary. Available with Windows 2, ListBox and ComboBox with Windows 3.

Static Control[edit | edit source]

Static controls are “controls” with no interaction with user.

A static control is a child window that displays either text, bitmap image, or icon. Static controls cannot be selected and do not receive focus from the keyboard. They can, however, receive mouse input with use of SS_NOTIFY. This will allow the control to detect click and double-click with the WM_NOTIFY event from its parent.

Static Text (Label)[edit | edit source]

A label should be placed before the control it refers. When done so, pressing Alt+Hotkey will move the focus to the next possible control automatically with no line of code.

Labels can also be used for showing the appropriate physical unit right to a Single-Line Edit Control. Because such text never acts as a label, the logical order (Z order) is not important. The style bit SS_NOPREFIX can be used to avoid problems with Ampersand (&) character.

Example Code[edit | edit source]
Create Label

The label Static Control should be a child of another window, either a main window or a child window. To create it, all you need to do is call CreateWindow() with the STATIC class and the parameters of your choice. Below is the basic code to create it.

/*Create a Static Label control*/

hwndLabel = CreateWindow(
                        TEXT("STATIC"),                   /*The name of the static control's class*/
                        TEXT("Label 1"),                  /*Label's Text*/
                        WS_CHILD | WS_VISIBLE | SS_LEFT,  /*Styles (continued)*/
                        0,                                /*X co-ordinates*/
                        0,                                /*Y co-ordinates*/
                        50,                               /*Width*/
                        25,                               /*Height*/
                        hwnd,                             /*Parent HWND*/
                        (HMENU) ID_MYSTATIC,              /*The Label's ID*/
                        hInstance,                        /*The HINSTANCE of your program*/ 
                        NULL);                            /*Parameters for main window*/

Using the Label

Setting Label's Text

To set the text of your Label (static control), use the SendMessage() function with the WM_SETTEXT message.

/*Setting the Label's text
/*You may need to cast the text as (LPARAM)*/

SendMessage(
   hwndLabel ,                /*HWND    Label*/
   WM_SETTEXT,  	      /*UINT    Message*/
   NULL,                      /*WPARAM  Unused*/
   (LPARAM) TEXT("Hello"));   /*LPARAM  Text*/

Image (Bitmap, Icon, Metafile)[edit | edit source]

With other style bits, bitmaps or icons can be shown – with no line of code when the source image is placed in the same resource.

Metafile support was added with Windows 95.

Rectangles (Divider lines when tall)[edit | edit source]

Static controls can be used to draw grouping rectangles or divider lines.

Button[edit | edit source]

Buttons are controls with a simple user interface.

Push Button[edit | edit source]

Everyone should be familiar with the windows push button. It is simply a raised square with text inside it, and when you click usually something happens.

Example Code[edit | edit source]

As with all windows controls the push button is a child of your window be it the main window or another child. So to implement it into your program you merely need to call the CreateWindow() function. Below is a line of code to create a button.

Create Button
// create button and store the handle                                                       

HWND hwndButton = CreateWindow (TEXT("button"),                      // The class name required is button
                               TEXT("Push Button"),                  // the caption of the button
                               WS_CHILD |WS_VISIBLE | BS_PUSHBUTTON,  // the styles
                               0,0,                                  // the left and top co-ordinates
                               100,300,                              // width and height
                               hwnd,                                 // parent window handle
                               (HMENU)ID_MYBUTTON,                   // the ID of your button
                               hInstance,                            // the instance of your application
                               NULL) ;                               // extra bits you don't really need

this will create the button for you but it will NOT do anything on a click. In order to do that you need to go into your Windows Procedure function and handle the WM_COMMAND event. In the WM_COMMAND event the low word in the wparam is the ID of the child that has caused the event. So to ensure that you have received a message from your button ensure that the ID's are the same.

Test button ID
// compare button ID to message ID
if(ID_MYBUTTON == LOWORD(wparam))
{ 
   /* it's your button so do some work */ 
}

So now you know that your button has been pressed you need to find out what has happened to it so we process the notification code that is stored in the High Word of the wparam. The notification code you need to watch for is BN_CLICKED.

Test notification
// compare Notification to message Notification
if(BN_CLICKED == HIWORD(wparam))
{ 
   /* the button has been clicked do some stuff */ 
}


One last thing you may need to know is that lparam contains the handle to the button pressed.

Ownerdrawn Button[edit | edit source]

This type can implement anything. In many cases, such buttons are disabled (WS_DISABLED, i.e. no user input) and used to draw anything else into a dialog. In that case, it acts as a handy placeholder for the actual painting, as the pixel size of the dialog and thus the control varies on the user's dpi setting and current font metric.

Split button[edit | edit source]

Introduced with Windows Vista, this button has an additional combo box drop-down field, typically to change the behaviour of this push button before pressing.

Checkbox[edit | edit source]

Checkboxes can be solely or collected to a group. In a group, a multiple-choice selection can be done. Furthermore, a Three-State checkbox is possible. This state should be used for “Don't know”, or “Differently checked choices below” as in Windows Setup Control Panel application.

For a good implementation of checkboxes, following rules should be followed at compile time, typically using a Resource Editor:

  • All checkboxes should have the BS_AUTOCHECKBOX style bit set, otherwise, program logic must handle clicks
  • A solely checkbox or the first checkbox of a group should have WS_GROUP style bit set
  • Other checkboxes should not have the WS_GROUP style bit
  • The next control after the checkboxes must have WS_GROUP set. Otherwise, the arrow right/down key will not circle the focus around but fall into the next control (perhaps an Edit control)
  • Checkbox groups must be contiguously ordered
  • Placing WS_TABSTOP is not mandatory. Typically, every checkbox get this style bit set.

If done so, the Arrow keys will move the focus as expected, and the space bar will toggle its state without one line of code.

A check box group should not be used for more than 7 options. If more options are available, or the count is unknown at compile time, a Checked List Box should be used.

Radio Button[edit | edit source]

Radio buttons are always in a group of at least 2, typically 3..7 choices. They release each-other, and only one can be selected (checked) an one time. Although possible, program logic should avoid that no or more than one radio button is checked. Windows (more precisely, the function IsDialogMessage() which is processed in a message pump loop as when calling DialogBox()) ensures this behaviour with no line of code automatically when:

  • All buttons have BS_AUTORADIOBUTTON style bit set
  • The first Radio Button of a group has WS_GROUP style
  • The other Radio Buttons has no WS_GROUP style
  • The Radio Buttons are contiguous ordered in dialog template resp. CreateWindowEx() calls (that is, the Z order)

Radio buttons should not have the WS_TABSTOP style bit set. Windows automatically sets the focus on TAB key to the selected radio button.

Radio buttons should not be used for more than 7 options. (This is out of the "3..7 choices rule" for good GUI design.) If more options are available, or the count is unknown at compile time, a combo box should be used.

The next control after the Radio Button group should have WS_GROUP bit set. Otherwise, the arrow right/down key doesn't work at user's expectation: The focus will jump to the next control instead of circling around the Radio Button group.

Example Code[edit | edit source]

That's all you need to implement a button in a Win32 Application using the API

Radio Button
HWND hRadio =CreateWindow(TEXT("button"), TEXT("&Red"),
                WS_CHILD | WS_VISIBLE | BS_AUTORADIOBUTTON,
                20, 155, 100, 30, hDlg, (HMENU)ID_RED, GetModuleHandle(NULL), NULL);

Btw. almost nobody creates radio buttons with CreateWindow. Typically it is placed into an RC file with following line:

 AUTORADIOBUTTON "&title",id,left,top,width,height[,morestyles]

One ampersand for the window title will create a hotkey function. While CreateWindow() takes pixel coordinates, the RC file entry takes Dialog Base Units for the four metrics.

Group Box[edit | edit source]

This is not a real button and has no interaction at all. It's class name is "BUTTON" too. This element is typically used to visually group radio buttons. It does not create a child element as its .NET counterpart.

Group boxes should be placed before its visual content. When done so, pressing Alt+Hotkey will move the focus to the next possible control automatically, with no line of code.

Scroll Bar[edit | edit source]

Whereas Scroll Bars can be attached to any window or control (and is not a window with a handle itself), a Scroll Bar Control is a true window with a handle. These can be created horizontally (SB_HORZ) or vertically (SB_VERT).

In the days before the Common Controls Progress Bar and Track Bar, someone had “misused” this control to set the volume for a speaker, or to show the progress of a lengthy process. Nowadays, single scroll bars are never used for such purposes.

However, scroll bars are necessary for sharing a Status Bar with a Horizontal Scroll bar, as known from Acrobat Reader or some office software. Because standard scroll bars are always as long as its window, not shorter.

Edit Control[edit | edit source]

The Edit control is the standard base object for text editing and display (it is commonly called a TextBox).

The Edit control has a quite large number of styles.

Single-Line Edit[edit | edit source]

Single-line edit controls are typically used for entering short descriptions, file names, parameters, and numbers. Although the style WS_VSCROLL is supported and shows a small vertical scroll bar like an up-down control, nothing else happens. Typically, ES_AUTOHSCROLL is used to move the content horizontally when text does not fit into the given space. The font can be changed programatically, but intermixing fonts and/or colors is not possible. For such purposes, the RichEdit control exists.

Multi-Line Edit[edit | edit source]

Multi-line edits look like Notepad.exe. Indeed, Notepad is just an Edit control with a frame window that supports the menu, load/save, resizing etc. All other functionality, even the context menu, the Unicode and Right-To-Left support, is already built into this control.

// create a text box and store the handle                                                       

HWND hwndText = CreateWindow(
                               TEXT("edit"),                              // The class name required is edit
                               TEXT(""),                                 // Default text.
                               WS_VISIBLE | WS_CHILD | WS_BORDER | WS_HSCROLL | WS_VSCROLL| ES_MULTILINE | ES_AUTOHSCROLL, // the styles
                               0,0,                                      // the left and top co-ordinates
                               100,300,                                  // width and height
                               hwnd,                                     // parent window handle
                               (HMENU)ID_MYTEXT,                         // the ID of your editbox
                               hInstance,                                // the instance of your application
                               NULL
                            );                                   // extra bits you dont really need

Here are the styles used in the example:

WS_BORDERA thin border around the text area.
WS_HSCROLLDisplay the horizontal scroll bar.
WS_VSCROLLDisplay the vertical scroll bar.
ES_MULTILINEThis is a multiline text box.
ES_AUTOHSCROLLNo word wrapping.


	// Set the text.
	SendMessage(hwndText, WM_SETTEXT, 0, (LPARAM)"Hello");

	// Get the text.
	LRESULT iTextSize = SendMessage(hwndText, EM_GETLIMITTEXT, 0, 0);
	char *szText = new char[iTextSize];
	SendMessage(hwndText, WM_GETTEXT, iTextSize, (LPARAM)szText);

Multiline edits require "\r\n" as line delimiter, nothing else. Otherwise, strange behaviour on some Windows versions may occur. The accompanying parent window dialog style DS_LOCALEDIT is superfluous for Win32. There is no way to access the internal text buffer directly (EM_GETHANDLE doesn't work), so you have to use GetWindowText() with a sufficiently large buffer to get a copy.

Edit controls behave differently in comparison to all other controls as they notify the parent with EN_CHANGE and EN_UPDATE when changing its content programmatically. An infinite loop can easily occur when multiple edits have circular dependencies. There are some workarounds to cope with this problem:

  • On EN_CHANGE, call SetTimer with some small delay. And after SetWindowText/SetDlgItemText/SetDlgItemInt, call KillTimer to remove the timer before the WM_TIMER handler gets called. Handle actual change in timer routine. This can be handy if OnChange processing is somehow lengthy but pressing the ENTER key should be avoided. For example for setting the dish position for a radio receiver where you can enter both an arbitrary longitude or a step-motor position.
  • On EN_CHANGE, check for EM_GETMODIFY. Clear the Modify Flag before SetWindowText.
  • Use a global variable ("bool NoEditChange") that you check on EN_CHANGE. Set it to true before SetWindowText, and to false when you are done. As usual dialogs are fully single-threaded, no race condition will occur here.

List Box[edit | edit source]

A list box can have tabulators so it can show somehow tabulated entries. This control is not so commonly used, except in spreadsheet-alike applications. It's main purpose is to deliver the Drop-Down Combo Boxes the pop-up List Box.

Combo Box[edit | edit source]

This control has three appearances.

Simple Combo Box (non-editable)[edit | edit source]

This type is very rarely used and not worth an explanation.

Non-editable Drop-Down Combo Box[edit | edit source]

Use this when the user has to choose out of some options.

On creation, use a large height. It sets the maximum height of the dropped-down list box. If less options are available, Windows will shrink its length automatically, but never extends. And scrolling here is annoying. (Note that Windows 3.x doesn't shink.) The height of the remaining control is 13 dialog units. (You need the number 13 when you combine it with similar Edit controls.)

// create a combo box and store the handle                                                       

HWND hwndCombo = CreateWindow (TEXT("combobox"),                         // The class name required is combobox
                               TEXT(""),                                 // not used, ignored.
                               WS_CHILD | WS_VISIBLE | CBS_DROPDOWNLIST, // the styles
                               0,0,                                      // the left and top co-ordinates
                               100,300,                                  // width and height
                               hwnd,                                     // parent window handle
                               (HMENU)ID_MYCOMBO,                        // the ID of your combobox
                               hInstance,                                // the instance of your application
                               NULL) ;                                   // extra bits you dont really need

The displayed height of the actual widget will be automatically changed depending on the font. The "unused" part of the height will be devoted to the size of the drop down menu.

For example, only 33 pixels of the 300 pixel height will be to the selected item. When the down button is clicked, the menu will be 267 pixels in height.

There are other combo box styles: CBS_SIMPLE (similar to a list box) and CBS_DROPDOWN (similar to CBS_DROPDOWNLIST but the selected field is editable).

// Add a list of strings to the combo box.

SendMessage(
              hwndCombo,                    // The handle of the combo box
              CB_ADDSTRING,                 // Tells the combo box to append this string to its list
              0,                            // Not used, ignored.
              (LPARAM) "Item A"             // The string to add.
           );

SendMessage(hwndCombo, CB_ADDSTRING, 0, (LPARAM) "Item B");
SendMessage(hwndCombo, CB_ADDSTRING, 0, (LPARAM) "Item C");
SendMessage(hwndCombo, CB_ADDSTRING, 0, (LPARAM) "Item D");
SendMessage(hwndCombo, CB_ADDSTRING, 0, (LPARAM) "Item E");


// Select the default item to be "Item C".
SendMessage(
              hwndCombo,                    // The handle of the combo b,
              CB_SETCURSEL,                 // Tells the combo box to select the specified index
              2,                            // The index of the item to select (starting at zero)
              0                             // Not used, ignored.
           );

Editable Drop-Down Combobox[edit | edit source]

Use it when the user can enter single-line text or can take some predefined strings. A typical example is choosing an URL or number with history function. Or selecting a port address, allowing to enter a yet-not-known port address.

Common Controls[edit | edit source]

Common controls were introduced roughly with Windows 3.11, and somehow constantly expanded both in functionality and in appearance. Note that, when using Windows XP Luna style or newer, the standard controls above gets automatically subclassified by comctl32.dll's code. A manifest resource controls this behaviour.

The version and functionality of Common Controls DLL heavily depends on version of Internet Explorer installed.

Header and Linking Requirements[edit | edit source]

Using Common Controls requires linking to comctl32.lib and including commctrl.h. Whereas some controls are there by default, some other need initialization with InitCommonControlsEx(). It depends on operating system, so it's wise to always call InitCommonControlsEx() with the right bits for the controls your application needs.

The function InitCommonControls() does nothing but ensures that the library is loaded when you don't link to any other Common Controls library function (like CreateToolBar()). Surely, the Windows 3.11 classes were registered at load time of the DLL.

Common controls are typically used like regular dialog elements. The dialog resource template, introduced roughly with Windows 2.0, reserves space for user class names if these are not STATIC, BUTTON, EDIT, LISTBOX, COMBOBOX, or SCROLLBAR, the standard controls above. Therefore, examples may show how to create a child window using CreateWindowEx(), but that's quite uncommon because including them into a dialog template is much easier.

Tool Bar[edit | edit source]

Introduced: Windows 3.11

case WM_VSCROLL:  
   {  
       int scrollMsg = (int)LOWORD(wParam);  
       int pos = (short int)HIWORD(wParam);  

       SCROLLINFO scrollInfo;  

       GetScrollInfo(hTextOutput, SB_VERT, &scrollInfo);  

       switch (scrollMsg)  
       {  
           case SB_LINEUP:  
           {  
               if (pos > 0)  
               {  
                   pos--;  
               }  
           }  
           break;  
           case SB_LINEDOWN:  
           {  
               if (pos < scrollInfo.nMax)  
               {  
                   pos++;  
               }  
           }  
           break;  
       }  

       scrollInfo.fMask = SIF_POS;  
       scrollInfo.nPos = pos;  

       SetScrollInfo (hTextOutput, SB_VERT, &scrollInfo, TRUE);  
   }

Status Bar (Status Strip)[edit | edit source]

Note that the documentation of CalcEffectiveClientRect() is mostly erraneous: The first UINT/BOOL pair of the array is not used at all.

Date/Time Picker[edit | edit source]

Introduced: Windows 95

IP Address Input Field[edit | edit source]

Up/Down Control[edit | edit source]

Mostly it's attached to an edit window so it looks like one with a tiny vertical scroll bar. Indeed, the trick with the scroll bar was widely used in the days of Windows 3.x. But now it looks worse than this control.

Up/Down controls automate integer counting and limit watching. They can automatically fit to their “buddy” window, so no code for placement is necessary. Edit controls are automatically shrunken horizontally, as if a vertical scroll bar were added.

Possible buddy windows are single-line edits, progress bars, and trackbars.

Unluckily, Win32 Up/Down Controls won't work with decimals, in opposite to the .NET counterpart. So you have to code a bunch of boring lines to implement .NET behaviour.

Edits with Up/Down control should have a height of 14 dialog base units for best appearance. Unluckily, the text content appears a bit too high (i.e. misaligned) when using standard Windows scheme.

Tab Control[edit | edit source]

This control is rarely used directly. The Property Sheet uses it heavily.

A Tab Control is needed when more controls around the tabs are needed. Property Sheets can have only some buttons on the lower side. When really used, each tab should control a child dialog (WS_CHILD style set) which is then a child of the parent dialog (not a child of the tab control!). The resource should be carefully planned, so no code for actual placement is needed. While the Property Sheet initializes every child late on first invocation (to save time), it's up to the problem whether you copy this behaviour or initialize all childs at showup. Clicking a tab should ShowWindow(...,SW_HIDE) the current child dialog, and ShowWindow(...,SW_SHOW) the wanted one.

Child dialogs are introduced with Property Sheets (Win95), not with the Tab Controls (Win32s). These have the following behaviour:

  • All its controls disappear when the Child dialog disappears – i.e. true hierarchy
  • Their child's tab order will be processed as if these are in the parent dialog
  • Nesting childs are not officially supported
  • Each has its own Dialog Procedure
  • The Dialog Font must be the same as for the Parent Dialog

Tooltip[edit | edit source]

Balloon-style tooltips were introduced quite late, with Windows XP. Therefore, most programs still use standard black-on-yellow rectangular tooltips.

List View, Tree View[edit | edit source]

The Windows Explorer is a perfect example for both controls. The left pane usually shows a directory Tree, and the right pane a file List. Also, the desktop itself is (very similar to a) List View.

static const INITCOMMONCONTROLSEX icc={sizeof(icc),ICC_TREEVIEW_CLASSES};
InitCommonControlsEx(&icc);

Combo Box Ex[edit | edit source]

Introduced: Windows 95.

Extra features:

  • Image at the left side to the text
  • Different images possible for selected items (but rarely used)

Caveats:

  • Cumbersome initializing
  • Don't forget InitCommonControlsEx!
  • Larger binary resource

Checked List Box[edit | edit source]

Progress Bar[edit | edit source]

What it is[edit | edit source]

A standard bar graph that displays progress of an item. Shows a graphical representation of amount completed over amount total.

Example code[edit | edit source]

Create Progress Bar

The Progress Bar control should be a child of another window, either a main window or a child window. To create it, all you need to do is call CreateWindow() with the PROGRESS_CLASS class and the parameters of your choice. Below is the basic code to create it.

/*Create a Progress Bar*/

HWND hwndProgress = CreateWindow(
			PROGRESS_CLASS,		/*The name of the progress class*/
			NULL, 			/*Caption Text*/
			WS_CHILD | WS_VISIBLE,	/*Styles*/
			0, 			/*X co-ordinates*/
			0, 			/*Y co-ordinates*/
			200, 			/*Width*/
			30, 			/*Height*/
			hwnd, 			/*Parent HWND*/
			(HMENU) ID_MYPROGRESS, 	/*The Progress Bar's ID*/
			hInstance,		/*The HINSTANCE of your program*/ 
			NULL);			/*Parameters for main window*/
Using the Progress Bar
Changing position

There are three ways to increment/decrement the progress bar. PBM_DELTAPOS steps by a given number. PBM_SETPOS sets a specific position. PBM_SETSTEP sets an increment/decrement number, and PBM_STEPIT steps with that number.

Delta Position
PBM_DELTAPOS advances the progress bar with a number given as the wparam.
/*Advance progress bar by 25 units*/

SendMessage(	hwndProgress ,	/*HWND*/	/*Progress Bar*/
		PBM_DELTAPOS, 	/*UINT*/	/*Message*/
		25,		/*WPARAM*/	/*Units*/
		NULL)		/*LPARAM*/	/*Unused*/
Set Position
PBM_SETPOS advances the progress bar to the specified position in the WPARAM
/*Advances progress bar to specified position (50)*/

SendMessage(	hwndProgress ,	/*HWND*/	/*Progress Bar*/
		PBM_SETPOS, 	/*UINT*/	/*Message*/
		50,		/*WPARAM*/	/*Units*/
		NULL)		/*LPARAM*/	/*Unused*/
Stepping Position
PBM_SETSTEP specifies the amount of units to step. PBM_STEPIT advances by the amount of units given with PBM_SETSTEP (default 10 units).
/*Advances progress bar by specified units*/

/*If progress bar is stepped when at the progress bar's maximum,
/* the progress bar will go back to its starting position*/

				/*Set the step*/
SendMessage(	hwndProgress ,	/*HWND*/	/*Progress Bar*/
		PBM_SETSTEP, 	/*UINT*/	/*Message*/
		1,		/*WPARAM*/	/*Amount to step by*/
		NULL)		/*LPARAM*/	/*Unused*/

				/*Step*/
SendMessage(	hwndProgress ,	/*HWND*/	/*Progress Bar*/
		PBM_STEPIT, 	/*UINT*/	/*Message*/
		NULL,		/*WPARAM*/	/*Unused*/
		NULL)		/*LPARAM*/	/*Unused*/

Next Chapter[edit | edit source]

References[edit | edit source]

MSDN - Windows Controls