Creating a Compelling UI for Windows Mobile and the Microsoft .NET Compact Framework
4/7/2010
Alex Yakhnin, Microsoft Corporation
April 2009
Summary
Learn how to use the alpha blending and gradient drawing APIs that are available on the Windows Mobile® platform to create a compelling and attractive user experience.
Download Msdn.UI_code.zip.
Applies To
Windows Mobile 5.0 and later
Microsoft .NET Compact Framework 3.5
Introduction
Drawing an Image with Transparency
Gradient Fill
Extended Graphics
Drawing Rules
Starting from the UIElement Class
Creating a Host
Custom Title and Menu Bar
Sample
Conclusion
Introduction
Advances in hardware for Windows Mobile devices over the last few years have led to the appearance of devices that enable you to create attractive and functional user interfaces (UIs) with rich graphics and natural interfaces. The Windows Mobile platform is necessary for developers to deliver such creative solutions.
When you examine some attractive and functional UIs on mobile devices or even on the Windows Vista® desktop, you can see that most applications use the transparency and gradient fill techniques. The Windows Mobile platform provides several useful native APIs that can take advantage of these techniques.
You can call these APIs from the .NET Compact Framework 3.5, create a Graphics extension class, and use a sample of the Framework to simplify creation of attractive UIs.
Drawing an Image with Transparency
When drawing an image with transparency, the most common approach is to draw an image or an icon with just one background color being transparent. This can easily be done in managed code by using the SetColorKey(Color, Color) method that is described in the .NET Compact Framework Developer Guide on MSDN®.
The second most common approach is when the whole image becomes transparent and looks as if you could see through it. This effect is achieved by using a technique called "alpha blending". Alpha blending is the process of combining a translucent foreground with a background color, thereby producing a new, blended color with the appearance of partial transparency. The Windows Mobile platform includes the native AlphaBlend function that can apply alpha blending over the whole source image. If an image contains an alpha channel defined when it was created, you can draw it with the Imaging API that is part of the Windows Mobile platform since Windows Mobile 5.0.
Chris Lorton, one of the members of the .NET Compact Framework team at Microsoft, describes a set of declarations for using the AlphaBlend function and Imaging API through P/Invoke in his blog post. The code from his sample has become a part of the PlatformAPIs class that you will find in the solution that accompanies this article.
Displaying a Gradient Fill
Another way of spicing up your applications is by displaying a gradient fill. Gradient fill is the process that blends two or more colors together by gradually changing one into another. This could be easily achieved through the GradientFill function. The sample usage of this function is described in the MSDN article, How to Display a Gradient Fill. Parts of this code are reused in the GradientFill class in Msdn.UI_code.zip.
Extending Graphics
Now when you have all the required pieces, you are ready to wrap them all together in a more useful package. You can use the new functionality of the .NET Framework 3.5, which is called the Extension methods. Extension methods let developers add new methods to the public contract of an existing Common Language Runtime (CLR) type. You can extend the System.Drawing.Graphics class. The following code example shows these two methods.
/// <summary>
/// Draws an image with transparency
/// </summary>
/// <param name="gx">Graphics to drawn on.</param>
/// <param name="image">Image to draw.</param>
/// <param name="transparency">Transparency constant</param>
/// <param name="x">X location</param>
/// <param name="y">Y location</param>
public static void DrawAlpha(this Graphics gx, Bitmap image,
byte transparency, int x, int y)
{
using (Graphics gxSrc = Graphics.FromImage(image))
{
IntPtr hdcDst = gx.GetHdc();
IntPtr hdcSrc = gxSrc.GetHdc();
BlendFunction blendFunction = new BlendFunction();
// Only supported blend operation
blendFunction.BlendOp = (byte)BlendOperation.AC_SRC_OVER;
// Documentation says put 0 here
blendFunction.BlendFlags = (byte)BlendFlags.Zero;
// Constant alpha factor
blendFunction.SourceConstantAlpha = transparency;
// Don't look for per pixel alpha
blendFunction.AlphaFormat = (byte)0;
PlatformAPIs.AlphaBlend(hdcDst, x, y, image.Width,
image.Height, hdcSrc, 0, 0, image.Width,
image.Height, blendFunction);
// Required cleanup to GetHdc()
gx.ReleaseHdc(hdcDst);
// Required cleanup to GetHdc()
gxSrc.ReleaseHdc(hdcSrc);
}
}
/// <summary>
/// Fills the rectagle with gradient colors
/// </summary>
/// <param name="gx">Destination graphics</param>
/// <param name="rc">Desctination rectangle</param>
/// <param name="startColorValue">Starting color for gradient</param>
/// <param name="endColorValue">End color for gradient</param>
/// <param name="fillDirection">The direction of the gradient</param>
public static void FillGradientRectangle(this Graphics gx,
Rectangle rc, Color startColorValue, Color endColorValue,
FillDirection fillDirection)
{
GradientFill.Fill(
gx,
rc,
startColorValue,
endColorValue,
fillDirection);
}
As you can see, the DrawAlpha and FillGradientRectangle methods extend the Graphics class and now can be easily used in your drawing code. These methods are included in the GraphicsExtension class incorporated into the sample solution that is part of a download package for this article. The following code example shows how to use these methods.
void Form1_Paint(object sender, PaintEventArgs e)
{
Rectangle rectangle = new Rectangle(0, 0, 200, 100);
// Create temporary bitmap
Bitmap bitmap = new Bitmap(200, 100);
// Create temporary graphics
Graphics tempGraphics = Graphics.FromImage(bitmap);
// Draw a red rectangle in the temp graphics object
using (Brush brush = new SolidBrush(Color.Red))
{
tempGraphics.FillRectangle(brush, rectangle);
}
// Draw a gradient background on the form
e.Graphics.FillGradientRectangle(this.ClientRectangle,
Color.Blue, Color.LightBlue,
FillDirection.TopToBottom);
// Draw temp bitmap with alpha transparency
e.Graphics.DrawAlpha(bitmap, 60, 20, 100);
}
The previous code example paints a form with gradient colors and draws a red rectangle with alpha transparency on top. The following illustration shows the result.
The GraphicsExtension class also includes several other very useful methods that let you draw rounded rectangles which could be drawn with a gradient colors and drawn with transparency, such as FillGradientRectangle, DrawRoundedRectangleAlpha, and more. Feel free to explore the "goodies" in this class.
The following code example shows the usage of the DrawGradientRoundedRectangle method.
void Form1_Paint(object sender, PaintEventArgs e)
{
// Prepare rectangle
Rectangle rectangle = new Rectangle(20, 100, 200, 30);
// Draw a gradient background on the form
e.Graphics.FillGradientRectangle(this.ClientRectangle,
Color.Blue, Color.LightBlue,
FillDirection.TopToBottom);
e.Graphics.DrawGradientRoundedRectangle(rectangle, Color.Red,
Color.LightPink, Color.Red, new Size(6, 6));
}
And the following illustration shows the result.
Drawing Rules
If you examine the previous code examples, you notice a certain rule for achieving transparency on a Windows Mobile platform: the drawing must be done in the same graphics context. This is dictated by the fact that Windows Mobile does not support transparency on the windowing level. If you create a COM control deriving from the System.Windows.Forms.Control class, you will encounter difficulties when you try to make the control transparent in relation to the form that is hosting it.
Remember, too, that, even though the hardware is becoming more powerful, the memory and processor speeds are still somewhat limited compared with a desktop computer. Every allocation of the graphics context, brush, or a simple drawing operation takes time and resources. The most optimal way of drawing would be to use a single instance of the System.Drawing.Graphics object.
There is also a noticeable flickering when you run the sample. To resolve the flickering issue, you should use double-buffering technique which I explained in my older article.
Armed with this knowledge and the GraphicsExtension class described earlier, I have created a sample UI framework that you can use as a starting point when you develop your own applications for the .NET Compact Framework.
Starting from the UIElement Class
This framework builds on the following premises:
- Every UI element should handle its own drawing.
- The single Graphics object should be passed to the UI element to enable drawing.
Look at the UIElement class from the solution. This class is a base class that is used by the inheritors to create elements like ImageElement, TextBlock, and Border. This class implements a set of events, properties and the following methods.
/// <summary>
/// Renders the UIElement.
/// </summary>
/// <param name="graphics">Graphics object.</param>
public void Render(Graphics graphics)
{
this.OnRender(graphics);
}
/// <summary>
/// Render method to implement by inheritors
/// </summary>
/// <param name="graphics">Graphics object.</param>
protected virtual void OnRender(Graphics graphics)
{
}
As you can see from this code, the Render method can be called from the form or control with an instance of the Graphics object. Therefore, the virtual OnRender method would be called on any concrete class that implements it. The following code example shows how the ImageElement class implements the OnRender method.
protected override void OnRender(Graphics graphics)
{
if (this.image != null)
{
if (transparentBackground)
{
graphics.DrawImageTransparent(this.image, new
Rectangle(this.Left, this.Top, this.Width, this.Height));
}
else if (alphaChannel)
{
graphics.DrawImageAlphaChannel(this.imagingImage,
this.Left, this.Top);
}
else
{
graphics.DrawAlpha(this.image, this.Opacity,
this.Left, this.Top);
}
}
}
In this method, you are taking advantage of the methods from the GraphicsExtension class to draw an image with transparency or with the alpha channel. The following code example shows the same OnRender method in the TextBlock class.
protected override void OnRender(Graphics drawingContext)
{
if (this.Text != null)
{
Size sizeContent = Size.Empty;
// Prepare font
Font font = new Font(this.fontFamily,
this.fontSize, fontStyle);
// Measure the string
SizeF sizeContentF = drawingContext.MeasureString(this.Text,
font);
sizeContent.Width = (int)sizeContentF.Width;
sizeContent.Height = (int)sizeContentF.Height;
// Calculate the location of the text
Point point = GetLocationFromContentAlignment(this.alignment,
sizeContent);
// Draw the text
using (Brush brushText = new SolidBrush(this.Foreground))
{
Rectangle textRect = new Rectangle(point.X, point.Y,
this.Width - 1, this.Height - 1);
drawingContext.DrawString(this.Text, font, brushText,
textRect);
}
// Clean up
font.Dispose();
}
}
Creating a Host
To use the UI elements from the framework, you have to have a valid form or control class to provide a Graphics object for the elements. The following code example shows the added UIForm class, which inherits from the System.Windows.Form class and overrides its OnPaint method.
public class UIForm : Form
{
private Canvas canvas;
public UIForm()
{
this.canvas = new Canvas(this);
// Don't show title bar and allocate the full screen
this.FormBorderStyle = FormBorderStyle.None;
this.WindowState = FormWindowState.Maximized;
}
protected override void OnPaint(PaintEventArgs e)
{
// Draw a background first
this.offGx.Clear(this.BackColor);
// Draw background image
if (this.backgroundImage != null)
{
this.offGx.DrawImage(this.backgroundImage, 0, 0);
}
// Pass the graphics to the canvas to render
if (this.canvas != null)
{
this.canvas.Render(offGx);
}
// Blit the offscreen bitmap
e.Graphics.DrawImage(offBitmap, 0, 0);
}
}
From the previous code example, you can see that we are using the Canvas class from the framework. This class plays the role of the parent to all UI elements by holding its instances in the VisualCollection and relaying the Render method call to each of the UI elements in the collection. The following code example shows what the Render method looks like in the Canvas class.
protected override void OnRender(Graphics graphics)
{
// Draw a back color
if (this.Background != null)
{
using (Brush brush = new SolidBrush(Background))
{
graphics.FillRectangle(brush, new Rectangle(this.Left,
this.Top, this.Height, this.Width));
}
}
// Pass the graphics objects to all UI elements
for (int i = 0; i < children.Count; i++)
{
UIElement element = children[i];
if (element.Visible)
{
element.Render(graphics);
}
}
}
Custom Title and Menu Bar
Because we are making the UIForm to show full screen, you can implement a custom version of the title bar and menu bar on the form. You will find TitleBar and MenuBar classes in the Msdn.UIFramework project and declared in the UIForm. Both classes of course are derived from UIElement and implement its drawing in the Render method. You can easily customize the way that they both look by changing the colors for the gradient or back color. Both TitleBar and MenuBar are exposed from UIForm by using appropriate properties.
Sample
In order to show how to use the UI framework, I have created a sample project that you will find in the Msdn.TestClient project. It contains the DemoForm class which is derived from UIForm. The following code example shows what the constructor of the DemoForm looks like.
public DemoForm()
{
InitializeComponent();
// Set gradient for title bar
this.TitleBar.Fill = new LinearGradient(Color.DarkBlue,
Color.CornflowerBlue);
this.TitleBar.Text = this.Text;
// Set values for menu
this.MenuBar.LeftMenu = "Exit";
this.MenuBar.RightMenu = "Location";
this.InitializeWeatherUI("75", "Redmond, WA", 50, 50);
this.InitializeSlideShow(50, 140, "Garden.jpg");
}
In the constructor, we set a custom gradient to the TitleBar and some text values to the LeftMenu and RightMenu properties for the MenuBar. Also, to demonstrate the capabilities of the UI framework, I have decided to reproduce the appearance of some Sidebar gadgets which are available in Windows Vista, Weather and Slide Show. I have "borrowed" some images that are included with the gadgets and included them as a part of the Msdn.TestClient project in the Images folder. These images are created by using the alpha transparency and should be perfect to test the capabilities of the UI framework. This is why you see the InitializeWeatherUI and InitializeSlideShow methods being called in the constructor code earlier. Look at one of the methods and see how it is done:
private void InitializeSlideShow(int left, int top, string imageFile)
{
this.Canvas.AddElement<ImageElement>(() => new ImageElement()
{
Source = @"Images\slideshow_glass_frame.png",
Top = top,
Left = left,
AlphaChannel = true,
});
this.Canvas.AddElement<ImageElement>(() => new ImageElement()
{
Name = "Slide",
Source = @"Images\" + imageFile,
Top = top + 5,
Left = left + 4,
Stretch = true,
Width = 120,
Height = 90
});
}
As you can see, the InitializeSlideShow method accepts several parameters to set the position and an image file for the ImageElement. We add two ImageElements to the Canvas by using the AddElement method. This method is declared the following way in the Canvas class:
public T AddElement<T>(Func<T> buildFunc) where T : UIElement
{
if (buildFunc == null)
throw new ArgumentNullException("buildFunc");
T instance = buildFunc();
this.children.Add(instance as UIElement);
return instance;
}
As you can see, the AddElement method has a parameter of a Func delegate which accepts an instance of the UIElement. This is why you can use the Object initializers when they create an instance of the ImageElement.
In order to reproduce the Weather gadget appearance in the sample, you can use a set of ImageElement and some TextBlock classes to display the temperature and a city.
private void InitializeWeatherUI(string temperature,
string city, int left, int top)
{
this.Canvas.AddElement<ImageElement>(() => new ImageElement()
{
Source = @"Images\BLUEDOCKED-base.png",
Top = top,
Left = left,
AlphaChannel = true,
});
this.Canvas.AddElement<ImageElement>(() => new ImageElement()
{
Source = @"Images\docked_sun.png",
Top = top,
Left = left,
AlphaChannel = true,
});
// Display temperature
this.Canvas.AddElement<TextBlock>(() => new TextBlock()
{
Text = temperature + Convert.ToChar(176),
Top = top + 2,
Left = left + 97,
FontSize = 12f,
FontStyle = FontStyle.Bold
});
// Display city
this.Canvas.AddElement<TextBlock>(() => new TextBlock()
{
Text = city,
Top = top + 40,
Left = left + 40,
Width = 200,
});
}
And finally, this is what the DemoForm looks like when you execute the project.
Conclusion
In this article, I have shown how to extend the capabilities of the Graphics class in the .NET Compact Framework, by using some helpful drawing APIs that are available on the Windows Mobile platform. I have also described the UI framework that I built to demonstrate the capabilities of the Windows Mobile platform. However, this framework should not be considered production-ready code. Think of it as an example that you can extend and change to suit your own needs.
Author Bio
Alex Yakhnin is a Senior Mobility Consultant for the Mobility Global Practices group at Microsoft Corporation. Before his work with Microsoft, Alex was the architect and developer of small and large systems for many financial companies. He has written several MSDN articles, and for many years he has been a .NET Compact Framework/Device Application Development MVP and also a part of the OpenNETCF initiative.
See Also
Other Resources
How to Create a Microsoft .NET Compact Framework-based Image Button
Chris Lorton's Blog
How to Display Gradient Fill
Alex Yakhnin's Blog