Windows Mobile 的曲线自定义控件






4.33/5 (3投票s)
本文介绍/提供一个适用于 Windows Mobile 的二维曲线自定义控件。

引言
在大多数与开发 Windows Mobile 应用程序相关的项目中,99% 的情况下都会有开发至少一个自定义控件的需求,以提供出色的用户体验,并以标准控件无法满足的方式呈现结果。
在我参与的一个 Windows Mobile 项目中,我面临着需要向用户展示二维曲线以详细说明应用程序应收集的某些统计数据的需求。因此,出现了创建可自定义的 Curve
控件的必要性。我想其他开发者肯定也会遇到类似的问题/需求。因此,我决定通过本文介绍自定义控件的概念以及我提供的 Curve
自定义控件。
背景
对于那些不熟悉 .NET 中控件开发的人来说,用户控件和自定义控件之间一直存在很大的混淆。
用户控件基本上是一个 UI 组件,它由其他控件(也可以是用户控件)组成,并且在设计时具有与窗体非常相似的体验。用户控件是基于组合构建的。它的目标是帮助您在多个窗体上重用一组 UI 元素,从而在开发和用户体验方面提供一致的外观和感觉以及可用性。
相比之下,自定义控件是单个控件。它是从头开始开发的 .NET 控件。它基本上是一个继承自 System.Windows.Forms.Control
并重写 Control
的一些方法的类,例如 OnPaint
,以提供预期的用户体验。
虽然用户控件更容易开发和支持,但自定义控件在运行时更轻便、更快速,并且为您提供了 .NET 控件中可能获得的最高灵活性,因为您可以自由绘制任何您想要的内容,包括动画,并处理键盘和触摸事件等输入消息。
Using the Code
作为附件,我提供了 Curve
自定义控件以及演示 Visual Studio 2008 项目。您将能够:直接使用我提供的 Curve
控件,或者甚至可以自定义它,重新编译并使用您自己的控件。
因此,在接下来的段落中,我将详细介绍直接将 Curve
控件集成到您的项目中以及如何自定义 Curve
控件并使用您自己的控件的步骤。
使用曲线自定义控件
首先,您需要下载本文附件中的 CurveControl
项目。完成后,您需要将 Curve
控件添加到您的项目中。您可以通过右键单击项目工具箱并单击“选择项...”来完成此操作(见下图)。然后,浏览并添加本文附加的“CurveControl
” Visual Studio 2008 的 release 目录中找到的“CurveControl.dll”文件。

然后,您将能够从工具箱将 Curve
控件拖放到项目的 Form
中。

完成后,您需要编辑 Curve
的 Paint
事件以对其进行自定义,并为其提供要绘制的 Point
对象列表。以下代码片段描述了一个示例,其中我首先创建要绘制在二维 Curve
上的点列表,然后设置 X 和 Y 轴的标题以及与这些标题相关的颜色和字体。
请注意,通过此 Curve
控件,还可以将其配置为绘制二维 Curve
以及所有点的坐标值。您只需调用 ShowPointCoordinates(true)
方法即可。还可以轻松自定义用于绘制坐标值的字体和颜色。
// 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));
}
自定义曲线控件
在下面,我已尽可能地注释了 Curve
控件的源代码。因此,您可以轻松地对其进行自定义。正如我稍后解释的,任何您从头开始创建的控件都应该继承自 Control
类并重写所需的方法。对于 Curve
控件,我重写了 OnPaint
方法,在该方法中我调用了 PlotCurve
方法。如以下代码片段所述,后者负责调整大小然后绘制 Curve
的不同组件。即,通过调用 PlotXYAxis
方法绘制轴及其标题。然后,PlotCurve
方法调用 AdaptValuesToXYAxisLenght
方法,以便根据当前的 Curve
控件大小调整要绘制的点坐标。完成后,PlotCurve
方法会继续绘制不同的点及其坐标(如果需要)。同样重要的是不要忘记实现 void Curve_Resize(object sender, EventArgs e)
方法以捕获 Resize
事件。确实,一旦 Resize
事件被触发,无论是在运行时还是在设计时,我们都需要回调用 PlotCurve
方法,该方法将考虑新的 Curve
控件大小并重新调整其组件的大小。
另请注意,以下代码片段提供了与您可以用来自定义 Curve
最终外观的方法相关的所有注释和详细信息。即,组件的字体和颜色。例如,我提供了 SetXAxisTitle
、SetYAxisTitle
、SetXYAxisColor
、SetTitlesColor
和 SetXYTitlesFont
方法来编辑轴标题、它们的字体和颜色。但是,标题的定位是根据曲线的宽度和高度隐式完成的。还可以使用 SetBackgroundColor
方法更改 Curve
的背景颜色。
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();
}
}
}
根据您的需求以及您要绘制的曲线的语义,您可以选择启用 Curve
控件自动根据 X 轴对点列表进行排序,或者不启用。您可以使用 SetSorting
方法来实现这一点。最后,我在 PlotCurve
方法中添加了一些控件,以考虑曲线方向来绘制点的坐标。确实,Curve
控件会根据曲线的未来方向更改要绘制的坐标的位置,以避免曲线隐藏坐标。
历史
- 首个版本:2010 年 3 月