Create Flexible Custom Menus in Windows CE

Wednesday Nov 13th 2002 by Nancy Nicolaisen
Share:

Learn to create flexible custom menus in your mobile WinCE applications by using combo boxes and dropdown buttons.

In this final installment of our explorations of Windows CE menus, we're going to learn how to implement a familiar control, the Combo Box. We'll also explore the use of a novel CE menu construct: the Dropdown button. Dropdown buttons are powerful tools, because they use very little menu real estate, but allow you great latitude in creating application specific functionality.

Adding Combo Boxes

Compared to buttons, adding a combo box to a command bar menu is a snap. There is really only one sticky point, and it's something that is sinuously woven throughout Windows CE: Unicode. More than any other single thing, Unicode will likely be the memorable porting gotcha of moving your code from Win32 to Win CE.

You can read more about exactly what Unicode is and why its such an integral design feature of Windows CE in the sidebar at the end of this article. Here are a few important Unicode rules to keep in mind when porting the user interface of your Win 32 application to Windows CE.

  • Always use the TEXT( ) macro when passing string literals as function arguments

  • Never assume a reference to a Unicode string is the same as the starting address for the string. When you need an address, use the & operator

  • Don't assume that a count of characters in a string is the same as the size of the buffer needed to hold the characters. Always multiply the character count ( plus a terminal null, if one is necessary ) by either sizeof(TCHAR) or sizeof(wchar_t) to calculate storage space.

  • Always use the string handling functions that are matched to the declared type of the strings you are manipulating.

Here's the code to add a combo box to a combo bar:

HWND    AddCombo( HWND hWnd )
{
    int i;
    TCHAR tszBuff[24];

    //we have to destroy the old one before expanding
    CommandBar_Destroy( hwndCB ); 
    hwndCB = CommandBar_Create(hInst, hWnd, 1);     

     // Insert the menu band
    CommandBar_InsertMenubar(hwndCB, hInst, IDM_MENU, 0);
  
    // Add a combo box between the view icons and the sort icons.

    CommandBar_InsertComboBox (hwndCB, hInst, 
                     GetSystemMetrics(SM_CXSCREEN ) / 4,
                     CBS_DROPDOWNLIST | WS_VSCROLL,
                     IDC_COMBO_BUTTON, 1);
    // Fill in combo box.
    for (i = 0; i < 5; i++)
    {
      wsprintf (tszBuff, TEXT ("Combo List %d"), i);
      SendDlgItemMessage (hwndCB, IDC_COMBO_BUTTON, 
                          CB_INSERTSTRING, -1, (LPARAM)tszBuff);
    }
    //select list item 0
    SendDlgItemMessage (hwndCB, 
                        IDC_COMBO_BUTTON, 
                        CB_SETCURSEL, 
                        0, 0);
    
   // AddAdornments puts an exit button on the command 
   // band control. It replaces the "Exit" menu item
    CommandBar_AddAdornments (hwndCB, 0, 0);
    CommandBar_Show(hwndCB, TRUE);
    return hwndCB;
}

Let's look at the combo box code in a bit more detail.

    CommandBar_InsertComboBox (hwndCB, hInst, 
                     GetSystemMetrics(SM_CXSCREEN ) / 4,
                     CBS_DROPDOWNLIST | WS_VSCROLL,
                     IDC_COMBO_BUTTON, 1);

The CommandBar_InsertComboBox() parameters, in the order shown, are the command bar handle, the instance handle, the width of the box in device units( pixels), the style flags, the combo box's control ID, and the 0 based index of the button to its right.

Notice that the width of the combo is calculated using a call to GetSystemMetrics(). There is no "standard" screen size across Windows CE devices, so this the safe and portable way to specify size for visual elements like controls and graphics.

