Implementing a Status Bar

Kyle Marsh

Microsoft Developer Network Technology Group

Created: April 2, 1992

ABSTRACT

A status bar is an area in a window that displays information about the current state of the window or application. The status bar can provide a variety of information, including descriptions of menu items, current keyboard-initiated modes such as NUM LOCK and CAPS LOCK, and brief messages. This article explains how to implement a 3-D status bar in the MicrosoftÒ WindowsÔ graphical environment and how to track information placed on the status bar.

WHAT IS A STATUS BAR?

A status bar is the area across the bottom of a window that displays information about the window or application (Figure 1).

Figure 1.

Status bars in early MicrosoftÒ WindowsÔ applications were simply areas of the window separated from the rest of the window by a single line. The next style was a two-dimensional (2-D) status bar, which looked like a raised bar. Now a popular style is a three-dimensional (3-D) status bar, which looks like a raised bar and has recessed areas in which the status information is displayed. 3-D style bars are only feasible with a VGA resolution display or better. An application intended for a lower resolution display should use 2-D status bars to improve readability.

ELEMENTS OF A STATUS BAR

The elements of a status bar are the status bar frame, text areas, separator, highlight, text area highlight, and text area shadow. Figure 2 shows the frame and text areas.

Figure 2.

Status Bar Frame. This is part of the background of the status bar. No text is displayed in this area. The status bar face is drawn in COLOR_BTNFACE as a series of vertical bars: the left edge of the status bar, the vertical separators between the text areas, and the right edge of the status bar.

Status Text Areas. These areas display the information for which the status bar exists. The text should be in COLOR_BTNTEXT with a COLOR_BTNFACE background color.

Figure 3 shows the separator, highlight, text area shadow, and text area highlight.

Figure 3.

Status Bar Separator. This is the single line, drawn in COLOR_WINDOWTEXT, along the top of the status bar that separates the status bar from the rest of the window.

Status Bar Highlight. This is the line next to the status bar separator that gives the status bar its raised appearance. In Windows version 3.1, the highlight should be drawn in COLOR_BTNHIGHLITE. In Windows versions prior to 3.1, COLOR_BTNHIGHLITE is not defined. In this case, the application should use a WHITE_BRUSH, which is the built-in button highlight color.

Status Text Area Shadow. These lines are drawn in COLOR_BTNSHADOW along the top and left of the status text area.

Status Text Area Highlight. These are the lines drawn along the bottom and right edges of a status text area. In Windows version 3.1, the highlight should be drawn in COLOR_BTNHIGHLITE. In Windows versions prior to 3.1, COLOR_BTNHIGHLITE is not defined. In this case, the application should use a WHITE_BRUSH, which is the built-in button highlight color.

CREATING A STATUS BAR

Determining the Status Bar Size

A number of elements determine when the size of the status bar is calculated.

First, an application needs the size of a window border that cannot be sized—in other words, the size of a thin window frame. The application obtains this value by calling the GetSystemMetrics function with the SM_CYBORDER value as its parameter. Drawing a status bar also uses 2, 3, 8, and 9 times this value, so it is a good idea to store these values as well.

Next, an application needs to select a font for the status bar. The following code shows how to create a font for use on a status bar.

HDC hDC;

char szHelv[] = "Helv";

int Fntheight; // height of the font

hDC = GetDC(NULL);

Fntheight = MulDiv(-10, GetDeviceCaps(hDC, LOGPIXELSY), 72);

StatbarPntData.hFontStatbar = CreateFont(Fntheight, 0, 0, 0, 400,

0, 0, 0,

ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,

DEFAULT_QUALITY, VARIABLE_PITCH | FF_SWISS, szHelv);

ReleaseDC(NULL, hDC);

After a font is selected, the height of the status bar can be calculated. The status bar height will be the height of the font and 7 times the thin border frame height. To calculate the height of the font, call the GetTextMetrics function to get the text metrics for the font and add the tmHeight and tmExternalLeading fields together. The 7 times the thin border height is used for:

The top of the status bar frame:1 times the thin border height

The bottom of the status bar frame: 2 times the thin border height

Status Bar Separator: 1 times the thin border height

Status Bar Highlight:1 times the thin border height

Status Text Area Highlight:1 times the thin border height

Status Text Area Shadow:1 times the thin border height

The application must also determine how wide to make the status text areas. For example, 3 times the maximum character width is a nice size to store the CAPS or the NUM designator. The text areas need to be large enough to display the information for the user.

Finally, the application needs the width of the status bar frame that lies on each side of the status bar as well as between each text area. For example, 8 times the thin border height gives a nice separation from the left side of the window and between each text area. Some applications use different text area separator sizes to customize the status bar.

