When I started this article, there were a lot of interesting points to
highlight about changes that needed to be made when porting a GDI+ desktop
application to Pocket PC. Then Visual Studio .NET 2003 (codenamed Everett)
came on the scene. If porting an application created with the Compact
Framework was already easy, the subtle changes in the Compact Framework,
which is now delivered as part of Everett, make things even simpler.
The .NET Framework opens whole new horizons to programmers especially
to game programmers with its ability to run the same code across different
devices running different operating systems. Of course this compatibility
will never be 100%, since every device has its own characteristics,
strengths, and weaknesses, but it's really great to be able to write a
program for a PC and then make it run on a Pocket PC with just a few
adjustments!
In previous versions of Visual Studio (before .NET), if we wanted to
create a program to run on a mobile device such as the Pocket PC we had to
use a specific version of the compiler, and the operating system made no
compromises to provide compatible functions. Porting a program was sometimes
a matter of erasing and rewriting everything. This portability problem was
especially evident when dealing with graphical functions. Even simple
programs sometimes needed adjustments before running on a different device.
Creating a Smart Device Application on Everett
Visual Studio .NET 2003 already has built-in support for the .NET
Compact Framework, with the corresponding assemblies and project templates
to support projects targeting mobile devices. The new project templates are
named "Smart Device Application" and "ASP.NET Mobile Application", and they
allow us to create applications to be used on either Pocket PC or Windows
CEbased devices. Figure 1 shows the "New Solution" window of Visual Studio,
highlighting the Smart Device Application item.
Once the project is created, we can see that we have new menu options:
in the Tools menu, there's a "Connect to Device" option, and under the Build
menu there is a new option to "Deploy".
After creating a program, we can press the Start button on the Visual
Studio toolbar just like we would do in any project targeting common PCs.
Visual Studio will then build the program with the proper libraries
according to the platform we choose, and open a window that will allow us to
choose the target device for the application, as presented in Figure 2.
If we choose to deploy the program to the emulator, Visual Studio will
load the emulator before deployment. The emulator is an exact copy of the
Pocket PC system, including all programs (yes, it comes with "Solitaire,"
too) and allows us to even choose an emulator "skin," which is a bitmap with
active buttons. It allows us to test our application in the exactly same way
we would with a real device, without needing a real device.
Once the emulator is loaded or the device is connected, Visual Studio
.NET will deploy not only the application but also any necessary libraries
to make the program run on the desired device. The application will be
deployed to the \Windows directory on the device, and Visual Studio will
automatically run it, even allowing us to debug it.
Porting an Existing Application to Pocket PC
We will be able to run any simple desktop PC program with very few
adjustments using the Smart Device Application template, and some programs
won't need any updates, just a new compilation and, of course, replacing the
form interface controls with the corresponding ones for the Smart Device
Application.
As for the graphical functions, of course GDI+ is not completely present
in the mobile device, but many of its functions are there and use the same
interfaces, so porting graphical applications is simpler than in any
previous version of Visual Studio.
To illustrate some common problems we face when porting an application
based on GDI+ to Pocket PC, we will create a Tetris clone called .Nettrix.
This sample comes from my book, .NET Game Programming, in which a desktop
version is created in Chapter 1. We will not present all code here, just
some highlights, but the full code is available below.
Creating the Interfaces
The first, somewhat tedious, step in porting an application to a mobile
device is re-creating all the interface elements, since there's no wizard to
port the forms for us. Figure 3 presents similar screens created for Pocket
PC and desktop PC.
Copying the Code
After creating the interface elements, we can copy all code (from the
forms and other modules) to the mobile device program. In our sample game,
the code is organized in four modules: the main form and three classes, as
presented in the simplified class diagram shown in Figure 4.
The Square class draws and erases a square on the screen; the Block
class draws, erases, and moves four squares to form basic .Nettrix blocks
with different shapes, and the GameEngine class has some general- use
functions, such as the collision-detection support array and the basic
functions to deal with it. Besides these classes, I implemented the game
logic directly in the main form events. The game variables are initialized
in the Load event; the game loop is in the Tick event of a timer, and the
input handling routine is in the KeyPress event.
Fixing the Errors
Although it's not my goal here to discuss all the possible issues you
can face when porting a GDI+ application to a mobile device, I will provide
a good example of each of the error classes:
Compilation errors due to modifications in the functions or event
interfaces or other programming elements
Compilation errors due to missing features in the target platform
Runtime errors due to different behavior of compatible functions or
object initialization
Program malfunctions in which there are no visible errors, but the
program doesn't work as expected, due to slightly different behavior in
compatible functions
Modifications in functions, events, and other programming elements
An example of this last type of error is the modification of the
parameters of the MessageBox Show event, which are different on the Pocket
PC version. The last parameter (the default button) is mandatory, and we
also have to take care selecting the icon used, since some icons are not
used. For instance, the Stop icon corresponds, in the Pocket PC, to the Hand
icon, the name formerly used on the desktop platform. So the desktop PC code
line
MessageBox.Show("GAME OVER", ".NetTrix",
MessageBoxButtons.OK, _ MessageBoxIcon.Stop)
must be ported to Pocket PC as
MessageBox.Show("GAME OVER",
".NetTrix", MessageBoxButtons.OK, _ MessageBoxIcon.Hand,
MessageBoxDefaultButton.Button1)
This illustrates perfectly the first type of error we would expect to
find when porting games to mobile devices: some functions take slightly
different parameters, and some of the overrides (different ways to call the
same functions) are missing. These are the easier problems to solve, since
all we have to do is make simple adjustments such as completing the extra
parameters or correcting the parameter values.
Another example of this kind of error is the modification of some events
names and arguments. For example, the Activated event for the form doesn't
exist on the Pocket PC, which uses the corresponding event, GotFocus.
Missing Features
The second type of problem we face when porting games to mobile devices
is that some functions, methods, and events are missing or correspond to
different ones. This kind of error may be somewhat difficult to fix, since
we must look for the relevant method, event, or function and, if there's no
exact match, sometimes we'll need to rewrite part of the program.
In my sample game, I came across one error of this type in the Show
method of the Square class: the Graphics object for the Pocket PC is far
simpler than the one for desktop computers; and it doesn't support the
DrawPath method used to draw a gradient square on the desktop version of the
game. In this case I needed to rewrite the Draw method of this class to make
it simply draw a square with a solid border.
The desktop PC version of the Show method, which draws a nice rectangle
filled with a gradient path, is presented in Listing 1 The Pocket PC version
of the same method is far simpler, as you can see in Listing 2.
Another error of this type pertains to the creation of the Graphics
object. While we can create a graphic for desktop PCs using the handle of
any control, the Pocket PC version can only be created from an image. In
fact, the controls on the Compact Framework don't have handles, so we must
avoid functions that take a handle as a parameter, since they will not work
on mobile devices.
Fixing Runtime Errors
After fixing the compilation errors, programs will run on the Pocket PC,
but sometimes they abort as soon as they are run. In the first betas of the
Compact Framework we faced some similar problems because all bitmap images
from PictureBoxes had to be explicitly created, or else any command issued
over them would fail. Fixing this problem was as simple as adding one extra
line to the form's Load event.
PicBackground.Image = New Bitmap(PicBackground.Width,
PicBackground.Height)
This type of error doesn't happen in Everett; but it illustrates very
well the third variety of error that we can find when moving programs to
other platforms such as mobile devices: there are no more build errors, but
the program generates a runtime error because something (a function, method,
or event) doesn't behave as expected.
This class of errors is a little more difficult than the previous ones
to fix, since the error can occur in a different place from where it is
generated. For example, if we don't explicitly create the image for the
PictureBox, the error will occur when the Square class tries to draw a
square and, depending on how we created the error-trapping routines, we
might need to set breakpoints and do a step-by-step debug to discover where
the error is occurring and how.
Fixing Program Malfunctioning
Once all compilation and runtime errors have been fixed, we are ready to
face the last and toughest error category we will encounter when porting
programs: everything works fine, but something doesn't behave as expected.
Or, in other words, there are no errors, but our program doesn't work as
planned.
Figure 5 presents one such error that occurred when porting .Nettrix to
an earlier version of Visual Studio with the Compact Framework. The screen
was not updated by the drawing routines, and we could only see the blocks on
the game field after closing the "game over" dialog box, which forced a
redraw of the underlying PictureBox.
In Everett this undesirable behavior is fixed, but let's continue with
this example just to see what we should do in this situation. The code has
no intrinsic errors, since this same code ran on a normal desktop computer.
Taking a new look at the program, we can't find anything wrong, and even the
documentation for mobile devices gives us no clues. What to do?
There are no magic tricks here: we must look for something similar that
works, like SDK samples. If we look through the samples downloaded with the
.NET Compact Framework, we'll find a simple graphical example called
Bubbles. This program presents some circles moving on the screen, providing
an illusion of floating soap bubbles.
If we run through this example, we will discover that the main
difference between the way our program updates the screen and the way this
sample does is that this sample draws directly to the screen using a
Graphics object created by the CreateGraphics function that receives the
form handle as a parameter. In contrast, our program does the drawing in a
Graphics object created from a bitmap contained in a PictureBox.
To make .Nettrix run in Visual Studio .NET 2001, we needed to modify the
drawing routines to draw directly on the form, forgetting the PictureBoxes.
In Everett, fortunately, the same routines would run in both platforms.
Final Touches
Creating any extra interface elements is a final touch that can enhance
the usability of the program. In our .Nettrix game, we created a set of
buttons that can be used by tapping with a pen on the Pocket PC, while
allowing keyboard input so desktop PC users can use navigation keys to play
the game. Figure 6 presents the game, running on the Pocket PC emulator.
Conclusion
One of the most interesting details about the migration of this specific
game is that once we have migrated the code to Pocket PC, we can copy all
the code back to the desktop .Nettrix project, and it will run without any
modification. After the updates, the code is 100% compatible with both
platforms, since the GDI+ on the Pocket PC is a subset of the GDI+ for
desktops, and all other functions used are present on both platforms.
About The Author
Alexandre Santos Lobäo got his first computer in 1981 at age 12 and
immediately began creating simple games in Basic. Since then, computers have evolved greatly and so has he. After many years of working on informatics in areas ranging from banking to small business, and computers ranging from the IBM 3090 to Pocket PCs, he
managed to return to his first passion, putting his knowledge gained as a
long-term nonprofessional game developer into his first technical book, .Net
Game Programming with DirectX 9.0, published in March by APress.
aslobao@hotmail.com
Listing 1
Public Sub Show(ByVal Graph As Graphics)
Dim graphPath As Drawing2D.GraphicsPath
Dim brushSquare As Drawing2D.PathGradientBrush
Dim surroundColor() As Color
Dim rectSquare As Rectangle
' Create a path consisting of one rectangle
graphPath = New Drawing2D.GraphicsPath()
rectSquare = New Rectangle(location.X, location.Y, _
size.Width, size.Height)
graphPath.AddRectangle(rectSquare)
' Creates the gradient brush which will draw the square
' Note: Therešs one center color and an array of border colors
brushSquare = New
Drawing2D.PathGradientBrush(graphPath)
brushSquare.CenterColor = forecolor
surroundColor = New Color() {backcolor}
brushSquare.SurroundColors = surroundColor
' Finally draws the square
Graph.FillPath(brushSquare, graphPath)
End Sub
Listing 2
Public Sub Show(ByVal Graph As Graphics)
' Draws the square
Graph.FillRectangle(New Drawing.SolidBrush(backcolor),
_location.X, location.Y, size.Width, size.Height)
' Draws the square border
Graph.DrawRectangle(New Pen(forecolor), _
location.X, location.Y, size.Width -
1, size.Height - 1)
End Sub
All Rights Reserved
Copyright © 2004 SYS-CON Media, Inc.
E-mail:
info@sys-con.com