The next parameter is the style flags. Here we begin to see some of the "shrinkage" of the Win 32 API. Windows CE supports only two combo styles: CBS_DROPDOWN or CBS_DROPDOWNLIST. The familiar CBS_SIMPLE style, where the list box is always visible and the current selection is displayed in the edit control is not supported under Win CE. This style was eliminated because it uses too much screen real estate.

The CBS_DROPDOWN and CBS_DROPDOWNLIST styles show something that looks like a single line text edit control, with a dropdown arrow icon displayed next to it. The list box is displayed when the user taps the dropdown arrow control. The CBS_DROPDOWNLIST style has a static text field that always displays the current selection and only allows users to select from a predefined list of items. CBS_DROPDOWN has an edit control and allows users to select from the list or provide their own input.

You can combine the combo style with other window style flags as we have done in the call shown above, but again, under Windows CE there is a limited subset of these style flags. A few particularly useful flag appear in the list below.

Table 1- My Favorite Combo Control Style Flags

Style Description
CBS_AUTOHSCROLL Horizontally scrolls text as a user types. Without this flag, a user can only enter as much text as the control width accommodates.
CBS_DROPDOWN Displays only an edit control unless the user taps the dropdown arrow next to the edit control. Users can provide their own input or choose from the list.
CBS_DROPDOWNLIST Displays only the current list selection unless the user taps the dropdown arrow next to the edit control.
CBS_LOWERCASE Converts all input to lower case lowercase.
CBS_UPPERCASE Converts all input to upper case.

Finally, we insert strings into the combo like this:

    TCHAR tszBuff[24];
              .
              .
              .
    for (i = 0; i < 5; i++)
    {
      wsprintf (tszBuff, TEXT ("Combo List %d"), i);
      SendDlgItemMessage (hwndCB, IDC_COMBO_BUTTON, 
                          CB_INSERTSTRING, -1, (LPARAM)tszBuff);
    }

Notice the function wsprintf(), which looks a lot like the familiar sprintf(),but with a couple of important differences. Note that the first parameter, tszBuff, is of type TCHAR.

TCHAR resolves to a WCHAR type if Unicode is defined in the Project build settings; otherwise it resolves to CHAR.

I make a special point of highlighting this, because as I mentioned earlier, Unicode is one of the most difficult and ubiquitous porting issues you will encounter. Using the TCHAR type is about the best insurance you'll find against memory overwrite errors when copying strings. The second parameter is also a key tool for working with Unicode strings. TEXT("Combo List %d") converts what is between the double quotes to a Unicode string. Like with ASCII character literals, you use the backslash to precede escape characters like tabs and newlines, and a double backslash to create a string that includes a backslash.

To create Unicode literal strings suitable for list box text, use the TEXT() macro, with the string literal enclosed in double quotes as a parameter.

   TEXT("This is how to make a Unicode string literal")

The Dropdown Button

The Dropdown button is definitely more work than anything we've seen so far. Is has one great redeeming advantage, though. Visually, the dropdown button has the impact and space requirements of a button with a drop arrow icon next to it. When the user taps it, it displays a dropdown menu. For this reason it's an important design option if you have floating menus or sub menus in the original Win 32 resource file.

If you can keep popup submenus and floating menus "logically intact", then you won't have nearly as much work to do in porting the message handling behind the menu items.

There are three things we have to do to support the drop button. First, we add the button to the command bar, which is the same as for other types, except that we specify the TBSTYLE_DROPDOWN in the TBBUTTON structure.

// Command band button initialization structure

TBBUTTON tbDropButton[] = {
  {0,             // Bitmap Index
   IDM_ONE,          //Command
   TBSTATE_ENABLED,  // State
   TBSTYLE_DROPDOWN, // Style
   0,                // UserData
   0},               // String
};