Drawing the Status Bar

The most efficient way for an application to draw a status bar is to do so within the application’s window. By simply drawing the status bar instead of creating a separate window for it, the application avoids the overhead that another window would require. Moreover, if the status bar is drawn within the application’s window, it can be updated more rapidly when the information it is displaying changes or when the window it is in requires repainting. A status bar thus becomes simply an area of the application’s window that contains status information and some fancy formatting. Because the status bar does not occupy its own window, Windows cannot clip to ensure that the application does not overdraw the status bar. The application itself must ensure that it does not overdraw the status bar.

The application draws the status bar during WM_PAINT processing, usually placing it along the bottom of the window, although it can place the status bar anywhere within the window. The application draws each element of the status bar separately and must take care that an element does not cover another element’s area. If an element draws over another element’s area, either the element will obscure the other element, if the element is drawn after the other element, or a flicker will occur as the other element draws over the first element. Thus, the application must calculate each element’s size and placement exactly and cannot use draw-overs, such as drawing the frame to be the size of the whole status bar and then drawing each text area over the frame. Because no elements can overlap as they are drawn, the drawing order is arbitrary.

When drawing text on the status bar, use the ExtTextOut function rather than the TextOut function to minimize any flicker that may occur as the text is updated.

Adjusting the Status Bar

When the window that the status bar is in is resized, the status bar must react. If the window grows, the location of the status bar must be invalidated so that the status bar is removed. If the window shrinks, the area the status bar should occupy must be invalidated so that the status bar can be redrawn. To track growth or shrinkage, the application uses the WM_GETMINMAXINFO message that Windows sends to the window immediately before the window is sized. (Windows also sends this message to the window at other times that have no bearing on the status bar.) When the window receives a WM_GETMINMAXINFO message, it calls GetClientRect for the window and adjusts the top of the rectangle to be the bottom of the rectangle minus the height of the status bar. This new rectangle is the rectangle that contains the status bar. The application stores this rectangle in a global or a static variable. When the window receives a WM_SIZE message, it again calls GetClientRect and adjusts this rectangle as it did for the WM_GETMINMAXINFO message. By comparing the top of the rectangle obtained with the WM_SIZE message with the rectangle obtained with the WM_GETMINMAXINFO message, the application can determine whether the window has grown or shrunk. If the window grew, the application can invalidate the rectangle obtained with WM_GETMINMAXINFO; if the window shrank, the application can invalidate the rectangle from WM_SIZE.

TRACKING INFORMATION FOR THE STATUS BAR

Let’s look at how to track some common information to put on a status bar.

Current Menu Selection

One common item for a status bar is a text description of the current menu selection. This arrangement lets the application designer maintain small menu item names (one or two words) and give the user a complete description on the status bar.

To track the current menu selection, the application uses the WM_MENUSELECT message. Windows sends this message to a window when a user selects a menu item from the window’s menu. A menu item is selected when the menu is active and the user highlights the item with the mouse or the keyboard. Windows also sends the WM_MENUSELECT message to an application when the menu is being exited.

When Windows sends a WM_MENUSELECT message to a window, the LOWORD of lParam contains flags that give information about the menu selection.

Flag Description

MF_BITMAP Item is a bitmap.
MF_CHECKED Item is checked.
MF_DISABLED Item is disabled.
MF_GRAYED Item is grayed.
MF_MOUSESELECT Item was selected with a mouse.
MF_OWNERDRAW Item is an owner-draw item.
MF_POPUP Item contains a pop-up menu.
MF_SEPARATOR Item is a menu item separator.
MF_SYSMENU Item is contained in the System menu. The high-order word identifies the menu associated with the message.

In addition, the LOWORD of lParam signals that the menu is being exited.

The following table describes the values of wParam and the HIWORD of lParam when various flags are set.

flags = LOWORD(lParam) Comment wParam Contains HIWORD(lParam)

flags == –1 Menu is being exited. 0–Value has no meaning. 0–Value has no meaning.
flags & MF_POPUP Selected item is a pop-up menu. Handle to the pop-up menu. 0–Value has no meaning.
flags & MF_SYSMENU && flags & MF_POPUP Selected item is the System menu. 0–Value has no meaning. Handle to the System menu.
flags & MF_SYSMENU && !(flags & MF_POPUP) Selected item is a regular menu item on the System menu. SC_* code corresponding to menu item (SC_RESTORE, SC_MOVE, SC_SIZE, and so on). 0–Value has no meaning.
Otherwise Regular menu item. Menu item ID. 0–Value has no meaning.

