The user interface
The most obvious consideration when writing an HPC application is that of the screen display. The current HPC machines have a relatively small screen resolution, though these do vary between models. Apart from the physical dimensions, color is also an issue. Version 2 of Windows CE introduced support for 16 colors. This has improved the color contrast on monochrome displays, though devices are now available with color displays.
Windows CE supports only two window styles-fixed border or no border-and there is no Minimize or Maximize button. You can set the form's BorderStyle design time property to another style but any styles other than those allowed will be overridden at run time. When creating a new form, Visual Basic defaults to a size near the maximum resolution of the HPC device you are using, but you can change this default size in the Project Properties dialog box. Any size that you set here will be retained as the default for future forms you create.
A new window style has been implemented for message box windows. If you specify a button configuration of vbOKOnly, the message box will be displayed with the OK button in the title bar next to the Close button. Other button configurations will display message boxes in the usual format. The height of the message box has been scaled down to obscure the least amount of space on the screen. While we're on the subject of message boxes, you should be aware of a glitch in the current version of the language. The message box is not modal, but floats above its parent window. The form below the parent window can still respond to mouse events. To avoid this problem you will need to disable your form while the message box is displayed.
When designing a Windows CE form, you should evaluate the need for a border and title bar. Many of the Microsoft applications, such as Pocket Word and Pocket Excel, do not have title bars or borders. This really does increase the amount of usable screen real estate.
- Check Box
- Combo Box
- Command Button
- Horizontal and Vertical Scroll Bars
- List Box
- Option Button
- Text Box
Although the remaining controls are not removed from the toolbox, a warning message will be displayed and the control will be removed if you attempt to place one of these on your form. Obviously, graphical capabilities must be retained, so two new graphical controls are available from the Components dialog box: PictureBox and ImageCtl.
These two graphical controls are replacements for the standard PictureBox and Image controls, though you should note that their class names have changed. These two controls retain the ability to display images and pictures, although there are some differences from the controls they replace. Apart from being a lightweight control, the PictureBox has undergone some changes to its methods. The methods such as Line, Circle, Point, and PSet have been removed and are now replaced by this new set of methods:
Pontoon, being a card game, relies heavily on graphics. The graphical methods supported by the Form object in other versions of Visual Basic are not available in the Windows CE toolkit. Therefore, the PictureBox control is used for displaying the graphics.
Windows CE contains a unique set of constraints or bounds that we must work within. One such constraint is that control arrays are not permitted. You can create control arrays but you will get an error at run time if you do so. In the case of the Pontoon game, the work-around to this problem is to use a PictureBox control and draw the card graphic in the picture box. In other versions of Visual Basic, it might have been easier to simply create a control array of Image controls on the fly, and then load the required images. The Pontoon game uses the Windows CE PictureBox control as a container into which the cards can be drawn. The PictureBox control does not have the ability to be a container object, so a problem arises because you cannot place labels within the PictureBox. Because labels are lower in the z-order, you can't show labels within a picture box. To get around this problem I've used two picture boxes to display the rows of cards and I've used a Shape control to create an area around the picture boxes and labels to form a playing table.
Figure 5-10 shows the Pontoon screen at design time. The number of controls have been kept to a minimum. The screen is built up using only essential controls or elements needed to improve clarity.
Figure 5-10 The design-time layout of the Pontoon game
You will notice that although the design-time form looks like a standard Windows NT/9x form, the form will be displayed using the Windows CE window style when the program is run-that is, with no Minimize or Maximize buttons. We have an interface where all elements are large enough to be clearly visible, resulting in little clutter on the screen.
Another important aspect of the user interface design is that of keyboard ergonomics. If I were sitting on a train playing this game, I might find it uncomfortable trying to use the stylus if the train is swaying. It might also be uncomfortable to use the accelerator keys because two hands are required. One design feature I've implemented to aid keyboard use is the KeyPreview property, which is used to intercept keystrokes and convert them to accelerator key presses. In an ideal world we could simply code the following in the form's KeyPress event, as shown here:
Private Sub Form_KeyPress(KeyAscii) SendKeys "%" & Chr(KeyAscii) End Sub
Alas, this is not possible-the SendKeys statement is not supported. Instead you can achieve the coding using a method like the one I've implemented here:
Private Sub Form_KeyPress(KeyAscii) If StrComp(Chr(KeyAscii), "g", vbTextCompare) = 0 Then txtGambleAmount.SetFocus End If If StrComp(Chr(KeyAscii), "t", vbTextCompare) = 0 Then cmdTwist.Value = True End If If StrComp(Chr(KeyAscii), "s", vbTextCompare) = 0 Then cmdStick.Value = True End If End Sub
Simple routines like this take no time to write but can drastically improve the ergonomics of an interface. I expect many new ideas to be developed in the future that will aid usability.
In addition to the intrinsic controls and the PictureBox and ImageCtl controls that are supplied with the development kit, you can also download a set of controls from the Microsoft Web site at
http://www.microsoft.com/windowsce/developer. At this site you can obtain the Microsoft Windows CE ActiveX Control Pack 1.0, which contains many more useful controls. The download is more than 5 MB but is worth downloading, because in this pack you get the following controls:
The addition of these controls allows you to create the same interface styles as the controls of full-blown Visual Basic applications. The list of controls will grow and I would expect a lot of new controls to emerge from third-party vendors as well.
Size and memory considerations
I said earlier that one of the goals for the Pontoon game was to be small in size. The word "size" might imply the physical file size, but an important factor is also the memory footprint of the application. You can control the program's memory footprint by enforcing restrictions on functionality and by writing more efficient code. The former method will nearly always be a business issue and, therefore, possibly might be out of the programmer's control. However, using efficient coding techniques is a trade-off against readability and maintainability. However, I'll discuss some techniques that you can use to code more efficiently.
Program variables are the obvious source of memory consumption. The Windows CE Toolkit for Visual Basic 5 allows only Variant type variables. This is a little surprising, given that Variants take more memory than a "typed" variable. Although your variables will be Variant types, you can still coerce them into a subtype using the conversion functions like CLng, CCur, and so forth, although this coercion will be performed automatically when a value is assigned to the variable. The Pontoon game makes extensive use of bit flag variables. This is an efficient way to store multiple values, providing there is no overlap in range of the bit values. By using bit values, the overall memory requirement can be reduced, but you must be careful if creating constants to represent the bits because you might end up using the same or larger amounts of memory. The following is the declaration section from the form:
Private m_btaDeck(12, 3) Private m_btaPlayerCards ' Byte Array stores cards held by player. Private m_btaDealerCards ' Byte Array stores cards held by dealer. Private m_nPlayerScore ' Player score - total value of cards held. Private m_nDealerScore ' Dealer score - total value of cards held. Private m_nPlayerFunds ' Dealer score - total value of cards held. Private m_nGameStatusFlags ' Long Integer of flags indicating game ' status. ' Constants used with m_nGameStatusFlags. ' Private Const m_GSF_PLAYER_HAS_21 = &H1 Private Const m_GSF_DEALER_HAS_21 = &H2 Private Const m_GSF_PLAYER_HAS_BUST = &H4 Private Const m_GSF_DEALER_HAS_BUST = &H8 Private Const m_GSF_PLAYER_HAS_HIGHER_SCORE = &H10 Private Const m_GSF_DEALER_HAS_HIGHER_SCORE = &H20 Private Const m_GSF_PLAYER_HAS_STUCK = &H40 Private Const m_GSF_IS_DEALER_TURN = &H100
You should note two points here. First, even though you can declare only Variant variables, it is still good practice to use scope and type prefixes. Because each variable can potentially store any value, you need to be sure that everyone knows what type of value is expected. For example, the variable UserName obviously contains a string, but a variable named ItemId could easily represent a string or numeric value. Second, you'll see that I've used hexadecimal notation to assign the bit flag constants. Bit manipulation often requires mask and other values to extrapolate the individual values within the flag variable. Using hexadecimal notation makes it much easier for others to understand the operations that are being performed, because the conversion from hexadecimal to binary is much easier than from decimal to binary.
Let's look for a moment at the variable m_nGameStatusFlags that I use in the Pontoon game to keep track of the game's progress. The variable is a Long Integer (32 bits) and stores nine separate values, which together provide a complete picture of the game's current status. Figure 5-11 shows how these values are stored.
Figure 5-11 Pontoon game bit flag values
Another technique you can use to reduce memory size is to pass all parameters by reference (ByRef). Doing this means that a pointer to the variable is passed and the procedure does not need to make a local copy of the variable within the procedure it is being passed to. In other versions of Visual Basic, passing by reference would not be good practice because of the potential of inadvertently changing a parameter's value, affecting code outside of a procedure. Many of the Pontoon game's functions are designed to process data that is held in module-scoped variables. However, it is still a good idea to pass the module-level variable into procedures, because this improves reuse as the procedure does not get tied to the module or form where the variable is declared.
A common problem when trying to optimize code is that complex algorithms are often created, which can be very difficult to understand. It is a good idea to encapsulate complex functionality within distinct functions so that if maintenance is required, the functionality is not shrouded by unnecessary code. An example of this encapsulation is the function below that shuffles the deck of cards in our Pontoon game.
Private Sub ShufflePack(btaDeck, btaHand1, btaHand2, nGameStatus) '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' Shuffle the pack. We acheive this by marking each byte in the card ' ' deck array as being available (False). Obviously we cannot unmark ' ' any card that is currently held by the player or dealer. ' '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' Dim bteCard ' Value of card being processed. Dim nCard ' Counter for iterating our card values. Dim nSuit ' Counter for iterating suit values. ' Mark each card in our array as False, meaning available. For nCard = LBound(btaDeck, 1) To UBound(btaDeck, 1) For nSuit = LBound(btaDeck, 2) To UBound(btaDeck, 2) btaDeck(nCard, nSuit) = False Next Next ' Loop through the player's cards. Each of the player's cards ' must be made unavailable in the deck by marking it True. If IsEmpty(btaHand1) = False Then For Each bteCard In btaHand1 ' Calculate the array index for the card and set ' its element to True, indicating the card is in ' use. Bit 9 of the card array is the suit and bit ' 1-8 is the card's value. btaDeck(bteCard And &HF, (bteCard And &H70) \ &H10) = True Next End If ' Do the same for the dealer's cards. If IsEmpty(btaHand2) = False Then For Each bteCard In btaHand2 btaDeck(bteCard And &HF, (bteCard And &H70) \ &H10) = True Next End If nGameStatus = (nGameStatus And &HFF) End Sub
Looking at the code above you can clearly see all the actions for shuffling the deck. Such a procedure might be called only from one location in your program, but placing the shuffling code in its own procedure will help clarify both its logic and that of the procedure that calls it. Using magic numbers instead of constants in code has always been bad practice; however, we have to consider any memory constraint criteria. In this case, maintainability has been compromised in favor of size. If you choose to make this kind of compromise, try to keep the code simple. Whenever you develop complex logic, always ensure that there are plenty of comments, because code that might be easy to understand a day after you've written it has a habit of becoming quite complex three months later.
Often your application will need to use external files and resources such as Registry entries. Do not forget to consider the size of these elements when you start designing. You will need to think carefully about what you write to storage because this eats into the overall memory of the HPC. The Pontoon game does not use any Registry entries, but it does store the card graphics in bitmap files. We have 52 cards in our deck; each one has a corresponding bitmap file of 2022 bytes. Therefore, the overall storage space required is 105,144 bytes, or 103 KB. Our program file is 24 KB and we are using the PictureBox control, which is 59 KB. We can therefore calculate that the application will require a total of 183 KB. Because the HPC device has the Visual Basic CE run-time files in ROM, we do not need to include these components in our calculation. It would be too difficult to attempt to calculate the absolute total memory requirement because of the other influences, but so long as your program is small enough to run in the available memory and you have allowed for program terminations caused by insufficient memory, you should not have any problems.
An important point to be aware of is that on the desktop operating system each card would actually take up more space than its physical size; my PC, for example, will store each card in a space 32 KB in size. This size is called an allocation unit. Essentially, when writing files to disk, the operating system always write the data in blocks of a predetermined size (32 KB in my case). If I create a file less than or equal to 32 KB, the amount of disk space allocated for the file will actually be 32 KB. If the file's size is 33 KB, it will occupy two allocation units, or 64 KB. The allocation unit size on the PC is dependent upon the size of the hard disk partition and is configurable, but it can still be quite wasteful. Windows CE does not use allocation units, so my cards will occupy only the space equivalent to the file's actual size.
Programming for Windows CE
The Windows CE Toolkit for Visual Basic 5 is based on a subset of the Visual Basic, Scripting Edition programming language, so many Visual Basic language features available in other versions are not applicable or available when writing Windows CE programs. The Windows CE toolkit uses Visual Basic's code editor and syntax checker, and for this reason you will find that many of the errors caused by using features not available to the Windows CE environment will not be reported by the syntax checker. These errors are reported only at run time. Moreover, these run-time errors do not give specific information-they simply report that an error has occurred. When you run a Windows CE application in the development environment, the Visual Basic interpreter is not used-instead a debug version of your program is created and loaded either in the emulator environment or on the HPC device. Certain errors can be detected only after your application is executed. Once your program has started it has no further interaction with Visual Basic; instead, the Windows CE debugging window interacts with your program. Figure 5-12 illustrates how Visual Basic and the Windows CE toolkit components interact when you run a program. I will explain the emulator and the debugger in more detail later in this chapter.
In a Visual Basic Windows CE program you can create as many forms as you like, but you might have only one standard module. You cannot use class modules or any other type of module, such as User Documents or Property Pages, so you will not be able to create ActiveX components. You can, however, have related documents attached to your project. You need to be careful when using the properties and methods of standard Visual Basic objects, because many of the properties and methods either are not supported or have changed. The Knowledge Base contains articles providing a full list of what has changed or the excluded functionality, so I will not repeat them all here.
Figure 5-12 The Visual Basic debugging environment for Windows CE
What's new or changed in the language?
In addition to changes in the development environment, some new features and changes elsewhere have been added. The following is a description of some elements that are either new or that work differently. Some elements are not new but might have missed your attention in previous versions, and they might now have a more prominent role in your designs.
One of the more common programming elements is the array. A big change here is that the lower bound of an array is always 0-this cannot be changed. A new set of functions has been included, which will make manipulation of arrays an easier task. The Array function was introduced in Visual Basic 5, but it might have gone unnoticed. Arrays will probably player a bigger role in your Windows CE code, so I'll describe the Array function.
The Array function takes a list of comma-separated arguments and returns an array containing the list of supplied arguments. The syntax of the Array function is variable = Array(arg1 [,arg2…]). The following code shows how you might use the Array function.
Dim ProductType Dim Item ProductType = Array("1 - Grocery", 1, "2 - Tobacco", 2, "3 - Alcohol", 3) For Each Item In ProductType If IsNumeric(Item) = False Then List1.AddItem Item Else List1.ItemData(List1.NewIndex) = Item End If Next
In the example above, the variable ProductType is initially declared as a Variant. Assigning the result from the Array function causes a coercion to type Variant array. The bounds of the array are 0 to 5 because Windows CE supports only 0 as the lower bound of an array. The same result could be achieved using the more conventional coding technique of declaring the variable as an array and then assigning a value to each element, but the Array function is more efficient for small arrays.
The arguments of the Array function need not be hard-coded "constant" type values, as in our example above. Because the array created is a Variant array, you can use variables or even other arrays as arguments. The example below illustrates this.
Dim FirstArray Dim SecondArray Dim ThirdVariable FirstArray = Array("A Value", "Another Value") SecondArray = Array(FirstArray, "A Third Value") ThirdVariable = SecondArray(0) Print ThirdVariable(0) ' Prints "A Value" Print ThirdVariable(1) ' Prints "Another Value" Print SecondArray(1) ' Prints "A Third Value"
When assigning arrays as elements of an array, remember that because the element is, in fact, an array, any variable you assign that element to will also be coerced to an array type. You can assign SecondArray(0) to another variable that would then, in fact, contain FirstArray, or you could interrogate the array in situ:
Print SecondArray(0)(0) ' Prints "A Value" Print SecondArray(0)(1) ' Prints "Another Value"
For Each statement
The For Each programming construct should be familiar to nearly all programmers. With the increased support functions for array handling, you should be aware that the For Each construct can be used for arrays in addition to objects. (This has been available since Visual Basic 5.)
Dim ItemPrice Dim Goods Goods = Array(10, 12.54, 9.85) For Each ItemPrice In Goods ItemPrice = ItemPrice + CalculateTaxOn(ItemPrice) Next
In the example above, as the loop iterates, ItemPrice evaluates to the actual data in the array Goods for each element in turn.
The CreateObject function is not new; in fact, it has been around for some time, but most Visual Basic programmers probably use the more familiar syntax Set X = New Object. The Windows CE Toolkit for Visual Basic 5 does not allow the declaration of API calls-the Declare keyword is no longer valid, nor is the New keyword. Therefore, it is now necessary to use the CreateObject function to instantiate instances of ActiveX (COM) objects.
If you have created objects in Visual Basic before, you might have noticed that the object reference held in HKEY_CLASSES_ROOT of the Registry identifies your object by ServerName.ClassName. Therefore, if you create an ActiveX component (say, CUSTOMER.DLL) with public classes of Account and History, the entry in HKEY_CLASSES_ROOT would contain the entries shown in Figure 513.
Figure 5-13 Objects are identified by ServerName.ClassName
Although you cannot build objects for Windows CE using Visual Basic, you can still use objects created in another language like Visual C++ 5 with the Windows CE Toolkit. This can be particularly useful because you have the ability to create C++ objects that wrap API functionality.
The syntax of the CreateObject function is CreateObject(ServerName.ClassName). The following code shows how you would normally use this function to create a COM object instance.
Dim WinCeDB WinCeDB = CreateObject("WinCeAPI.DataBaseFunctions") WinCeDB.OpenDatabase Id, "My Database", 0, 0, vbNull
Some Microsoft applications like Word and Excel expose objects that you can use. I would strongly recommend using these and other Microsoft objects where possible. I would also expect a plethora of third-party objects to hit the market shortly, though you should apply your usual testing methods before using any of these.
For Next statement
A minor change has been made to the For Next construct. In Windows CE development you are not allowed to specify the counter variable on the Next line. The code
For Counter = 1 to 100 . . . Next Counter
is invalid and will produce an error. The code would have to be written as
For Counter = 1 to 100 . . . Next
in order to work.
Visual Basic string functions can no longer be used with the type suffix. Because the language supports only Variant data types, you will need to use the Variant functions instead, such as Mid instead of Mid$.
File handling is an intrinsic part of many applications. However, the file handling routines you are probably used to are not included in the language. Instead, a new ActiveX control is provided that wraps all the file functionality. The FileSystem component adds two controls to the toolbox: the FileSystem control and the File control. To use these you need to place them on a form.
The FileSystem control This control provides the means to manipulate the file system, such as creating and deleting files, searching, copying, and getting or setting file attributes. The following code snippet shows how you would use a FileSystem control (fs) to fill a list box with file names.
Do If sFile = "" Then sFile = fs.Dir(sPath & "*.*") Else sFile = fs.Dir If sFile = "" Then Exit Do List1.AddItem sPath & sFile Loop
The File control Whereas the FileSystem control provides the functionality to manipulate the file system, the File control allows you to create, read, and write file data. This example writes data to a random access file.
Dim vData Const REC_LEN = 20 Const ModeInput = 1: Const LockShared = 1: Const AccessRead = 1 Const ModeOutput = 2: Const LockRead = 2: Const AccessWrite = 2 Const ModeRandom = 4: Const LockWrite = 3: Const AccessReadWrite = 3 Const ModeAppend = 8: Const LockReadWrite = 5 Const ModeBinary = 32 vData = Array("Chris", "John", "Julie") fl.Open "My File", ModeRandom, AccessReadWrite, LockShared, REC_LEN fl.Put vData(0), 1 ' Write record 1 fl.Put vData(1), 2 ' Write record 2 fl.Put vData(2), 3 ' Write record 3 fl.Close
The Windows CE Toolkit for Visual Basic 5 language has seven objects. These are:
Of these objects, the Finance object is new. The other objects are Windows CE implementations with reduced functionality from other Visual Basic versions. The Finance object, as you might expect, provides financial functions, but you must create this object yourself in order to use it, as you can see below:
Dim oFinance Set oFinance = CreateObject("pVBFinFunc.pVBFinFunc") Text1.Text = oFinance.Pmt(0.0083, 48, 12000)