With the release of the .NET Compact Framework developers have a new
platform for working in the mobile space. The .NET Compact Framework brings
most of the namespaces and functionality of Windows Forms development and
the .NET Framework to PDAs and other devices. This portability of code and
knowledge also applies to components for the presentation layer. Whether you
use your own custom-built controls or controls purchased from a third party,
you can reap the benefits of lower development costs and a faster time to
market using .NET Framework components within Compact Framework
applications.
While Microsoft has done an outstanding job of maintaining similar
interfaces in the Compact Framework and the .NET Framework, developers must
realize that not all of the features of controls will transfer. Because of
the nature of the underlying hardware, developers must remember that memory,
CPU power, and other system resources are at a premium.
When it comes to control design, as with all Compact Framework
applications, the general rule of thumb is "small is good." This applies not
only to the amount of screen real estate the control uses, but also to the
way control must be designed in the .NET Compact Framework.
If you develop a control that inherits from System.Windows.Control and
then attempt to use that control in a Smart Device Application project, you
will find that the control is grayed out in your toolbox. Since the control
is not marked with any designer attributes for the .NET Compact Framework, a
version must be compiled that uses an alternate version of the .NET Compact
Framework libraries. These libraries are only intended to be referenced at
design time. The standard location for these files is C:\Program
Files\Microsoft Visual Studio .NET 2003\CompactFrameworkSDK\v1.0.5000\Windows CE\Designer.
The need to develop dual versions of your control allows you to place
attributes or code in the control that will only be used by Visual Studio
.NET at design time, while still reducing the footprint of your control at
runtime. The issue this presents is how to make sure your runtime version
and design time version are the same without maintaining two code bases.
The answer is to put both the designer and control solutions in the same
library. Visual Studio .NET will not allow you to create two projects in the
same directory. To get around this you will have to do a little work behind
the back of Visual Studio .NET.
First create a new C# Smart Device Application project called
BasicChart. Add a new class file called BasicChart.cs to this project.
BasicChart.cs will be where the core control functionality will be located
later.
After creating the first project, open another copy of Visual Studio
.NET and create a second project called BasicChartDesigner (see Figure 1).
The project will be a C# Windows Control Library project. Now close the
second copy of Visual Studio .NET and copy the files shown in Figure 2 into
the project directory for the BasicChart project.
By copying the solution files into the same directory you will be able
to open the BasicChartDesigner project and, after deleting the UserControl1
file and the existing BasicChart.cs, you will be able to have both projects
share the same code base.
In the BasicChartDesigner you will need to update your references to
reflect the need to support "designer friendly" libraries. Delete the
references to System.XML, System.Data, System.Drawing, and System
.Windows.Forms, and add references to the System.CF.Design, System.CF.Drawing, and System.CF.Windows. Forms libraries from the C:\Program Files\Microsoft Visual Studio
.NET 2003\CompactFrameworkSDK\v1.0.5000\Windows CE\Designer directory.
After you add these references you will need to add a reference to the
System.Windows.Forms library. It is important to add the references in the
correct sequence because the order in which you add them is the same order
in which they are evaluated by the compiler.
With the project files set up and the references in place, you can begin
coding the control and completing the BasicChart class. This control will
use two ArrayList objects to hold the data for the chart (see Listing 1). In
our example our chart will display profits by month.
The chart should display some data or image during design time to help
the user position and size the control. For this introduction the control
randomly generates five months of data to display (see Listing 2). The code is wrapped in a compiler directive so it will only be compiled into the
assembly during design time. By only having the code in the designer we will
be able to reduce the memory requirements and storage size of the component
on the device.
In order for the control to render to the screen you will need to handle
the onPaint event. To design for the extension of this control at a later
time you will put the drawing logic in a separate function. Later, this
control can be expanded to support additional chart types or rendering
engines.
protected override void OnPaint(System.Windows.Forms.
PaintEventArgs e)
{
DrawDemoGraph("CF Graph", e.Graphics);
}
The DrawDemoGraph function will fetch a local copy of the ArrayLists to use for the X and Y axes of the chart. The number of items on the
axes will help determine the size and width of each column.
ArrayList aX = Months;
ArrayList aY = Profits;
int MaxWidth = (ColWidth + ColSpace) * aX.Count + ColSpace;
int MaxColHeight = 0;
int TotalHeight = MaxHeight + XLegendSpace + TitleSpace;
After gathering the height information we will draw out the background
for the chart. Because the Compact Framework supports a wide range of the
System.Drawing namespace you are able to reuse a lot of drawing code you
might already have from other GDI+ applications. This control uses the
FillRectangle function to paint a white background with an ivory background.
objGraphics.FillRectangle(new SolidBrush(Color.White), 0, 0,
MaxWidth, TotalHeight);
objGraphics.FillRectangle(new SolidBrush(Color.Ivory), 0, 0,
MaxWidth, MaxHeight);
The control will compute the relative heights for the columns based on
the values in the ArrayList object and prepare the fonts for the text items.
After this is complete it is time to render the columns of our chart.
Looping through each of the values in the Y axis ArrayList, the control will
compute the height and position for the text string displaying the value
above each column (see Listing 3).
The last thing the control needs to do is render the title at the bottom
of the grid. That is done with a simple call to DrawString. The control uses
the MaxHeight and XLegendSpace settings to control the vertical placement of the title.
objGraphics.DrawString(Title,fontTitle, objBrush, 0,
MaxHeight + XLegendSpace);
After compiling the designer and control project, all that is left is to
test the control. You can do this by creating a new Smart Device Application
project. Once the project is created, customize your toolbox by adding the
BasicChart control. Remember to select the BasicChartDesigner.dll since this
is for design time.
After placing and sizing a BasicChart control on a page, a few lines of
code are needed in the page load event handler to populate the control with
data. Since this is a test project, randomly generated data will be used for
the first five months (see Figures 3 and Listing 4).
Listing 5 contains the complete code for BasicChart.cs should you wish
to publish it.
Conclusion
Thanks to similarities between the .NET Framework and Compact Framework,
developers are able to leverage their extensive knowledge and code bases
when developing for the mobile world. The .NET Compact Framework continues
the Microsoft tradition of allowing developers to build or buy rich
presentation layer components that allow them to encapsulate and reuse
functionality across applications. With a few easy steps you can begin
building your own controls to reuse on your mobile development projects.
About The Author
Brad McCabe is a technology evangelist for .NET, ASP.NET, and .NET CF for
Infragistics (www.infragistics.com), a leader in providing a broad
infrastructure of reusable presentation-layer components essential for the
creation of next-generation Web-based applications and XML Web services utilizing .NET, COM, and Java.
brad@infragistics.com
Listing 1
private ArrayList months = new ArrayList();
private ArrayList profits = new ArrayList();
public ArrayList Months
{
get { return months; }
set { months = value; }
}
public ArrayList Profits
{
get { return profits; }
set { profits = value; }
}
Listing 2
public BasicChart()
{
#if DESIGN
ArrayList _months = new ArrayList();
ArrayList _profits = new ArrayList();
_months.Add("Jan");
_months.Add("Feb");
_months.Add("Mar");
_months.Add("Apr");
_months.Add("May");
Random _random = new Random();
_profits.Add(_random.Next(1000, 9999));
_profits.Add(_random.Next(1000, 9999));
_profits.Add(_random.Next(1000, 9999));
_profits.Add(_random.Next(1000, 9999));
_profits.Add(_random.Next(1000, 9999));
this.Months = _months;
this.Profits = _profits;
#endif
}
Listing 3
CurrentHeight = (Convert.ToDouble(aY[iLoop]) /
Convert.ToDouble(MaxColHeight)) * Convert.ToDouble(MaxHeight - HeightSpace);
objGraphics.FillRectangle(objBrush, BarX, MaxHeight -
Convert.ToInt32(CurrentHeight), ColWidth, Convert.ToInt32(CurrentHeight));
objGraphics.DrawString(aX[iLoop].ToString(), fontLegend, objBrush, BarX,
MaxHeight);
objGraphics.DrawString(String.Format("{0:#,###}", aY[iLoop]), fontValues,
objBrush, BarX, MaxHeight - Convert.ToInt32(CurrentHeight) - 15);
BarX += (ColSpace + ColWidth);
Listing 4
ArrayList _months = new ArrayList();
ArrayList _profits = new ArrayList();
_months.Add("Jan");
_months.Add("Feb");
_months.Add("Mar");
_months.Add("Apr");
_months.Add("May");
Random _random = new Random();
_profits.Add(_random.Next(1000, 9999));
_profits.Add(_random.Next(1000, 9999));
_profits.Add(_random.Next(1000, 9999));
_profits.Add(_random.Next(1000, 9999));
_profits.Add(_random.Next(1000, 9999));
this.basicChart1.Months = _months;
this.basicChart1.Profits = _profits;
Listing 5
using System;
using System.Drawing;
using System.Collections;
using System.Windows.Forms;
using System.Collections.Specialized;
namespace Infragistics
{
public class BasicChart : System.Windows.Forms .Control
{
// An arraylist containing all of the datapoints
private ArrayList months = new ArrayList();
private ArrayList profits = new ArrayList();
public BasicChart()
{
#if DESIGN
ArrayList _months = new ArrayList();
ArrayList _profits = new ArrayList();
_months.Add("Jan");
_months.Add("Feb");
_months.Add("Mar");
_months.Add("Apr");
_months.Add("May");
Random _random = new Random();
_profits.Add(_random.Next(1000, 9999));
_profits.Add(_random.Next(1000, 9999));
_profits.Add(_random.Next(1000, 9999));
_profits.Add(_random.Next(1000, 9999));
_profits.Add(_random.Next(1000, 9999));
this.Months = _months;
this.Profits = _profits;
#endif
}
public ArrayList Months
{
get { return months; }
set { months = value; }
}
public ArrayList Profits
{
get { return profits; }
set { profits = value; }
}
public void DrawDemoGraph(string Title, Graphics objGraphics)
{
const int ColWidth = 25;
const int ColSpace = 5;
const int MaxHeight = 200;
const int HeightSpace = 20;
const int XLegendSpace = 30;
const int TitleSpace = 20;
ArrayList aX = Months;
ArrayList aY = Profits;
int MaxWidth = (ColWidth + ColSpace) * aX.Count + ColSpace;
int MaxColHeight = 0;
int TotalHeight = MaxHeight + XLegendSpace + TitleSpace;
objGraphics.FillRectangle(new SolidBrush(Color.White), 0, 0,
MaxWidth, TotalHeight);
objGraphics.FillRectangle(new SolidBrush(Color.Ivory), 0, 0,
MaxWidth, MaxHeight);
// find the maximum value
for (int iValue = 0; iValue < aY.Count; iValue++)
{
if (Convert.ToInt32(aY[iValue]) > MaxColHeight)
MaxColHeight = Convert.ToInt32(aY[iValue]);
}
int BarX = ColSpace;
double CurrentHeight;
SolidBrush objBrush = new SolidBrush(Color.Navy);
Font fontLegend = new Font("Arial", 11, FontStyle.Regular);
Font fontValues = new Font("Arial", 8, FontStyle.Regular);
Font fontTitle = new Font("Arial", 12, FontStyle.Regular);
// loop through and draw each bar
for (int iLoop = 0; iLoop < aX.Count; iLoop++)
{
CurrentHeight = (Convert.ToDouble(aY[iLoop]) /
Convert.ToDouble(MaxColHeight)) * Convert.ToDouble(MaxHeight - HeightSpace);
objGraphics.FillRectangle(objBrush, BarX, MaxHeight -
Convert.ToInt32(CurrentHeight), ColWidth, Convert.ToInt32(CurrentHeight));
objGraphics.DrawString(aX[iLoop].ToString(), fontLegend,
objBrush, BarX, MaxHeight);
objGraphics.DrawString(String.Format("{0:#,###}",
aY[iLoop]), fontValues, objBrush, BarX, MaxHeight -
Convert.ToInt32(CurrentHeight) - 15);
BarX += (ColSpace + ColWidth);
}
objGraphics.DrawString(Title, fontTitle, objBrush, 0, MaxHeight
+ XLegendSpace);
}
protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
{
DrawDemoGraph("CF Graph", e.Graphics);
}
}
}
All Rights Reserved
Copyright © 2004 SYS-CON Media, Inc.
E-mail:
info@sys-con.com