The easiest way to display descriptions for regular menu items, both on the System menu and on the window’s menu, is to put the descriptions in a string resource with the menu item ID as the ID for the string. When the application determines that a menu item description needs to be displayed, it can do a LoadString with the menu item ID, passed to the application with the WM_MENUSELECT message, as in the following examples.

In the RC file:

STRINGTABLE {

SC_SIZE, "Size the window with the keyboard"

SC_MOVE, "Move the window with the keyboard"

SC_MINIMIZE, "Minimize the window"

SC_MAXIMIZE, "Maximize the window"

SC_CLOSE, "Close the window"

SC_RESTORE, "Restore the window"

SC_TASKLIST, "Switch to another window"

IDM_S1I1, "Menu is now on SubMenu1 Item 1"

IDM_S1I2, "Menu is now on SubMenu1 Item 2"

IDM_S1I3, "Menu is now on SubMenu1 Item 3"

}

In the application:

VOID SetStatbarText(HWND hwnd, int nID, BOOL redrawflg)

{

RECT rc, rcTemp, rcTemp2;

HDC hdc;

static int nLastID = -1;

if ( nID != nLastID ) {

if ( LoadString(g_hinst, nID, szStatBarText, 255) == 0)

_fstrcpy(szStatBarText,"Unknown Item");

}

.

.

.

Pop-up menus require a bit more work by the application and the application developer. Again, the descriptions can be put into the string table as are regular menu items, but the application developer must assign IDs to them. For example, in the resource file, place the following statements in the string table:

IDM_SYSMENU, "Move, size, minimize, maximize or close window"

IDM_OPTION, "Close, Set Idle Option, About StatBar..."

IDM_SUBMENU1, "SubMenu1"

If the System menu is selected, Windows notifies the application by setting the MF_SYSMENU bit in the flags field and the application displays the corresponding description. If a pop-up menu is selected, Windows sends the handle of the pop-up menu to the window; so the application must know the handles of all pop-up menus in the window in order to identify the pop-up menu selected. The application can obtain this information when the window is created by calling the GetMenu and GetSubMenu functions and storing the results. For example, the following code gets the handles to the five submenus in the sample application:

BOOL StatBar_OnCreate(HWND hwnd, CREATESTRUCT FAR* lpCreateStruct)

{

HMENU hMenu;

hSysMenu = GetSystemMenu(hwnd, FALSE);

hMenu = GetMenu(hwnd);

hOptionMenu = GetSubMenu(hMenu,0);

hPopupMenu1 = GetSubMenu(hMenu,1);

hPopupMenu2 = GetSubMenu(hMenu,2);

hPopupMenu3 = GetSubMenu(hMenu,3);

hPopupMenu4 = GetSubMenu(hMenu,4);

return TRUE;

}

When the application determines that a pop-up menu is selected, it compares the handle that Windows passed with the handles that the application stored when the window was created. When a match is found, the corresponding description is displayed on the status bar.

The processing that occurs when Windows sends each WM_MENUSELECT message can take some time. On slow systems (for example, an 80286), the processing can actually make the performance unacceptable. To avoid this situation, applications should keep processing at each WM_MENUSELECT message to a minimum. The best way to do this is to set a variable to indicate which text description to display while processing the WM_MENUSELECT message and actually display the status bar text while processing a WM_ENTERIDLE message. Windows sends the window a WM_ENTERIDLE message when no messages are waiting in its queue after it has processed one or more previous messages. Waiting for a WM_ENTERIDLE message minimizes performance reduction.

NUM LOCK and CAPS LOCK

Other common information to display on a status bar is the current state of the NUM LOCK and CAPS LOCK keys. To track the current state of these keys, an application calls the GetKeyState function with VK_CAPITAL (for CAPS LOCK) or VK_NUMLOCK (for NUM LOCK). The GetKeyState function returns an integer whose low-order bit indicates the current toggle state for the key. For example, the following code could be used:

// Num Lock Text

if ( GetKeyState(VK_NUMLOCK) & 0x0001) {

// NUM LOCK is ON

.

.

.

}

else {

// NUM LOCK is OFF

.

.

.

}

An application must update the CAPS LOCK or NUM LOCK status when the application gets the focus and when Windows sends a WM_KEYDOWN message:

When the application gets the focus. When the application did not have the focus, the state might have changed; so each time the application gets the focus, including at initialization, it must check the key states.

When Windows sends a WM_KEYDOWN message. The state of the key changes when the WM_KEYDOWN message is received. If the user holds down a key, Windows continues to send WM_KEYDOWN messages to the window, but the state of the key does not change. A WM_KEYUP message must be sent between WM_KEYDOWN messages for the key to change state.