Graphics To Go

Make A Mobile Imaging App With The .NET Compact Framework 2.0

Rob Pierry

This article discusses:

  • Managed code for mobile apps
  • Testing with device emulators
  • Graphics with the .NET Compact Framework
  • Interacting with Pocket Outlook
This article uses the following technologies:
The .NET Compact Framework 2.0, Windows Mobile 5.0, Visual Studio 2005

Code download available at: Pocket PC 2006_12.exe(157 KB)

Contents

Working with Images
Drawing Shapes and Text
Device Emulator Manager
Using the Camera
Checking System State
Working with Pocket Outlook
Advanced Synchronization Ideas
Conclusion

With the introduction of the .NET Compact Framework, Microsoft brought all of the advantages of managed code development to mobile applications. Developers already familiar with writing Windows® Forms applications could quickly get up to speed on programming for mobile devices. Version 2.0 of the Microsoft® .NET Compact Framework is now available and with it development has become even easier through the introduction of new tools (like the Device Emulator Manager) and a broadening of its coverage. In particular, version 2.0 adds more classes and methods for working with graphics-including, among other enhancements, angled text, custom pens, image manipulation, and interacting with a device's built-in camera. This article will cover these and other new enhancements that are available with Visual Studio® 2005 and the .NET Compact Framework version 2.0.

When you use the .NET Compact Framework to develop for Windows Mobile® 5.0, there are two main types of device you can target: Smartphones and Pocket PCs. While the tools for developing for each of these platforms may be the same, the development experience for each has been customized for the class of device it targets. Pocket PCs tend to be larger PDA devices that may optionally include phone functionality. Smartphones, on the other hand, are smaller devices that are primarily phones with PDA functionality included secondarily. Generally, Pocket PCs are more powerful, offering touch screens, higher screen resolutions, more memory, and faster processors. This article focuses on developing for Pocket PCs. But once you know how to develop for Pocket PCs, developing for Smartphones should be quite easy.

Before getting started, you need to install the Windows Mobile 5.0 SDK for Pocket PC. The SDK includes emulators for Pocket PC devices, documentation, and samples. Once that is installed, you're ready to get started.

In this article, I'll guide you through the development of a simple imaging application that supports a background image with text and lines drawn over it. The background image can either be loaded from an existing picture or captured using the device's built-in camera. Both of these tasks-opening and capturing pictures-will make use of standard dialogs that are new to version 2.0 of the Compact Framework. For the overlay, I'll demonstrate how to use the newly supported features of custom pens and angled text to draw on top of the background. The application will also let you save your pictures after you've drawn on them. As a quick aside, I'll show you how to add support for e-mailing pictures to a Pocket Outlook® contact.

Working with Images

At a fundamental level, the application must be able to store the background image and the foreground overlay of text and lines, and it must be able to form these elements into a single image (see Figure 1). Internally, the images remain separate elements until the application saves the final picture. This is to support the basic editing capabilities-replacing the background while keeping the overlay and clearing the overlay while keeping the background.

Figure 1 Seperate Background and Foreground Layers

Figure 1** Seperate Background and Foreground Layers **

When the main form loads, it creates two instances of the Bitmap class, one for the background (which defaults to light gray) and one for the overlay (which defaults to transparent). When the form paints itself, it draws the background and then the overlay.

To support transparency, the application uses the ImageAttributes class. In general, this class can be used to set a range of colors to be transparent, as well as to replace colors with other colors and to perform other manipulations targeted at bitmaps, brushes, pens, and the like. Unfortunately, in the Compact Framework, only the transparency settings are supported. The application uses a specific color to represent transparent and then uses ImageAttributes to prevent that color from being drawn when the form is painting. In this example, you'll use Color.AliceBlue as transparent and then you will make sure to not allow the user to choose this as a color for drawing text and lines. The application only needs to call the SetColorKey method of an instance of ImageAttributes and pass Color.AliceBlue for both parameters since you want a range of exactly one color to be transparent. Here's the code for drawing the overlay onto the background:

private void DrawOverlay(Graphics g, Rectangle region)
{
    using(ImageAttributes attribs = new ImageAttributes())
    {
        attribs.SetColorKey(_transparentColor, _transparentColor);
        g.DrawImage(_overlay, region, region.X, region.Y,
            region.Width, region.Height, GraphicsUnit.Pixel, attribs);
    }
}

