Rodney Rushing
Microsoft Corporation
July 1999
Summary: Microsoft Windows® CE contains a new control called the CommandBand, a hybrid of the “rebar” control found on the Windows desktop and server platforms and the traditional menu bar. CommandBand controls can host buttons, menu items, and even combo boxes. With the CommandBand, Windows CE provides a compact, efficient visual control that serves multiple purposes without consuming too much valuable screen real estate. (15 printed pages)
Click to download the Check List sample files.
The Windows CE operating system contains many of the same visual controls and elements as its traditional desktop cousin. In fact, developers looking to build GUI-based applications for Windows CE will find that the skills and experience they acquired while constructing Windows desktop and server applications also apply to Windows CE.
Windows CE contains a new control called the CommandBand, a hybrid of the “rebar” control found on the Windows desktop and server platforms and the traditional menu bar. CommandBand controls can host buttons, menu items, and even combo boxes. With CommandBand controls, Windows CE provides a compact, efficient visual control that serves multiple purposes without consuming too much valuable screen real estate.
The Check List Sample, available for download at the beginning of this article, demonstrates the use of several Win32® programming interfaces, combining them into a functional application. The primary purpose of this application is to demonstrate the use of the CommandBand (rebar) control. However, several ancillary Windows CE development concepts are also explored. Minimal error checking is done to make the sample concepts a little easier to study.
The program functions as a simple checklist manager, enabling users to create multiple categories of check lists, then add items to each check list.
Figure 1. Category view on a Palm-size PC
Items can be “checked” off of the list to show completion of the items that were checked. Further, each item also has a value associated with it that is added to a total when the item is checked. For example, in a grocery list, the value might indicate the estimated cost of each item. As each item is checked, its value is added to the check list “total.”
Figure 2. Check list view on a Handheld PC
Categories and items can be created, edited, and deleted, and all state information is persisted in a Windows CE database.
The first screen displayed is always the categories view. On first execution, this view will contain a rebar with a menu and a toolbar. The rebar can be customized by:
To use the rebar context menu, users can Alt+click or double-click in any command bar area. A popup menu will display, showing the available command bands for that view and their current status. Selecting an item in the context menu will toggle the visibility of the respective command band. The rebar configuration is saved separately for category and check list views.
The menu bar and command bar are used to add, delete, or edit an item in the current view, or to exit the program. They are also used to switch views between categories and check lists. The check list view has a special toolbar, which contains a ComboBox and a read-only edit field. The ComboBox is used to switch directly to a different check list. The edit field shows the sum of the values for all items that are currently checked.
All changes to rebars, categories, and check lists are stored at the time the changes are made. In debug mode, an extra button will appear on the common toolbar. This button is used to delete the current application database and registry keys for debugging purposes.
The rebar control extends the capabilities of the command bar by allowing for multiple command bars that can be arranged adjacent to each other and in rows. The rebar allows this arrangement to occur programmatically, or through manipulation of the user interface by the user.
Each command bar is owned by a band, and the rebar manages these bands. The band effectively adds visual decorations to a command bar, such as a gripper to drag, maximize, and restore the command bar, and a smart label icon that appears when a command bar is minimized. Bands are not controls themselves but rather a logical context for each command bar within the rebar control.
Figure 3. Relationship between the rebar control, bands, and command bars
To add buttons, tooltips, menus, or other controls to a command bar, the developer must simply obtain the handle of the command bar from its parent band and manipulate the command bar normally.
The rebar provides no user interface for selectively disabling a particular band, so we’ve created one. The method we’ve chosen is a context menu that pops up if the user Alt+clicks or double-clicks a command bar. We chose these actions, but the subclass routine can be easily adapted for any desired action that a Windows control can support.
The reason we have subclassed the command bars is that they are children of the rebar and therefore always on top of the rebar. This means that our user actions will be received by the command bars first and may not be passed onto the rebar. By subclassing the command bar, we know that we’ll always get first shot at any mouse messages that are sent to the command bar. The bands themselves are not actually controls, so we can’t subclass those.
The subclassing is accomplished by obtaining the command bar’s real window procedure, saving it in the bar’s user data area, and then pointing the window to our custom window procedure.
void CLRebar_EnableContextMessage(
HWND hwndCmdBar )
{
…
lOriginalProc = (LONG)GetWindowLong( hwndCmdBar, GWL_WNDPROC );
SetWindowLong( hwndCmdBar, GWL_USERDATA, lOriginalProc );
SetWindowLong( hwndCmdBar, GWL_WNDPROC, (LONG)CLRebar_CommandBarProc );
…
}
The subclassing is performed every time a band is added to a rebar in the following sequence: Create a new band, obtain the command bar’s handle, subclass the command bar.
Now, when the user performs the “context menu” action on a command bar, we capture that message:
// Either a double tap or an ALT+tap will work.
if ( ( uMsg == WM_LBUTTONDBLCLK ) ||
( ( uMsg == WM_LBUTTONDOWN ) && ( 0x8000 & GetKeyState(VK_MENU) ) ) )
{
CLRebar_SendContextMessage( hwnd, LOWORD(lParam), HIWORD(lParam) );
return 0;
}
and send our application-defined WM_CL_BARCONTEXT message to the frame window.
void CLRebar_SendContextMessage(
HWND hwnd,
int x,
int y )
{
…
SendMessage( CLGlobal_hwndFrame, WM_CL_BARCONTEXT, 0, MAKELONG(pt.x,pt.y) );
…
}
We send the coordinates of the mouse action in our message so that these coordinates can be used to position the context menu at the same location.
The frame window routes this message to the current view procedure, which then displays a popup menu containing the bands that are available for that particular view.
case WM_CL_BARCONTEXT: // This message is sent by our command bar
// subclassing when the user requests a
// command bar context menu.
// Display the category view’s context menu.
hmenuPopup = LoadMenu( CLGlobal_hInstance, MAKEINTRESOURCE(CLMENU_POPUPS) );
hmenuPopup = GetSubMenu( hmenuPopup, POPUP_CATEGORY );
TrackPopupMenu( hmenuPopup,
TPM_LEFTALIGN|TPM_VERTICAL|TPM_NONOTIFY,
LOWORD(lParam),
HIWORD(lParam),
0,
CLGlobal_hwndFrame,
NULL );
return TRUE;
The check marks, which reflect the current visibility state of each band type, are generated by simply capturing the WM_INITMENUPOPUP message that is sent when any menu is about to appear, and then checking the rebar for the existence of each band type.
For this sample, we have chosen to implement the rebar interface as a collection of predefined bands and respective command bars. There are four possible command bands to add to the rebar: a category menu, a check list menu, a common toolbar, and a check list toolbar. Our rebar can’t contain two of the same command bar, so we’ve assigned each band a type value, which will also serve as the band’s ID. This way we can determine if a particular band type is visible by looking for the corresponding band ID in the rebar.
The allowable band types in a particular view are determined by the initial rebar layout defined by each view and the command bar selections that are available from the command bar context menu.
The sample contains examples of creating, reading, and writing registry keys and values. In using the system registry, we are able to save the state of the CommandBand between uses of the application.
To persist the layout of the bands in a rebar control, the application must save and restore the position information for each band. The CommandBands_GetRestoreInformation(..) function is used to obtain the position information about a band. It returns everything we need to know: the ID(type) of the band, the size of the band (which determines the position of the next band), the maximized state, and the style of the band.
cbri.cbSize = sizeof(COMMANDBANDSRESTOREINFO);
for ( uBandIndex = 0; CommandBands_GetRestoreInformation( hwndRebar, uBandIndex, &cbri );
uBandIndex++ )
{
if ( *(SWORD*)&cbri.wID < 0 )
continue;
wsprintf( achValueName, TEXT(“Band%d”), iIndex );
cbData = sizeof(COMMANDBANDSRESTOREINFO);
CLGlobal_PutRegData( achSubkeyName, achValueName, (LPBYTE)&cbri, cbData );
iIndex++;
}
Because the position of a band is inferred from the size of the preceding band, the band order is important. This is not part of the restore information, but we can determine the order by retrieving bands by position index rather than band ID. Rows are also determined by band order. The first band of each row, except the first row, has the RBBS_BREAK style. The restore information includes style flags, so we’re covered here.
When retrieving the band information from the rebar, we will also receive information about any adornments we have added. We will want to ignore adornments because they are not created like other bands. Adornments have fixed identifiers, which are negative, and are always located in the same position. Since all of our band IDs will be positive, we’ll simply ignore any bands that have negative IDs.
The band information is stored in the application’s registry key. Each rebar in the application has a unique subkey under the application key. The rebar key contains values for each band in its layout. The name of each band’s registry value corresponds to its position in the rebar, allowing us to simply read the values sequentially to restore the previous band order.
for ( iIndex=0;;iIndex++ )
{
wsprintf( achValueName, TEXT(“Band%d”), iIndex );
cbData = sizeof(COMMANDBANDSRESTOREINFO);
if ( !CLGlobal_GetRegData( achSubkeyName,
achValueName,
(LPBYTE)&pBands[iIndex],
&cbData ) )
break;
}
Restoring the band layout is pretty much a simple matter of sequentially reading the band info into a COMMANDBANDSRESTOREINFO array, and then creating each band in the array. The maximized state is the one exception to this. A band cannot be created in a maximized state, so after we have added the bands to the rebar with correct position and style, we traverse the band information array checking for the fMaximized flags that are true and send the RB_MAXIMIZEBAND message to those bands.
for ( i=0; pBands[i].wID != CLEND_OF_LIST; i++ )
{
if ( pBands[i].fMaximized )
SendMessage( hwndRebar, RB_MAXIMIZEBAND, (WPARAM)i, 0 );
}
If a band’s position is changed in any way, the rebar’s parent receives an RBN_LAYOUTCHANGED notification. This includes band maximizing, restoring, dragging, and resizing of the rebar. Any time the layout changes, we update the registry.
Changing the visibility of a band is handled in the same way, except that there is no notification for changing visibility. Visibility is updated in the registry when the command bar context menu message is received. The command bar context menu was discussed earlier.
If a band is dragged to a different row, the rebar may become taller or shorter. This will cause an RBN_HEIGHTCHANGE notification to be generated. When a view receives this event, it resizes the client area so that it occupies the area underneath the rebar.
To manage band information when a rebar doesn’t yet exist, we use an array of COMMANDBANDSRESTOREINFO structures. When creating a new rebar, the application calls the build routine with an array containing the default layout. The build routine modifies this array with any layout information it finds in the registry, and then uses the modified array to create the new rebar.
HWND CLRebar_Build(
UINT uRebarID,
COMMANDBANDSRESTOREINFO* pBands ) // Default layout
{
…
// Get band information from the registry...if there is any.
CLRebar_RestoreBandsInfo( uRebarID, pBands );
// Create the specified bands.
for ( i=0; pBands[i].wID != CLEND_OF_LIST; i++ )
CLRebar_DoAddBand( hwndRebar, &pBands[i] );
…
}
The array is always terminated with a final info structure that has an identifier of 0xFFFF.
Many other concepts are covered in this sample that are investigated more deeply in other samples and white papers. The Check List sample delves into the intricacies of using ListView controls and accessing and manipulating the Windows CE internal database.
This sample is a Multi-Document Interface (MDI) application of sorts, but Windows CE does not support the MDI Win32 API. So, we’ve devised a method to give us the simplest of view implementations. The application consists of a frame window and a logical view. The view can be either the category view or one of several check list views.
These views are not windows, but merely a context that determines which rebar control, ListView control, and view window procedure are used for the two modes of operation. When either view becomes active, the currently visible view controls are hidden (the rebar and the ListView controls), the new view’s controls are shown, and the new view “plugs in” its window procedure to receive messages. This is accomplished in the frame window procedure by calling the *_Show function of each view.
BOOL CLCat_Show(
BOOL fShow )
{
…
if ( !CLCat_hwndView ) // Have we not intialized before?
{
if ( fShow ) // If we’re showing the view...
CLCat_Init(); // ...create the controls.
}
else
{ // If the controls already exist, just hide/unhide them.
CommandBands_Show( CLCat_hwndRebar, fShow );
ShowWindow( CLCat_hwndView, fShow );
}
// Put keyboard focus on the listview control.
if ( fShow )
SetFocus( CLCat_hwndView );
// The frame procedure should let us have a crack at notifications
// if we’re becoming the current view.
CLGlobal_wndprocClientHandler = fShow ? CLCat_MessageHandler : NULL;
return TRUE;
}
The rebar and window controls are all owned by the frame control. In order to isolate processing for a particular view context into a common area, we implemented the view window procedure. This is simply a window procedure that the frame window procedure calls before it handles any messages. The function is called through a global function pointer. Each view stores its window procedure in this pointer when it becomes active, which gives the view first crack at any messages the frame window procedure receives.
The database is implemented using the CeDatabase* family of functions. A simple database structure was used where all records have a common format. There are two types of records that are stored in the database: categories and check list items. The common format that these records use is as follows:
Property | Description | Type | Note |
1 | Record Type | short | Category or Item |
2 | Name of category or item | String | Up to 32 characters |
3 | Category identifier | Uint | Unique for each category |
4 | Check state | short | Boolean flag |
5 | Item Value | BLOB | Storage for a float value |
The first property is used to determine if the record is for a category or a check list item. Property 2 specifies the name for the category or check list item. Property 3 is used to “bundle” all of the records for a particular check list category together. When a category is created, it is assigned this unique identifier which all check list items for that category will also get. Property 4 indicates if a check list item is checked or not. We used the smallest type available to store a Boolean value. Property 5 is a binary large object (BLOB) that holds a float. We chose blob for two reasons: to demonstrate using a BLOB, and because we don’t know that a float will fit in the space of the other property types for a given platform. So we store the float as binary data.
One important thing to note about the use of the BLOB to store a “simple” data type, is that BLOBs are only required to be byte-aligned. Our float must be aligned on larger size boundary for some platforms, so we must obtain the float from the BLOB in a fashion that is not dependent on alignment. For our implementation, memcpy was used:
memcpy( &pRecord->rValue, pval->blob.lpb, sizeof(float) );
The database sort order determines how the database will find the “next” record during our searches. For example, in the case where we are filling the category ListView control, we want to search for records that contain a category type. But when filling the check list, we want all records with a particular category identifier. The database routines demonstrate the use of multiple sort orders through the use of multiple handles to the same database.
CommandBand controls are a unique visual element designed to make Windows CE applications easier to use and more intuitive to the user. With little additional effort, developers can quickly learn the intricacies of using the CommandBand and add it to their existing Win32 development knowledge. In so doing, Windows CE programmers can combine their experience in traditional Windows development with subtleties of Windows CE development and build powerful mobile solutions for the next generation of computing devices.
The following files are included in the sample:
CLStdHdr.cpp—Used to generate precompiled header.
CLMain.cpp—Program entry point and initialization.
CLFrame.cpp—Program main window processing. All ListViews and rebars are children of this window.
CLGlobal.cpp—Global variables and general utility routines.
CLCat.cpp—Processing for category view. Includes processing of commands and notifications for this view.
CLList.cpp—Processing for check list view. Includes processing of commands and notifications for this view.
CLData.cpp—Database functions.
CLRebar.cpp—Rebar functions.
All functions and variables implemented in this sample follow the following naming convention:
CL<file>_<name>
For example, the function CLGlobal_DeviceType() is located in file CLGlobal.cpp.
CommandBands_AddAdornments
CommandBands_AddBands
CommandBands_Create
CommandBands_GetCommandBar
CommandBands_GetRestoreInformation
CommandBands_Height (RB_GETBARHEIGHT)
CommandBands_IsVisible
CommandBands_Show
RB_DELETEBAND
RB_IDTOINDEX
RB_MAXIMIZEBAND
CommandBar_AddBitmap
CommandBar_AddButtons (TB_ADDBUTTONS)
CommandBar_AddToolTips (TB_SETTOOLTIPS)
CommandBar_InsertComboBox
CommandBar_InsertMenubarEx
The sample was at least run successfully on the following platforms:
Other platforms may be supported, but haven’t been verified by the author of this sample application.
Rod Rushing is a Technical Support Professional for Microsoft specializing in C/C++ and Assembly Language on Windows platforms.