MobileWinCE Forms - Getting It All On One

WinCE Forms – Getting It All On One

Developer.com content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

I Want It All Now

In this example you’ll see a forms solution that allows you to put a very large number of fields on a single screen, without using paging. This example shows how to make a form using a CommandBands control with moveable, resizeable bands.

Like the previous example , this one builds on the material presented in earlier articles, so we’ll focus on the differences between this example and the other CommandBands based forms.

One assumption that we’ve made up to point is that we could safely iterate through the CommandBands control using band indices, grab the contained command bar by its handle, and proceed to party on the embedded control. This was safe in the earlier examples because we specified the CommandBands style flag that forces individual bands to retain their order. Here’s the place where we made this decision in the previous two examples:

//NOT WHAT HAPPENS IN THE MOVEAND SIZE EXAMPLE
// Create a command band.
hwndCB = CommandBands_Create(hInst, 
                             hWnd, 
                             IDC_BAND_BASE_ID,
                             RBS_BANDBORDERS |
                             RBS_AUTOSIZE | 
                             RBS_FIXEDORDER,
                              NULL);

This time we have a subtle but very important difference. Here’s how we create the MoveAndSize CommandBands control

// Create a command band with moveable bands.
hwndCB = CommandBands_Create(hInst, 
                             hWnd,
                             IDC_BAND_BASE_ID,
                             RBS_BANDBORDERS | 
                             RBS_AUTOSIZE ,
                             NULL);

Recall that one of the first points I made about CommandBands controls is that the control itself is mostly a screen real estate manager and a container of CommandBars. If we omit the RBS_FIXEDORDER style, we are allowing users to move CommandBars between bands. When they do that, it changes the containment relationships between individual bands and bars, but it doesn’t change the control’s view of band indices. To the CommandBands control, band 0 is the topmost, leftmost band, regardless of which bar happens to occupy that spot at any given point in time.

This means that we can’t simply iterate through the band indices when we want to access the controls contained in the command bars if its possible that a band has been moved by the user. To get access to specific moveable controls, we have to translate their band index to a control ID. Getting the control index by using the RB_IDTOINDEX parameter to SendMessage() will let you iterate the controls in the order you created them, no matter where the user has put them. Here’s how to do this:

//get the handle to the command bands control
hwndCB = GetDlgItem( (HWND)lParam, IDC_BAND_BASE_ID );

for( i = 1; i < nBands; i++ )
{
   //since the combos may have been moved by the user,
   //we can count on their ordering only if we loop 
   //them using their combo index
   iIndex = SendMessage (hwndCB, 
                         RB_IDTOINDEX, 
                         IDC_BAND_BASE_ID + i,
                         0);
   hwndBand = CommandBands_GetCommandBar (hwndCB, iIndex);
   hwndCombo = GetDlgItem(hwndBand,
                          IDC_COMBO_BASE_ID + i-1 );
   //get the character count of the input
   iInputCharCnt = SendMessage( hwndCombo,
                                WM_GETTEXTLENGTH, 
                                0, 0); 
      .
      .
      .
   //do some kind of control access
}

The excerpt above comes from ShowRecDlgProc(), a dialog box that shows user input to controls. The caption and user input for each field is displayed in the dialog in the order that the fields appear in the initial form layout, even if the bands have been rearranged. Here’s one more thing to note before moving on. Notice the return from the SendMessage() call that uses WM_GETTEXTLENGTH. This return is a count, not a size, and it excludes the terminal NULL. If you were to allocate memory for the control text based of the reported length, you’d do it like this:

ptszBuff = (TCHAR*)LocalAlloc( LPTR, 
                 ( iInputCharCnt + 1 ) * sizeof(TCHAR) );


Porting Tip:
Look carefully at all dialog code that copies text from controls. If the text is bound for stack based buffers, make the buffers type TCHAR. If you allocate space for control text, use SendMessage() with WM_GETTEXTLENGTH, and calculate the buffer space like this:

    ( iInputCharCnt + 1 ) * sizeof(TCHAR)

