Click here to Skip to main content
15,884,099 members
Articles / Programming Languages / C#

A Curve Custom Control for Windows Mobile

Rate me:
Please Sign up or sign in to vote.
4.33/5 (3 votes)
31 Mar 2010CPOL5 min read 18.6K   213   12  
This article describes/provides a 2D Curve Custom Control for Windows Mobile.

Introduction

In most of the projects related to developing Windows Mobile applications, 99% there will be the need for developing at least one custom control to provide a really good user experience and for presenting results in such a way that the standard controls are never enough for fulfilling it.

During a Windows Mobile project that I was involved in, I was faced with the need to present to the user 2D Curves detailing some statistics that the application is supposed to collect. Hence, the emergence of the need to create a customizable Curve control. I supposed that for sure other developers will face such a problem/need. So, I decided to present through this article the concept of custom controls and the Curve custom control that I'm providing.

Background

For those of you who are new to controls development in .NET, there used to be a big confusion between what a User Control is and what a Custom Control is.

A User Control is basically a UI component, which is made of other controls which can also be User Controls, and that has a design-time experience very similar to the Form’s one. A User Control is built based on composition. Its goal is helping you to reuse a set of UI elements on more than one Form providing a consistent look & feel and usability from both the development and user experience point of view.

In contrast, a Custom Control is a single control. A .NET control which is developed from scratch. It’s basically a class that inherits from System.Windows.Forms.Control and overrides some of the Control’s methods, like OnPaint, to provide the expected user experience.

While a User Control is easier to develop and support, a Custom Control is lighter and faster at runtime, and it gives you the highest flexibility you can get in a .NET control because you are free to draw whatever you want, even animations, and to handle input messages like keyboard and touch events.

Using the Code

As attachments, I provide both the Curve custom control as well as the demo Visual Studio 2008 projects. You will be able to: either use directly the Curve control as I'm providing it or you can even customize it, re-compile it and use yours.

So, in the following paragraphs, I'll detail both the steps to follow in order to directly plug and use the Curve control within your project and the steps to follow in order to customize the Curve control and use yours.

Using the Curve Custom Control

First of all, you have to download the CurveControl project attached to this article. Once done, you have to add the Curve control to your project. You can do that by right clicking on your project toolbox and clicking on the "Choose Items ..." (see picture below). Then, you browse and add the "CurveControl.dll" file available within the release directory of "CurveControl" Visual Studio 2008 attached to this article.

Then, you will be able to drop the Curve control from the toolbox to your project Form.

Once done, you will need to edit the Curve Paint event in order to customize it and to supply it with the list of Point objects that you want to plot. The following code snippet describes an example where I start by creating the list of point(s) to plot on the 2D Curve, then I setup the titles of the X and Y axis(s) as well as the colors and font(s) associated to these titles.

Note that through this Curve control, it is also possible to configure it in such a way that it plots the 2D Curve as well as the coordinates' values of all the points. You just need to make a call to the ShowPointCoordinates(true) method. It is also possible to easily customize the font and color to be used for plotting the coordinates' values.

C#
// The method that will handle the curve1 Paint event.
private void curve1_Paint(object sender, PaintEventArgs e)
{
	// Setting up the list of Point(s) that will be plotted
	// You can also supply an already available list. There is no need to
	// edit the list each time the Paint event is fired.
	// You can fill the list with either sorted values or not.
	List<Point> values = new List<Point>();
	values.Add(new Point(20, 30));
	values.Add(new Point(100, 70));
	values.Add(new Point(40, 50));
	values.Add(new Point(150, 40));
	values.Add(new Point(70, 20));
	// Setting the curve1 object list of values.
	curve1.SetValuesList(values);

	// Setting the title of the curve1 X axis.
	curve1.SetXAxisTitle("Time(s)");
	// Setting the title of the curve1 Y axis.
	curve1.SetYAxisTitle("Avg Rate");
	// Choosing to show the coordinates of each plotted Point.
	curve1.ShowPointCoordinates(true);
	// Choosing to sort the list of Point(s) before plotting them.
	curve1.SetSorting(true);
	// Setting the curve Background color.
	curve1.SetBackgroundColor(Color.White);
	// Setting the curve1 X and Y axis color.
	curve1.SetXYAxisColor(Color.Black);
	// Setting the color of the curve1 X and Y axis titles. 
	curve1.SetTitlesColor(Color.Red);
	// Setting the Font of the curve1 X and Y axis titles.
	curve1.SetXYTitlesFont(new Font("Calibri", 8, FontStyle.Bold));
	// Setting coordinates color.
	curve1.SetCoordinatesColor(Color.Blue);
	// Setting the coordinates font.	
	curve1.SetCoordinatesFont(new Font("Calibri", 8, FontStyle.Bold));
}