To keep things simple, the application limits the image size to the visible area of the device's screen, available through the ClientRectangle property. Bear in mind that this value isn't necessarily constant. On devices with slide-out keyboards, for example, the client rectangle can be different depending on whether the keyboard is open (320×188) or closed (240×268). To work around this, the application defaults the images to the size of the client rectangle when the application is started. This underscores the importance of testing your Windows Mobile applications on actual hardware since the emulator can't cover all hardware configurations.

While the solid light gray background is very pleasant, it would be nice if users could select their own pictures to use as backgrounds. The application uses the new SelectPictureDialog class to accomplish this. Figure 2 shows the interface of SelectPictureDialog. This class is available in the Microsoft.WindowsMobile.Forms assembly, which you'll have to add a reference to if you want to use the dialog. The class has properties that let you set the dialog's title (Title), control whether or not the user can take a new picture with the built-in camera (CameraAccess), filter which pictures are shown by file type (Filter) or whether they are protected by Digital Rights Management (ShowDrmContent and ShowForwardLockedContent), set the initial folder to display (InitialDirectory), and prevent the user from changing to any other folder (LockDirectory). After the dialog is closed, you can check the FileName property to get the file name of the picture the user selected.

Figure 2 Selecting a Picture

Figure 2** Selecting a Picture **

Once the user selects a picture, the application replaces the current background with the selected picture, scaled down to fit on the screen. After grabbing a Graphics object for the background image, the DrawImage method is used to draw the newly selected picture onto the background. The second parameter represents the rectangle of the destination onto which you will be drawing (in this case, this is the entire background). The third parameter is the rectangle of the source image (meaning the entire source image), which is being mapped into the destination rectangle. This method is very flexible. How you're using it now automatically makes smaller pictures get stretched and larger pictures get shrunk to fit the background, but you could very easily just take a background-sized chunk out of the new picture by passing the background rectangle as both parameters.

Now that the application is able to create and draw the background and overlay, the task of saving is pretty simple. Instead of painting on the form, you use the SaveFileDialog to prompt the user for the new file name. It creates a copy of the background and then draws the overlay on top of it. Once you have the final image, you use the Save method (new in version 2.0 of the Compact Framework) to write the image out to disk in JPEG format. (BMP, GIF, and PNG formats are also supported.) Here's the code for generating and saving the image:

using (Bitmap newImage = new Bitmap(_background))
{
    using (Graphics g = Graphics.FromImage(newImage))
    {
        DrawOverlay(g, new Rectangle(
            0, 0, _overlay.Width, _overlay.Height));
    }
    newImage.Save(dlgSave.FileName, ImageFormat.Jpeg);
}

Drawing Shapes and Text

Now the user can customize the background by selecting an image, but you still need to allow the user to draw on the overlay. The Graphics object is used for drawing shapes. This has methods like DrawEllipse, DrawLine, and DrawRectangle-all of which take a Pen as a parameter. The Pen dictates the color and thickness used to draw the shapes. Previously, only Pens of a specified color could be created, but the Compact Framework version 2.0 exposes the Width property and another constructor, allowing you to specify both the width and color to use.

In the application, SelectPen.cs implements a form that lets the user choose a color and width for drawing shapes. The available colors are listed in a ComboBox and the width is specified using a NumericUpDown control. After the user makes selections, the main form sets the current pen to a new instance of the Pen class with the specified color and width. All subsequent drawing uses this pen. In this form, remember that you have to make sure that the color that represents transparent in the overlay (AliceBlue) isn't listed in the ComboBox. Otherwise the user may inadvertently draw invisible shapes.

The Draw Line option under the Draw menu shows how the current pen can be used to draw a line on the image. First, the user selects the two end points of the line. The application then obtains a Graphics object for drawing on the overlay and uses the DrawLine method with the current pen to draw a line connecting the two points.

The last step is to call Invalidate with the whole ClientRectangle as a parameter. This results in the form repainting itself, causing the background and the updated overlay to be drawn on the form. Strictly speaking, the only portion of the form that needs to be updated is the rectangle that contains the line that was just drawn. Therefore, one optimization is to invalidate only this rectangle instead of the entire client area.

When looking at the Graphics object, you may have noticed the DrawString method. This method draws a string in the specified font and color at a specified location. Since version 2.0 of the Compact Framework supports angled text, you can let the user select two points to define the line along which text will be drawn and then provide a dialog that allows the user to enter the text and select a size.