Here’s another issue that arises from the fact that the bands are moveable: What happens if the user moves the bands, the control gets destroyed, and then the bands need to be shown again in the order the user last saw them? In practice, this could happen if the application allows data entry to be an interruptible operation ( a very likely prospect in mobile computing settings). We accommodate this need by collecting restoration information from the control, and saving it in a special purpose structure called COMMANDBANDSRESTOREINFO. When we next build a control, some of the information in the REBARBANDINFO structure is initialized with saved data. In the MoveAndSize example program, we use two arrays of structures to save CommandBand restore information.

//when we hide the bands, we store
//their current order in this array
int     iCtrlInitOrderArray[ dim(structCmdBarInit)]; 

//this struct saves band size and 
//position for the bands
COMMANDBANDSRESTOREINFO cbrRestoreArray[ dim(structCmdBarInit) + 1];

As mentioned earlier, the controls contained in the individual bands have sort of a dual nature : they are constituent parts of the CommandBands, but they are also inherently controls, and therefore windows. While it’s not a good idea to treat the controls as independent windows in any context where they have a containment relationship, we can take advantage of the “window-ness” of them by using their individual Window Long values to store state information or other important per control details. In MoveAndSize, we demonstrate the use of the window long value by storing the original index of the band there. You can also use the window long store pointers to more elaborate aggregations of data. This is an excerpt from CreateDataBands. It is the top of the band initialization loop. (It occurs just after the call to CommandBands_AddBands().)