Customizing the Curve Control

Below, I've tried to comment as much as needed the source code of the Curve control. So, you will be able to easily customize it. As I've explained later, any control that you would start from scratch should inherit from the Control class and override the needed methods. In the case of the Curve control, I've overridden the OnPaint method within which I make a call to the PlotCurve method. As described in the following code snippet, the later method takes in charge resizing then plotting the different components of the Curve. Namely, plotting the axis(s) and their titles by calling the PlotXYAxis method. Then, the PlotCurve method makes a call to the AdaptValuesToXYAxisLenght method in order to adjust the coordinates of the Point(s) to plot according to the current Curve control size. Once done, the PlotCurve method proceeds to plotting the different Point(s) as well as their coordinates if asked. It is also important to not forget to implement the method void Curve_Resize(object sender, EventArgs e) in order to catch the Resize event. Indeed, Once the Resize event is fired, either on runtime or during the design time, we need to call back the PlotCurve method which will take into consideration the new Curve control size and re-size its components.

Note also that the following code snippet provides all the comments and details related to the methods that you can use to customize the final appearance of the Curve. Namely, the components’ fonts and colors. For example, I provided the methods SetXAxisTitle, SetYAxisTitle, SetXYAxisColor, SetTitlesColor and SetXYTitlesFont for editing the axis(s) titles, their font and colors. However, the positioning of the titles is done implicitly according to the curve width and height. It is also possible to change the background color of the Curve using the method SetBackgroundColor.

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

namespace CurveControl
{
	// As I explained later while defining Custom Controls, 
         // the Curve one should inherit from the Control class and override
	// the needed methods
    public partial class Curve : Control
    {
	// List of values
	private List<Point> valuesList;
	private List<Point> convertedList;
	// X Axis title
	private string xAxisTitle = "X Title";
	// Y Axis Title
	private string yAxisTitle = "Y Title";
	// X position of the curve
	private int curveXPosition = 0;
	// Y position of the curve
	private int curveYPosition = 0;
	// Curve width
	private int curveWidth = 0;
	// Curve height
	private int curveHeight = 0;
	// Main curve coordiantes
	private int xOrigin = 0;
	private int yOrigin = 0;
	// Text height
	private int xAxisTitleHeight = 0;
	private int yAxisTitleWidth = 0;
	private int yAxisTitleHeight = 0;
	// Specifies either to show or not points coordinates
	private bool showCoordinates;
	private Graphics g = null;
	private bool sorting;
	Font xyTitlesFont = null;
	Font coordinatesFont = null;
	Color xyTitlesColor = Color.Red;
	Color coordinatesColor = Color.Blue;
	Color xyAxisColor = Color.Black;
	// Constructor
        public Curve()
        {
            valuesList = new List<Point>();
            convertedList = new List<Point>();
            showCoordinates = false;
            sorting = true;
            xyTitlesFont = new Font("Calibri", 8, FontStyle.Bold);
            coordinatesFont = new Font("Calibri", 8, FontStyle.Bold);
            InitializeComponent();
        }

	// Calling the CreateGraphics method which is inherited from the 
	// Control class in order to create the Graphics object which we'll use 
	// for plotting the different parts of the curve
        private Graphics GetGraphics
        {
            get
            {
                if (g == null)
                    g = CreateGraphics();
                return g;
            }
        }

        /// Setting the title of the X Axis.
        public void SetXAxisTitle(string title)
        {
            xAxisTitle = title;
        }

        /// Specifies either to show or not point' coordinates within the curve.
        public void ShowPointCoordinates(bool show)
        {
            showCoordinates = show;
        }

        /// Specifies either to sort or not the list of points before plotting. 
        public void SetSorting(bool value)
        {
            sorting = value;
        }

        /// Setting the title of the Y Axis.
        public void SetYAxisTitle(string title)
        {
            yAxisTitle = title;
        }

        /// Sets the XY titles font.
        public void SetXYTitlesFont(Font f)
        {
            xyTitlesFont = f;
        }


        /// Setting the axis color.
        public void SetXYAxisColor(Color c)
        {
            xyAxisColor = c;
        }

        /// Setting the XY Titles color.
        public void SetTitlesColor(Color c)
        {
            xyTitlesColor = c;
        }


        /// Setting the coordinates color.
        public void SetCoordinatesColor(Color c)
        {
            coordinatesColor = c;
        }

        /// Setting the coordinates font.
        public void SetCoordinatesFont(Font f)
        {
            coordinatesFont = f;
        }
        