Angled text is supported via the LogFont class. This name stands for logical font, which is a structure utilized internally by Windows CE, the underlying operating system upon which Windows Mobile 5.0 is based. To adjust the angle of the text, you set the Escapement property of a LogFont instance to an angle in degrees, multiplied by 10. For example, for text that runs vertically from bottom to top, set the Escapement to 900 (90 degrees from the positive x axis).

The code for creating a Font via a LogFont is provided in the GetFont method of the text entry dialog, TextDialog.cs. There is a small bit of trigonometry at the top used to derive the angle for the text, given two points. Note that the y-axis of the device is inverted, having 0 at the top of the screen rather than at the bottom. Once you adjust for this and adjust for the range of the arctan function, you have a suitable angle for the Escapement property.

Keep in mind that when working with LogFont, you must always set the Orientation property to the same value as Escapement. To set the Height of the LogFont, you take the size provided by the user and adjust for the DPI of the device. (You can determine the DPI using the new DpiX and DpiY properties of the Graphics object.) When you provide this as a negative number, the Framework selects the font with the point size closest to the absolute value of the property. There are more options here, which are explained in the MSDN® documentation. Figure 3 shows the code used for getting a font via LogFont.

Figure 3 Getting a Font via LogFont

public Font GetFont(Point p1, Point p2, float dpi)
{
    int y = -(p2.Y - p1.Y); //adjust for inverted y axis
    int x = p2.X - p1.X;
    int angle = (int)(Math.Atan2(y, x) * 180 / Math.PI);
        
    //adjust for quadrant
    if (y < 0) angle = 360 + angle;

    LogFont lf = new LogFont();
    lf.Height = (int)(-EnteredSize * dpi / 96);
    lf.Escapement = angle * 10;
    lf.Orientation = lf.Escapement;
    lf.FaceName = Font.Name;

    return Font.FromLogFont(lf);
}

Once the LogFont has been set up appropriately, you can create an instance of Font by calling the static FromLogFont method of the Font class. Now that you have an appropriately sized font, some text, and a point, you can easily output it to the image using the DrawString method.

Device Emulator Manager

If you've done any mobile development with Visual Studio, you're probably already familiar with the Device Emulator. The emulator looks and acts just like a Pocket PC, letting you simulate running your programs on a real mobile device. The emulator can be configured to a variety of screen sizes, memory configurations, and available ports. It can even be configured for simulated phone functionality.

In Visual Studio 2005, emulators are handled via the Device Emulator Manager, which you can access via the Tools menu. When the Device Emulator Manager starts up, it lists all available emulators, grouped by target operating system. Note that there are several choices listed under the Windows Mobile 5.0 Pocket PC SDK, including the base emulator, one with phone functionality, and one with a VGA (640×480) resolution screen.

Right-clicking on an emulator and selecting the Connect option causes the emulator to start up. The Device Emulator Manager then displays a green arrow next to the emulator. Figure 4 shows the Device Emulator Manager with an emulator running.

Figure 4 Device Emulator Manager with an Emulator Running

Figure 4** Device Emulator Manager with an Emulator Running **(Click the image for a larger view)

A feature new to Visual Studio 2005 and ActiveSync® 4.0 is the ability to create a partnership between the desktop machine and the emulator, as if it were an actual device. To do this, open ActiveSync and choose File | Connection Settings. Make sure Allow connections to one of the following is checked and select DMA from the dropdown (the emulator talks to ActiveSync over DMA). Press OK. Now, in the Device Emulator Manager, right-click on the running emulator (the one with the green arrow next to it) and choose Cradle. ActiveSync will activate and then prompt you to create a partnership with the new device. The Device Emulator Manager will change the green arrow to a different icon indicating that the device is cradled. To disconnect the emulator from ActiveSync, simply right-click on the emulator in the Device Emulator Manager list and choose Uncradle.

Although the Device Emulator Manager has made emulators more powerful in Visual Studio 2005, the emulators aren't without limitations. As I mentioned earlier, the emulator doesn't currently simulate devices with slide out keyboards. Another critical limitation comes up when you start adding support for backgrounds captured via a device's camera. Right now the emulator doesn't support camera functionality at all. If you try to access the camera dialog while running on the emulator, you will get an Invalid-OperationException.

Using the Camera

