dcsimg
December 10, 2016
Hot Topics:

A Better Approach to WinCE/Pocket PC Forms

  • December 18, 2002
  • By Nancy Nicolaisen
  • Send Email »
  • More Articles »

Steps To A Form Using CommandBands

Let's assume that the sole purpose of our application is to collect data using the UFO CommandBands based form. We'll call CreateDataBands(), the function that creates the form when we process the WM_CREATE message for the application. This has the same functional effect as if we created a modeless dialog. In other words, the form will persist until we destroy it or until the user closes the application. Here's the code for this:

case WM_CREATE:
   // build the bands that 
   // replace the Win32 
   // dialog box form
   CreateDataBands( hWnd, 
                    message, 
                    wParam, 
                    lParam);
   break;

The job of building the bands is handled in the function CreateDataBands(). Notice the parameters to this function are simply copies of the parameters to WindProc that came with the WM_CREATE message. Now let's look more closely at the CreateDataBands() function. First, notice this declaration:

LPREBARBANDINFO prbi;

This datum is a long pointer to a REBARBANDINFO structure. This structure contains the information used to size, position, and specify the behavior and appearance of each band. This is a subtlety that I can't emphasize enough. The band is the real estate where the CommandBar and it's control live. If you want to access the bar, and by extension, it's a contained control, you have to do so through it's band.

Porting Tip
As you are most probably aware, all of these controls are implemented as windows, and they have ownership relationships among themselves. I mention this, because if you are porting a real application, it's very tempting to try "clever" solutions that will preserve your existing code, and a lot of Win32 forms code does interact directly with the windows that underlie dialog elements. There are very legitimate reasons for doing this ( data validation and input masking are two good examples ). I have found that trying to treat any of the CommandBands constituents ( bands, bars, or controls) as individual windows ( as opposed to bands that contain bars that contain controls) produces highly unpredictable behavior.

Here is the declaration of the REBARBANDINFO structure, along with the meanings of it's members:

typedef struct tagREBARBANDINFO
{
  UINT          cbSize;       //size of this structure
  UINT          fMask;        //flags that tell ether what 
                              //    info is being passed
                              // or what info is being requested
  UINT          fStyle;       //flags for band styles
  COLORREF      clrFore;      //band foreground color 
  COLORREF      clrBack;      //band background color
  LPSTR         lpText;       //ptr to band label text
  UINT          cch;          //size of text in the band label
                              //    in bytes
  int           iImage;       //0 based index of image list image
                              // to be displayed in this band
  HWND          hwndChild;    //handle to child window 
                              //    contained in band
  UINT          cxMinChild;   //min child window width in pixels
  UINT          cyMinChild;   //min child window height in pixels
  UINT          cx;           //band width, pixels
  HBITMAP       hbmBack;      // handle to a bitmap for 
                              //     band background
                              // if this member is filled, 
                              // clrFore and clgBack are ignored
  UINT          wID;          // ID the CommandBand control uses to
                              //notify this band
  UINT          cyChild;      //initial height of child window
  UINT          cyMaxChild;   //max child height - not used unless 
                              // style RBBS_VARIABLEHEIGHT is set
  UINT          cyIntegral;   //step value for band height increase
  UINT          cxIdeal;      //ideal band width in pixels
  LPARAM        lParam;       //application specific data
}   REBARBANDINFO

Whew. It's a truckload of structure. In most cases, you'll only need to fill in a few of these members to create a CommandBands control. One structure is required for each band in the control. We use this structure to initialize an existing CommandBands control.

Here's how to create the control:

// Create a command band; save the handle in a global
// variable.  We need to preserve it to access the CommandBands 
// control
hwndCB = CommandBands_Create(hInst, hWnd, 
                             IDC_BAND_BASE_ID,
                             RBS_BANDBORDERS | 
                             RBS_AUTOSIZE | 
                             RBS_FIXEDORDER, NULL);