	/// Setting the list of points which will be plotted.
        public void SetValuesList(List<Point> values)
        {
            valuesList = values;
        }

        /// Returns the max x value with respect to the X Axis from a 
        /// given list of points.
        private int GetMaxX(ref List<Point> list)
        {
            int max = 0;
            foreach (Point p in list)
            {
                if (p.X > max)
                    max = p.X;
            }
            return max;
        }

        /// Returns the max y value with respect to the Y Axis from a 
        /// given list of points.
        int GetMaxY(ref List<Point> list)
        {
            int max = 0;
            foreach (Point p in list)
            {
                if (p.Y > max)
                    max = p.Y;
            }
            return max;
        }

        /// A private method which enable scaling the list of Points' 
        /// coordinates according to the real length of the X 			
        /// Axis.
        private void AdaptValuesToXYAxisLenght(int xAxisLength, int yAxisLength)
        {
            int maxX = GetMaxX(ref valuesList);
            int maxY = GetMaxY(ref valuesList);

            foreach (Point p in valuesList)
            {
                convertedList.Add(new Point((p.X * xAxisLength) / 
				maxX, (p.Y * yAxisLength) / maxY));
            }
        }

        /// Compares two points according to the X Axis
        private int ComparePoints(Point a, Point b)
        {
            if (a.X > b.X)
                return -1;
            else if (a.X < a.Y) return 1;
            else return 0;
        }

        /// Setting the background color for the curve.
        public void SetBackgroundColor(Color color)
        {
            // Changing bitmap color
            Rectangle rect = new Rectangle(0, 0, 
		curveWidth - curveXPosition, curveHeight - curveYPosition);
            GetGraphics.FillRectangle(new SolidBrush(color), rect);

        }

        /// Drawing the X and Y Axis and their Titles.
        private void PlotXYAxis(ref int xAxisLenght, ref int yAxisLenght)
        {
	// Drawing the X Axis
            GetGraphics.DrawLine(new Pen(xyAxisColor, 4), 
		xOrigin, yOrigin, xOrigin+xAxisLenght, yOrigin);
            GetGraphics.DrawLine(new Pen(xyAxisColor, 2), 
		xOrigin + xAxisLenght, yOrigin, xOrigin + xAxisLenght - 6, yOrigin
			 + 6 + 2);
            GetGraphics.DrawLine(new Pen(xyAxisColor, 2), 
		xOrigin + xAxisLenght, yOrigin, xOrigin + xAxisLenght - 6, yOrigin
			 - 6 - 2);

            // Drawing the Y Axis
            GetGraphics.DrawLine(new Pen(xyAxisColor, 4), 
		xOrigin, yOrigin, xOrigin, yOrigin - yAxisLenght);
            GetGraphics.DrawLine(new Pen(xyAxisColor, 2), 
		xOrigin, yOrigin - yAxisLenght - 2, xOrigin + 6, yOrigin - 
			yAxisLenght + 6 - 2);
            GetGraphics.DrawLine(new Pen(xyAxisColor, 2), 
		xOrigin, yOrigin - yAxisLenght - 2, xOrigin - 6, yOrigin - 
			yAxisLenght + 6 - 2);

            // Drawing X and Y axis titles
            GetGraphics.DrawString(xAxisTitle, 
		xyTitlesFont, new SolidBrush(xyTitlesColor), (curveWidth - 
			xAxisTitle.Length)/ 2, yOrigin);
            GetGraphics.DrawString(yAxisTitle, xyTitlesFont, 
		new SolidBrush(xyTitlesColor), curveXPosition, curveYPosition -
			yAxisTitleHeight - yAxisTitleHeight/2);
        }