HWND    AddDropDownButton( HWND hWnd )
{
    //we have to destroy the old one before expanding
    CommandBar_Destroy( hwndCB ); 
    hwndCB = CommandBar_Create(hInst, hWnd, 1);     
    // Insert the menu band
    CommandBar_InsertMenubar(hwndCB, hInst, IDM_MENU, 0);
    //add bitmaps for buttons
    CommandBar_AddBitmap(hwndCB, hInst, IDB_ONE_TWO, 2, 0, 0);
    //Add the buttons
    CommandBar_AddButtons(hwndCB, 1, tbDropButton);
    // Add exit button to command band control.
    CommandBar_AddAdornments (hwndCB, 0, 0);
    CommandBar_Show(hwndCB, TRUE);
    return hwndCB;

  }

Unlike other button types, however, the dropdown doesn't do much to manage it's own appearance. When the user taps a dropdown button, the button will change its appearance to look "pressed", and send the application a WM_NOTIFY message. Since many control elements can generate these messages, we must check to make sure it's from our drop down button.

Here's how to do this. The lParam of the WM_NOTIFY message is a pointer to a generic notification message header. To determine if the notification is coming from the button, we cast the lParam to a pointer of LPNMHDR type, and then use the pointer to get the value of the lpnmHeader->code. The code tells us what kind notification structure is being provided. In this case, we check for a toolbar notification from a dropdown button. If that's what we got, then we call our handler.

   case WM_NOTIFY:
   // Notify messages have a generic header.
   //check it to see what sent the notify message
     lpnmHeader = (LPNMHDR)lParam;
     if (lpnmHeader->code == TBN_DROPDOWN) 
     {
         //if the notify message came from our button, 
         //recast lParam to a toolbar notify pointer
         //and call the handler for the drop button
         return HandleDropButton( hWnd, (LPNMHDR)lParam,
                                 (LPNMTOOLBAR)lParam);
  }

Here's where the rubber meets the road, so to speak. We have to display the dropdown menu directly underneath the dropdown button, making sure we don't cover up the button itself.

long HandleDropButton( HWND hWnd, 
                       LPNMHDR lpnmHeader, 
                       LPNMTOOLBAR lpnmDropButton )
{
    RECT rect;
    TPMPARAMS tpm;
    HMENU hMenu;

    if (lpnmDropButton->iItem == IDM_ONE)
    {

      // Get the rectangle of the drop-down button.
      SendMessage (lpnmHeader->hwndFrom, TB_GETRECT,
         lpnmDropButton->iItem, (LPARAM)&rect);

      // Convert rect into screen coordinates.  The rect is
      // considered here to be an array of 2 POINT structures.
      MapWindowPoints (lpnmHeader->hwndFrom, HWND_DESKTOP,
           (LPPOINT)&rect, 2);

      // Prevent the menu from covering the button.
      tpm.cbSize = sizeof (tpm);
      CopyRect (&tpm.rcExclude, &rect);

      hMenu = LoadMenu (hInst, TEXT("DROPMENU"));
      hMenu = GetSubMenu ( hMenu, 0);
      TrackPopupMenuEx (hMenu, TPM_LEFTALIGN | TPM_VERTICAL,
            rect.left, rect.bottom, hWnd, &tpm);
    }
  return 0;
}

We get the button's screen rectangle with a call to SendMessage(). The parameters, in the order shown, are the handle to the button window, which we get from the notify message header, the Windows message constant TB_GETRECT, the control ID of the toolbar button, and the address of a RECT structure which will contain the rectangle coordinates when the function returns.

   // Get the rectangle of the drop-down button.
   SendMessage (lpnmHeader->hwndFrom, TB_GETRECT,
      lpnmDropButton->iItem, (LPARAM)&rect);

The rectangle of the button is reported in parent window coordinates, but we need screen coordinates to draw the menu properly. MapWindowPoints() translates the button rectangle to the screen coordinate systems. Notice that we treat the RECT structure as an array of two POINT structures in this call.

   // Convert rect into screen coordinates.  The rect is
   // considered here to be an array of 2 POINT structures.
   MapWindowPoints (lpnmHeader->hwndFrom, HWND_DESKTOP,
        (LPPOINT)&rect, 2);