//Move past the menu bar & close box and set the control behaviors
for (i = 1; i < nBands; i++) 
{
   hwndBand = CommandBands_GetCommandBar (hwndCB, i);
   SetWindowLong(hwndBand, GWL_USERDATA, i - 1 );
   CommandBar_InsertComboBox (hwndBand, 
                              hInst, 
                              prbi[i].cx - 6, 
                              structCmdBarInit[i - 1].lStyles,
                              IDC_COMBO_BASE_ID + (i - 1), 
                              0);

To set a value that stays with its window, use SetWindowLong(). The parameter, in the order shown, are the handle to the window whose value is being set, the constant GWL_USERDATA, which means that you are setting an application specific datum, and the value you wish to set. The last parameter is of type long, so don’t try to copy anything large than that. ( f you need more space than a long provides, allocate it and store its address. )

We hide and restore the CommandBandsform in response to menu items in the MoveAndSize example. Here is the excerpt from the WndProc() message switch where handle these menu commands:

case WM_COMMAND:
     wmId    = LOWORD(wParam); 
     wmEvent = HIWORD(wParam); 
     // handle the menu selections:
     switch (wmId)
     {
        case IDM_HIDE_RECORD:
           //destroy the record's command band control
           //and replace it w/ a single band containing the menu
           HideRecord( hWnd );
           
        case IDM_RESTORE_RECORD:
           //use the restore information to restore the bands 
           //as we last saw them
           RestoreDataBands(hWnd);
           break;

In HideRecord(), we save band restoration information, destroy the CommandBands control that implements the form, and replace it with a minimal CommandBands control which contains only the application’s menu. Here’s the portion of HideRecord() that saves the restore information:

//get the handle to the command bands control
hwndCB = GetDlgItem (hWnd, IDC_BAND_BASE_ID);

for (i = 1; i < nBands; i++)
{
  // Get band index from ID value.
  iIndex = SendMessage (hwndCB, 
                         RB_IDTOINDEX, 
                         IDC_BAND_BASE_ID + i , 
                         0);

  //ALWAYS initialize a struct's cbSize member if it has one .
  cbrRestoreArray[i].cbSize = sizeof (COMMANDBANDSRESTOREINFO);

  //get restore info for the band
  CommandBands_GetRestoreInformation (hwndCB, 
                                      iIndex, 
                                      &cbrRestoreArray[i]);
  
  //now get restore info for the ctrl
  //get the handle to this band
  hwndBand = CommandBands_GetCommandBar (hwndCB, iIndex);
  
  //get it's window long value -- this indexes 
  //the control init struct array
  iCtrlInitOrderArray[i - 1] =
  (int)GetWindowLong(hwndBand, GWL_USERDATA );
}

When we restore the bands to their previous configuration, we simply use the saved information to fill in the array of REBARBANDINFO structures. Here are the places where the restored initialization info is used in CreateDataBands():

First, all the bands get their style and width from the saved data:

//Initialize data entry band attributes
for (i = 1; i < nBands; i++) 
{
   //  Common Combobox ctrl band attributes
   prbi[i].fMask |= RBBIM_ID | RBBIM_SIZE| RBBIM_CHILDSIZE;
   prbi[i].cxMinChild = (GetSystemMetrics(SM_CXSCREEN ) / 6 ) ;
   prbi[i].cxIdeal = (GetSystemMetrics(SM_CXSCREEN ) / 2 ) ;
   prbi[i].cx = cbrRestoreArray[i].cxRestored;
   prbi[i].fStyle = cbrRestoreArray[i].fStyle;
}

Next, any bands that were maximized are restored to that state;

//now we restore the bands that were maximized
for( i = 0; i < dim( cbrRestoreArray ); i++ )
{
   if( cbrRestoreArray[i].fMaximized)
   {
      hwndBand = CommandBands_GetCommandBar (hwndCB,i);
      SendMessage(hwndBand, RB_MAXIMIZEBAND, i, prbi[i].cxIdeal);
   }// end if max'ed
}//end for cbrRestoreArray

Finally, when the controls are reinitialized, the elements in the array where band order was saved, iCtrlInitOrderArray is used to index structCmdBarInit ( the control initialization array ).

//loop the user bands 
for (i = 1; i < nBands; i++) 
{
  hwndBand = CommandBands_GetCommandBar (hwndCB, i);
  //get the index of the init struct for this band
  iCtrlInitIndex = iCtrlInitOrderArray[i - 1];
  //set its ctrl array index in the window long value
  SetWindowLong( hwndBand, GWL_USERDATA, iCtrlInitIndex );
  hwndCombo = 
     CommandBar_InsertComboBox (hwndBand, 
                  hInst, 
                  prbi[i].cx - 6, 
                  structCmdBarInit[iCtrlInitIndex].lStyles,
                  IDC_COMBO_BASE_ID + (i - 1), 
                  0);
  //get the caption string
  LoadString( hInst, 
              structCmdBarInit[iCtrlInitIndex].uiCaption, 
              (LPTSTR)&tszCaptionBuff,
              sizeof(tszCaptionBuff));

  //insert the label string
  SendMessage ( hwndCombo, 
                CB_INSERTSTRING, 
                0, 
                (long)&tszCaptionBuff);
  //highlight the caption
  SendMessage (hwndCombo, CB_SETCURSEL, 0, 0);
  //does this combo have a string list?
  if(structCmdBarInit[iCtrlInitIndex].iNumStrings)
  {
    //if so, insert them in the combo
    for( j = 0; j < structCmdBarInit[iCtrlInitIndex].iNumStrings;
         j ++ )
    {
      LoadString( hInst, 
              structCmdBarInit[iCtrlInitIndex].iStringIndexBase + j,
              (LPTSTR)&tszCaptionBuff,
              sizeof(tszCaptionBuff));
      //add strings to the end of the list
      SendMessage ( hwndCombo, 
                    CB_INSERTSTRING, -1, 
                    (long)&tszCaptionBuff);
    } //end for(iNumStrings)
  }//end if(iNumStrings)
}

Forms. You’ve gotta love ’em.

Looking Ahead

To date, we’ve explored most of what you need to know in order to make a Win 32 application look like a Win CE application. In upcoming examples, we’ll explore two more important aspects of CE’ visual interface: graphics and user input.

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.

# # #

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Latest Posts

Related Stories