Notice that we don't specify any details about the individual bands, not even their number. The parameters to CommandBands_Create(), in the order shown, are the instance handle of this app, the main window's handle, the ID of the first band in the control ( which is the zeroth band ), and the style flags that define the appearance and behavior of the CommandBands control. These styles apply to all the individual bands. RBS_BANDBORDERS means the bands will be separated by thin lines, which give the control a gridded appearance. RBS_AUTOSIZE means the bands will be sized to fit within the control if the position of the control changes. RBS_FIXEDORDER means the bands will maintain their initial ordering, even if the user moves a band using its gripper. The last parameter is the optional handle to an image list. We aren't using any images in this control, so it's NULL here. If successful, CommandBands_Create() returns the handle to the control.

Next, we allocate an array of REBARBANDINFO structures.

// Allocate space for the REBARBANDINFO array
// Need one entry per ctl band ( nBands ) plus the menu
prbi = (LPREBARBANDINFO)LocalAlloc(LPTR,
                   (sizeof (REBARBANDINFO) * nBands) );

//always test returns if you attempt to allocate memory
if (!prbi) {
        MessageBox( hWnd, 
                    TEXT("LocalAlloc Failed"), 
                    TEXT("Oops!"), 
                    MB_OK); 
        return 0;
}

These structures are quite large, and it's a definite possibility that your allocation request could fail. Always check the return from an allocation request. Also, notice that we called LocalAlloc() with the LPTR flag as its first parameter. This flag allocates fixed memory that is zero initialized. It's important to initialize the structures before you use them because many of the fields and their interpretations are interdependent. For example, if there is a value in the hbmBack member, the members for foreground and background colors are ignored. The fmask flags define your intentions about what structure members are valid and how they should be used, but if you zero initialize the structures, you won't be as vulnerable to subtleties of the mask flags.

There are several REBARBANDINFO structure fields that are the same for every band. We initialize these first.

// Initialize common REBARBANDINFO structure fields.
for (i = 0; i < nBands; i++) {  
   prbi[i].cbSize = sizeof (REBARBANDINFO);
   prbi[i].fMask = RBBIM_ID | 
                   RBBIM_SIZE |
                   RBBIM_BACKGROUND |
                   RBBIM_STYLE |
                   RBBIM_IDEALSIZE ; 
   prbi[i].wID    = IDC_BAND_BASE_ID+i;
   prbi[i].hbmBack = hBmp;
}

The structure members initialized here, in the order they appear, are the size field , which gives the size of the REBARBANDINFO structure; the mask flags; the ID of this band, calculated using the band base ID and the loop index; and the handle to the bitmap that serves as the background for this band.

It may seem superfluous to pass the size of the structure, but notice that when we call the function that adds the bands using this structure, we pass its address. Since the very first member of the structure contains the size, the called function can accurately determine the size of the structure by dereferencing only the DWORD at the address in the pointer. Supplying the structure size makes for more reliable allocation behavior in the called function.

Porting Tip:
Always initialize the size member of structure if it has one.

The fmask member of the structure tells which other members of the REBARBANDINFO structure are valid. Some of the masks define more than one member to be valid or specifically exclude others from being valid. The ones specified in the call above validate the following members of the structure:

  • wID
  • cx
  • hbmBack
  • fStyle
  • cxIdeal

Recall that in the Menubar example in Chapter 1, the CommandBar menu replaced the resource based-based menu. When you use a CommandBands control, the CommandBar in the 0th band serves as the menu. For this reason the 0th REBARBANDINFO is initialized differently than the bands that hold the controls that make up the form.

// Initialize REBARBANDINFO structure for Menu band
prbi[0].cx = GetSystemMetrics(SM_CXSCREEN ) / 4;
prbi[0].fStyle = prbi[1].fStyle = RBBS_NOGRIPPER |
                                  RBBS_BREAK;

We limit the width of the menu bar to 1/4 of the screen dimension, but it could be larger, because no other bands are inserted between it and the Adornments. We set its style flags and the style flags of the immediately following band to RBBS_NOGRIPPER | RBBS_BREAK. This is an important point. Here's why:

The RBBS_NOGRIPPER style means that the user can't move the band to a new location by dragging it with the gripper control. We want the menu to stay at the top of the screen, so we explicitly eliminate the gripper, which is a default with out this style.

We also specify the RBBS_BREAK, which means the band begins on a new line. (Of course, the 0th band always begins on a new line.) To make sure the 0th band occupies the full screen width, the band following it must have the RBBS_BREAK style as well.

Next, we prepare to initialize the structures for the bands that contain the controls. This is where we get an initial glimpse of the effect of variability in Windows CE device sizes. Obviously, the bigger devices can legibly display more bands in a single row of screen real estate that the smaller devices. We choose the number of bands per row based on runtime information about the platform type of the host.

//Set bands per row based on platform type
memset( &tszPlatType, 0x0, sizeof(tszPlatType));
rc = SystemParametersInfo( SPI_GETPLATFORMTYPE, 
                           sizeof(tszPlatType),
                           &tszPlatType, 0);
if( lstrcmp( tszPlatType, TEXT("Jupiter") ) == 0 )
   { nBandsPerRow = 4;}
else if( lstrcmp( tszPlatType, TEXT("HPC") ) == 0 )
   { nBandsPerRow = 3;}
else if( lstrcmp( tszPlatType, TEXT("Palm PC") ) == 0 )
   { nBandsPerRow = 1;}

Notice that to distinguish between the different form factors we use the SystemParametersInfo() function. The flag SPI_GETPLATFORMTYPE request a string that describes the class of the Windows CE device on which we are running, rather than an OS version. Here's why this is the most reliable way to distinguish between hosts at runtime.

The Windows CE Platform Developers' Kit is designed in a highly componentized fashion, essentially allowing device vendors to "roll their own" version of the OS to implement on their devices. This offers them a great competitive advantage, to wit, the opportunity to innovate. Not surprisingly, they do. For this reason, Windows CE version numbers don't provide the same kind of definite information as Windows version numbers on the desktop, because hardware developers have great latitude in choosing what parts of CE they implement and how they do it. Put another way, you can't safely or effectively use OS version numbers for decision-making at runtime.

The real business of creating the form is accomplished when we initialize the structures for the bands that contain the form's fields.

//Initialize data entry band attributes
for (i = 1; i < nBands; i++) 
{
   //  Common Combobox ctrl band attributes
   prbi[i].fMask |= RBBIM_IDEALSIZE;
   prbi[i].cx = (GetSystemMetrics(SM_CXSCREEN ) - 20 ) / 
          nBandsPerRow ;
   prbi[i].cxIdeal = (GetSystemMetrics(SM_CXSCREEN ) - 20 ) /
          nBandsPerRow ;

   //Set style for line break at the end of a row
   if(((i - 1 ) % nBandsPerRow ) == 0 ) 
   {
      prbi[i].fStyle |= RBBS_BREAK | RBBS_NOGRIPPER;
   }
   else
   {
      prbi[i].fStyle |= RBBS_NOGRIPPER;
   }
}

Notice that we "OR" the RBBIM_IDEALSIZE flag to the fmask member. Next we set the initial size, cx, and the ideal band size, cxIdeal, to a fraction of the screen width that is determined by the platform type. Finally, we use a conditional to assign styles that will assure that we have the correct number of bands in each row. The structure array is fully initialized, and we are ready to add the bands to the CommandBands control.

// Add the bands-- we've reserved real estate  // for our dlg controls.
CommandBands_AddBands (hwndCB, hInst, nBands, prbi);

The parameters to the CommandBands_AddBands() function, in the order shown, are the handle to the command bands control to which the bands are being added, the instance handle, the number of bands being added (this is the count of the bands, not the highest zero based index ), and the address of the array of REBARBANDINFO structures.





Page 2 of 3



Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Enterprise Development Update

Don't miss an article. Subscribe to our newsletter below.

Sitemap | Contact Us

Thanks for your registration, follow us on our social networks to keep up-to-date
Rocket Fuel