Now it's time to let the user take a picture to serve as the background. To run this portion of the application, you'll need a Windows Mobile 5.0 Pocket PC with a camera. Once you've connected the device to your machine and optionally created a profile in ActiveSync, change the Target Device from Windows Mobile 5.0 Pocket PC Emulator to Windows Mobile 5.0 Pocket PC Device in the dropdown on the Device toolbar and then run the project as usual. (Note that breakpoints and debugging continue to work when you deploy to a real device just like during debugging within the emulator.)

When the user selects New Background from the File menu, the application creates and shows an instance of the new CameraCaptureDialog. This is in the same assembly as the SelectPictureDialog, Microsoft.WindowsMobile.Forms. The dialog lets you fully configure the initial settings for the camera, although users can still adjust them via the regular options menu. The Mode property specifies whether the user can only take a single picture (CameraCaptureMode.Still), a video (CameraCaptureMode.VideoOnly), or a video with audio (CameraCaptureMode.VideoWithAudio). Since you are looking to capture a background image, you want the application to set the dialog to still picture mode and to use the highest quality for still images (using the StillQuality property). You can also set properties indicating an optional time limit for the length of the video (VideoTimeLimit) and for ensuring that the video is suitable for sending in a multimedia message (VideoTypes).

Optionally, the Resolution property can be set to control the size of the picture being captured. In general, arbitrary values aren't supported. Instead, you should use values appropriate to the capabilities of your device (such as 640×480 or 1280×1024). Since you'll be scaling the picture down to fit onto the screen, leave the Resolution at its default value.

Once the user has taken a picture, the ShowDialog method returns with a result of DialogResult.OK. Now, to load the newly taken picture, you can use the same scaling technique you used earlier to resize an existing image to the background. Since only the background image is replaced, any lines or text the user has already drawn remain intact. Once the picture has been drawn onto the background, you call Invalidate with the entire client rectangle, and this causes the screen to repaint with the new picture now displayed as the background.

Checking System State

You can show the CameraCaptureDialog and then, if the device doesn't have a camera, have the InvalidOperationException raised. But it is more graceful to determine ahead of time whether the camera capability exists and then simply disable the menu option if the device does not offer camera functionality. You can do this by querying the device for its general configuration and monitoring real-time properties for changes.

Information about the device is exposed through the SystemState class, which is contained in the Microsoft.WindowsMobile.Status assembly. You can use this information in your application by adding a reference to the assembly and its dependency, Microsoft.WindowsMobile. The SystemState class has a host of static properties that let you determine fixed configuration information and real-time information like the current date and time or the remaining charge in the battery.

The application only needs to check the CameraPresent property once at startup, and then it can disable the New Background menu option if a camera isn't available. The SystemState class, however, exposes a Changed event that you can handle to be notified when the requested state changes. Instead of using the static properties, you can create an instance of SystemState, passing the specific system property to be monitored into the constructor and then just adding a handler to the Changed event. You could, for example, extend the application to monitor the DisplayRotation system property so the application is notified when the user slides out the device's keyboard and the display changes from portrait to landscape. Or the application could monitor the ActiveSyncStatus property to possibly take some action when the device is being synchronized.

The SystemState class also takes the ComparisonType and ComparisonValue properties into account before raising the Changed event. By default, all changes in the monitored property result in the Changed event being raised. But if, for instance, you set the ComparisonType property to ComparisonType.Greater and the ComparisonValue property to 2, the Changed event will only be raised if the system property you're monitoring changes to a value greater than 2. Using other comparison types-like Equals, Contains, or StartsWith-you get finely grained control over which events your application handles.

The main limitation to SystemState is that your application has to be running when the status changes in order for it to be notified. You also need to make sure you declare the instance of SystemState whose Changed event you're handling as a class-level field, instead of a local method variable, so that it isn't garbage collected, causing you to miss events.

Working with Pocket Outlook

Windows Mobile 5.0 provides a new assembly, Microsoft.WindowsMobile.PocketOutlook, for interacting with the messaging accounts, tasks, calendar items, and contacts stored on a device. You can use this assembly in your application, allowing the user to select a contact with an e-mail address and then send the contact a picture.

Using the new ChooseContactDialog, you can prompt the user to either select an entire contact or a specific property of a specific contact (as shown in Figure 5). One of the most useful features of this dialog is the ability to filter the list of available contacts. For this application, you only want to show contacts with e-mail addresses, since the feature is intended for e-mailing a picture to the contact. The RequiredProperties property of the dialog contains an array of ContactProperty enumerations that control the filtering. The application uses a single ContactProperty with the value ContactProperty.Email1Address to indicate that only contacts with a valid e-mail address should be shown. This enumeration contains all the properties of the contact, such as Birthday, BusinessAddress, and HomeTelephoneNumber. Using RequiredProperties, you can easily filter the list of contacts down to a manageable list that is relevant to how the contact will be used in this application.