We get a bit of help in putting up the menu. We initialize a popup menu tracking structure with the screen coordinates of the button's rectangle so that it can be excluded when the menu is painted, load the menu resource and get a handle to it. TrackPopupMenuEx() does the rest.

   // Prevent the menu from covering the button.
   tpm.cbSize = sizeof (tpm);
   CopyRect (&tpm.rcExclude, &rect);

   hMenu = LoadMenu (hInst, TEXT("DROPMENU"));
   hMenu = GetSubMenu ( hMenu, 0);
   TrackPopupMenuEx (hMenu, TPM_LEFTALIGN | TPM_VERTICAL,
         rect.left, rect.bottom, hWnd, &tpm);
 }

I'd like you to notice two things particularly. First, LoadMenu() uses the text macro to create the literal string with the menu name. File names, resources names, path names and control text are always Unicode under Win CE. Second, notice that the alignment, positioning and upper left hand corner of the popup are set in the second, third and fourth parameters of TrackPopupMenuEx().

This brings us to the point where we can articulate the rules for menu porting decisions:

Win 32 Menu Porting Checklist:

  • Examine menu resources and eliminate menu items that are superfluous under Windows CE. If there are four or fewer dropdowns, you may be able to port with no changes.
  • If there are popup submenus or floating menus, consider converting to command bar style menus.
  • If you choose command bar menus, sub divide menu functionality across several command bar menus, creating them as needed.
  • Eliminate menu items that duplicate the command bar adornments.

Looking Ahead

Now that we've examined the issues around porting menus, we are ready to dive into the more application specific and functional area of dialog boxes. Dialog boxes present several kinds of porting challenges. First, of course, is the issue of size. More subtly, though, we begin to see the influence of CE's minimalist nature. In contrast to menus, which we move to CE simply by doing more or less the same thing but in a smaller space, porting complex dialogs requires fundamental functional shifts. In the next several installments, we'll see how to quickly move dialog based applications to CE. Then we'll turn our attention to modifying dialog based applications, incrementally moving ported code toward the spirit of the CE environment.

What Is Unicode and Why Do We Need It?

Conceptually, all computer character sets map arbitrary integer values to specific characters. Some familiar standard mappings of the past include the EBCIDIC and ASCII character sets. These two sets are incompatible with one another and neither fully supports special characters or the diacritical marks necessary to render languages other than English. In order to make textual data portable and accessible, we need a universal character encoding system: one that accommodates all languages and is consistent across platforms Enter Unicode.

Unicode is a sort of "lingua franca" for text display. By definition, it is a character representation standard that uniquely encodes all characters of all languages on all platforms. From a programmer's point of view, the key thing to remember about Unicode is that characters are encoded in multiple bytes. We have to treat Unicode data in the way we treat numeric data types:

  • Don't count on a particular size in bytes for a datum
  • Don't assume anything about the ordering of bytes
  • To get the address of Unicode data, use the address operator

The assignment of Unicode character values is administered and maintained by the Unicode Standards Organization. You can read about the organization and its membership at www.unicode.org. Most industry leaders are members of the organization, giving the Unicode standard a broad base of support among both hardware and software vendors.

About the Author

Nancy Nicolaisen is a software engineer who has designed and implemented highly modular Windows CE products that include features such as full remote diagnostics, CE-side data compression, dynamically constructed user interface, automatic screen size detection, entry time data validation.

In addition to writing for Developer.com, she has written several books including Making Win 32 Applications Mobile. She has also written numerous articles on programming technology for national publications including Dr. Dobbs, BYTE Magazine, Microsoft Systems Journal, PC Magazine; Computer Shopper, Windows Sources and Databased Advisor.

# # #

Share:
Home
Mobile Site | Full Site
Copyright 2017 © QuinStreet Inc. All Rights Reserved