Friday, April 20, 2007

.NET CF Custom Control: BorderPanel

Creating custom controls using the .NET Compact Framework 2.0 and Visual Studio 2005 can be a tough chore at times, but sometimes it can also be very rewarding and time saving. This entry details how to create a .NET CF BorderPanel custom control. A BorderPanel is a type of Panel which contains a border of a specified color and width. This control is used in the Run Application at Time (and Time Change) post to separate different sections of the demonstration application into logical groupings.

Inside the Rhinomobile.ControlLibrary sample project, which is of type ControlLibrary, there are three files to note: BorderPanel.bmp, BorderPanel.cs, and DesignTimeAttributes.xmta. BorderPanel.bmp is the graphic which represents the BorderPanel when it is placed in the Toolbox. BorderPanel.cs is the source code for the BorderPanel custom control. DesignTimeAttributes.xmta is an xml file which contains information used by Visual Studio when using the control through the designer. The project layout including these three files is displayed in Image 1.

Image 1 - Solution Explorer Showing BorderPanel.bmp, BorderPanel.cs, and DesignTimeAttributes.xmta Files


BorderPanel.bmp is a 16 x 16 bitmap added to the project with the same base name as the custom control. The Build Action for this file should be set to "Embedded Resource". The BorderPanel.bmp file used in this project is displayed in Image 2.

Image 2 - BorderPanel.bmp Graphic


BorderPanel is a .NET Compact Framework 2.0 Custom Control which behaves exactly like a Panel yet has a border of a specified color and width. BorderPanel inherits from the standard System.Windows.Forms.Panel class. By doing so, BorderPanel automatically acts in the same fashion as a Panel. The only difference with a BorderPanel is how it is drawn.

Two fields are added to the BorderPanel class, one containing information about the border color and the other about the border width. Corresponding public properties are also added to the class. These are named appropriately enough BorderColor and BorderWidth. A call to Invalidate() is also added to the mutator (set) of the public properties. The Invalidate method with no parameters causes the entire control to be redrawn on the next painting. Changing the border color or changing the border width would both cause the visual look of the control to change and is why Invalidate is called.

The OnPaint method inherited from the Panel class is overridden to draw the custom control. A new Pen object is instantiated, used to draw the border, and then disposed. It is of extreme importance to dispose of GDI objects as soon as they are finished being used on a mobile device as they are a major source of memory issues. The width of the Pen is set to the BorderWidth property and the color is set to BorderColor.

Calculating where to draw the border though is an interesting issue. The drawing is covered in these two lines:


int halfWidth = m_borderWidth / 2;
e.Graphics.DrawRectangle(borderPen, halfWidth, halfWidth,
this.Width - m_borderWidth, this.Height - m_borderWidth);


The halfWidth variable is used due to the alignment of the Pen object. Pen.Alignment is not a property that is supported on the .NET Compact Framework. The alignment of the Pen is therefore set to center, meaning that if the width of the pen is 10 pixels, 5 are going to be drawn on one side of the pen and 5 on the other. For this reason, half the width (halfWidth) of the Pen is calculated and used as the starting x,y coordinate for the drawing of the border. This ensures that the entire border is drawn on the viewable area. The third and fourth parameters to the DrawRectangle method are a width and height of the rectangle which is the reason the entire width and entire height of the border are subtracted from these values respectively.

The final item in OnPaint is a call to base.OnPaint(e) to ensure that other items are properly drawn. The source code for the BorderPanel is shown in Code Listing 1.

Code Listing 1 - BorderPanel.cs


#region Using Directives

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;

#endregion

namespace Rhinomobile.ControlLibrary
{
/// <summary>
/// A Panel with a colored sizable border
/// </summary>
public class BorderPanel : Panel
{
#region Fields

private Color m_borderColor = SystemColors.Control;
private int m_borderWidth = 4;

#endregion

#region Constructor

/// <summary>
/// BorderPanel Constructor
/// </summary>
public BorderPanel() : base()
{
}

#endregion

#region Protected Methods

protected override void OnPaint(PaintEventArgs e)
{
if ((m_borderWidth > 0) && (m_borderWidth < (this.Width / 2)))
{
Pen borderPen = new Pen(m_borderColor, (float)m_borderWidth);
try
{
int halfWidth = m_borderWidth / 2;
e.Graphics.DrawRectangle(borderPen, halfWidth, halfWidth,
this.Width - m_borderWidth, this.Height - m_borderWidth);
}
finally
{
borderPen.Dispose();
}
}
base.OnPaint(e);
}

#endregion

#region Properties

/// <summary>
/// Color used for drawing border on panel
/// </summary>
public Color BorderColor
{
get
{
return m_borderColor;
}
set
{
m_borderColor = value;
this.Invalidate();
}
}

/// <summary>
/// Width of border on panel
/// </summary>
public int BorderWidth
{
get
{
return m_borderWidth;
}
set
{
m_borderWidth = value;
this.Invalidate();
}
}

#endregion
}
}



DesignTimeAttributes.xmta is an xml file which contains information about classes, properties, methods, and events used by Visual Studio at design time for controls. The xmta file for the BorderPanel custom control, demonstrates how to add descriptions for added properties as well as placing the properties within the proper categories in the Properties Window.

Working with the DesignTimeAttributes.xmta file can be one of the flakiest and time consuming tasks when working with the .NET Compact Framework. The one for this control has been simplified to only show the simplest two elements. The DesignTimeAttributes.xmta file can be found in Code Listing 2.

Code Listing 2 - DesignTimeAttributes.xmta


<?xml version="1.0" encoding="utf-16"?>
<Classes xmlns="http://schemas.microsoft.com/VisualStudio/2004/03/SmartDevices/XMTA.xsd">
<Class Name="Rhinomobile.ControlLibrary.BorderPanel">
<Property Name="BorderColor">
<Category>Appearance</Category>
<Description>The color used to draw the border of the panel.</Description>
</Property>
<Property Name="BorderWidth">
<Category>Layout</Category>
<Description>The width of the panel border.</Description>
</Property>
</Class>
</Classes>



At this point the project can be compiled and the control library built. The following images indicate how to go about adding the BorderPanel to the Toolbox within Visual Studio.

Image 3 - Toolbox Before Adding BorderPanel


Right click on the Toolbox and select "Choose Items..."

Image 4 - Choose Toolbox Items Dialog Before Adding BorderPanel


Click the "Browse" button on the Choose Toolbox Items Dialog.

Image 5 - Choose Toolbox Items Browse Dialog


Navigate to the location of the control library assembly and select it.

Image 6 - Choose Toolbox Items After Adding BorderPanel


Notice the controls in the assembly have been added to the list of components and checked. Click the "Ok" button on the dialog and the controls should be added to the Toolbox.

Image 7 - Toolbox After Adding BorderPanel


Image 8 - Form1.cs Design After Dragging Over a BorderPanel from the Toolbox


Image 9 - Properties Window Showing the BorderColor and BorderWidth Properties

2 comments:

MobileProjects.net said...

I use Resco MobileForms Toolkit. very slick solution easy to use and great support. see the whole list of their controls and components

Marcello said...

Great and very usefull work!

But I've a problem I cant't figure out.
How I can create a custom control (like a panel) that can contains other panels at design time?
I'll create a component like a Tab Control to make a wizard panel.
I can create the control, but when I add something at design time the added object is not inserted in "my component" but to the form.

Thanks.