Figure 5 Selecting a Contact

Figure 5** Selecting a Contact **

You use an instance of the EmailMessage class to actually send the message. You simply add a new Recipient to the collection in the To property, setting the Subject and Body properties, and add the image to the collection of Attachments. The main interaction with all messaging accounts, the calendar, and other Outlook functions is accomplished through OutlookSession. Once you create an instance of OutlookSession, you can use the EmailAccounts property to grab the first e-mail account and send the message. Based on the status and settings of the device, the e-mail is either sent immediately or the next time the user has network connectivity and starts a send operation. Figure 6 shows the code for sending the e-mail.

Figure 6 Using OutlookSession to Send E-Mail Messages

if (dlgContact.ShowDialog() == DialogResult.OK)
{
    //send an email
    EmailMessage msg = new EmailMessage();
    msg.To.Add(new Recipient(dlgContact.SelectedContact.Email1Address));
    msg.Subject = "Here is an image I created...";
    msg.BodyText = "...using Windows Mobile 5";
    msg.Attachments.Add(new Attachment(filename));

    using (OutlookSession outlook = new OutlookSession())
    {
        outlook.EmailAccounts[0].Send(msg);
    }

    MessageBox.Show("Send successful.");
}

Advanced Synchronization Ideas

Although sending an e-mail message works fine for the simplified example, it isn't always the correct choice for transferring data. For more complex scenarios when you need to move data between a device and a desktop, ActiveSync would be more appropriate. Even though Microsoft has greatly expanded the coverage of the Compact Framework in version 2.0, some areas still remain outside the realm of managed code. Interacting with ActiveSync and communicating with a device from a desktop machine still require the use of unmanaged code. But there are ways of getting started without writing unmanaged code.

ActiveSync synchronizes data between a mobile device and a desktop through a partnership and various service providers (such as the Outlook provider for synchronizing mail and calendar information). Each provider is comprised of a set of DLLs (one on the device and one on the desktop) that implement specific COM interfaces. These providers are very capable, but also complicated to develop. For basic scenarios where intricate synchronization is less of a concern, however, there is an easy way to be notified when a mobile device has been connected or disconnected without implementing a service provider.

In the registry there is a list of programs to be run automatically when a device either connects or disconnects. The keys are located at HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows CE Services\AutoStartOnConnect and \AutoStartOnDisconnect. To make use of these keys, just create a string value with a unique name that contains the path to the executable you want to run. For example, if you add a string with the name NotepadTest and the value notepad to AutoStartOnConnect, Notepad will launch every time a device is connected to the system. If you want to pass command-line arguments to the program, enclose the name of the program in double quotes. A value of "notepad" c:\test.txt will start Notepad with the contents of test.txt when a device connects. Alternatively, Windows Mobile 5.0 allows you to start and stop the ActiveSync process from the device programmatically through the unmanaged functions ActiveSyncStart and ActiveSyncStop.

Now that you know how to start an application on the desktop whenever a device is connected, the next step is to communicate with the device, which is accomplished via the Remote Application Programming Interface (RAPI). The .NET Framework does not currently include a managed version of RAPI. Since RAPI is just a native API, .NET supports the ability to call the functions it contains via Platform Invoke. Creating a .NET-based application that uses this API and is automatically started when a device connects is an easy way to handle moving data back and forth between a mobile device and a desktop machine.

Conclusion

The Compact Framework version 2.0 (along with Visual Studio 2005) builds on the successes of the first release to make developing mobile applications even easier. And the applications you produce will be even more capable. Since developers can use the same tools and classes to develop mobile applications as they use to develop other managed code applications, more developers can more quickly get into writing powerful mobile applications.

Meanwhile, the new APIs allow developers to take fuller advantage of all the capabilities of increasingly powerful mobile devices. They can write apps that interact with a device's camera and include features for communicating with other devices. Ultimately, developers can create the most well-suited solutions for their needs, utilizing the appropriate features on the appropriate devices, without having to worry about extra development effort and costs.

Rob Pierry is a Senior Consultant with Geniant, a leading enterprise IT consulting company specializing in service-oriented architecture. He can be reached at rpierry+msdn@gmail.com.