        /// The main method which enable us to plot the curve axis, labels and points.
        public void PlotCurve()
        {
            // Scaling
            this.curveXPosition = this.Location.X;
            this.curveYPosition = this.Location.Y;
            this.curveWidth = this.Size.Width;
            this.curveHeight = this.Size.Height;
            xOrigin = curveXPosition + yAxisTitleWidth / 2;
            yOrigin = curveHeight - xAxisTitleHeight;

            yAxisTitleWidth = (int)Math.Floor((double)
		(GetGraphics.MeasureString(xAxisTitle, xyTitlesFont).Width));
            xAxisTitleHeight = (int)Math.Floor((double)
		(GetGraphics.MeasureString(xAxisTitle, xyTitlesFont).Height));
            yAxisTitleHeight = (int)Math.Floor((double)
		(GetGraphics.MeasureString(yAxisTitle, xyTitlesFont).Height));
            // Real length of the X Axis
            int xAxisLength = curveWidth - xOrigin;
            // Real length of the Y Axis
            int yAxisLength = yOrigin - curveYPosition;

            PlotXYAxis(ref xAxisLength, ref yAxisLength);

            // Adapting the real points coordinates to the real axis lengths
            this.AdaptValuesToXYAxisLenght(xAxisLength, yAxisLength);

            int maxX = GetMaxX(ref convertedList);
            int maxY = GetMaxY(ref convertedList);

            // Drawing the curve
            if (convertedList.Count > 0)
            {
                // Sort the points to plot according to the X Axis
                if (sorting)
                {
                    convertedList.Sort(new Comparison<Point>(ComparePoints));
                }

                Point last = convertedList[0];
                // indicates the direction of the curve parts, true = up otherwise down
                bool direction = false;
                int xHeight = (int)Math.Floor((double)
		(GetGraphics.MeasureString("X",coordinatesFont).Height));
                int yWidth = (int)Math.Floor((double)
		(GetGraphics.MeasureString("X", coordinatesFont).Width));
                foreach (Point p in convertedList)
                {
                    if (last != p)
                    {
                        // Drawing a line from the last point to the current one 
                        GetGraphics.DrawLine(new Pen(Color.Black, 2), 
			xOrigin + last.X, yOrigin - last.Y, xOrigin + p.X, 
			yOrigin - p.Y);
                        if (p.Y > last.Y)
                            direction = true;
                        else direction = false;
                    }
                    // Drawing the current point
                    GetGraphics.DrawString("X", coordinatesFont, 
			new SolidBrush(coordinatesColor), xOrigin + p.X - yWidth / 
			2, yOrigin - p.Y - xHeight/2);
                    if (showCoordinates)
                    {
                        // Coordinates string
                        string coordinates = "x: " + p.X.ToString() + 
					"\n" + "y: " + p.Y.ToString();

                        int coordinatesHeight = (int)Math.Floor((double)
				(GetGraphics.MeasureString(coordinates, 
				coordinatesFont).Height));
                        int coordinatesWidth = (int)Math.Floor((double)
				(GetGraphics.MeasureString(coordinates, 
				coordinatesFont).Width));

                        if (direction)
                        {
                            if (p.X == maxX )
                            {
                                GetGraphics.DrawString(coordinates, 
				coordinatesFont, new SolidBrush(coordinatesColor), 
				xOrigin + p.X - coordinatesWidth, 
				yOrigin - p.Y - coordinatesHeight / 2 - xHeight / 2);
                            }
                            else if (p.Y == maxY)
                            {
                                GetGraphics.DrawString(coordinates, 
				coordinatesFont, new SolidBrush(coordinatesColor), 
				xOrigin + p.X - coordinatesWidth - 
				coordinatesWidth/2, yOrigin - p.Y - 
				coordinatesHeight / 2);
                            }
                            else
                            {
                                GetGraphics.DrawString(coordinates, 
				coordinatesFont, new SolidBrush(coordinatesColor), 
				xOrigin + p.X - coordinatesWidth / 2, 
				yOrigin - p.Y - coordinatesHeight - xHeight / 2);
                            }
                        }
                        else
                        {
                            if (p.X == maxX )
                            {
                                GetGraphics.DrawString(coordinates, 
				coordinatesFont, new SolidBrush(coordinatesColor), 
				xOrigin + p.X - coordinatesWidth , 
				yOrigin - p.Y + xHeight / 2);
                            
                            }
                            else
                            {
                                GetGraphics.DrawString(coordinates, 
				coordinatesFont, new SolidBrush(coordinatesColor), 
				xOrigin + p.X - coordinatesWidth / 2, 
				yOrigin - p.Y + xHeight / 2);
                            }
                        }
                    }
                    last = p;
                }
            }            
        }
 
        protected override void OnPaint(PaintEventArgs pe)
        {
            // Call the OnPaint method of the base class.
            base.OnPaint(pe);
            PlotCurve();
        }
	// If the control is resized, then resize and 
	// re-plot the Curve components.
        private void Curve_Resize(object sender, EventArgs e)
        {
            PlotCurve();
        }
    }
}

Depending on your needs and the semantic of the curve you want to plot, you can choose either to enable Curve control to implicitly sort the list of Point(s) according to the X axis or not. You can do that using the SetSorting method. And finally, I've introduced some controls within the PlotCurve method in order to take into account the direction of the curve when plotting the Point(s)' coordinates. Indeed, the Curve control changes the position of the coordinates to plot depending on the future curve direction in order to avoid that curve hides the coordinates.

History

  • First version: March 2010

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Alcmeon
France France
http://fr.linkedin.com/in/amirkrifa

Comments and Discussions

 
-- There are no messages in this forum --