有了前面的坐标相关知识的了解,我们这次来实战一把,绘制一个表盘。当然,绘制表盘我们要学会GDI+绘图中的路径绘图与两个必要的Transform变形函数,RotateTransform(旋转变形)、TranslateTransform(移动变形)。我们先来看看效果:
 
一、路径
1、GraphicsPath介绍
什么是图形路径呢?用官方的话来说就是“表示一系列相互连接的直线和曲线”,为什么会有这么一个类呢?很显然,以前我们学习的图形类,要么是圆,要么是直线,这个类显然就是要把他们都结合在一起。这个类的使命决定它的用法是比较丰富的,我们可以具体参看官网这里我们来介绍几个比较常用的方法,满足我们后面的绘图需要即可。
2、初识GraphicsPath与基本元素
绘制一个GraphicPath最基本的条件是要拥有绘制的元素,所以GDI+为GraphicPath提供了一大堆的添加元素的方法,都是以Addxxx命名的函数(如:AddArc,AddBezier,AddCuve,AddEllipse,AddLine,AddRectangle,AddString等及他们对应的复数形式方法和AddPie,AddPath,AddPloygon)。他们的用法都很简单,这里举例看看效果(我们准备了一个干净的窗体):
private void drawPath(Graphics gp)
{
    
    Point[] myArray =
             {
         new Point(30,30),
         new Point(60,60),
         new Point(0,60),
         new Point(30,30)
     };
    GraphicsPath myPath = new GraphicsPath();
    myPath.AddLines(myArray);
    Point[] myArray2 =
             {
         new Point(30,30),
         new Point(0,0),
         new Point(60,0),
         new Point(30,30)
     };
    GraphicsPath myPath2 = new GraphicsPath();
    myPath2.AddLines(myArray2);
    myPath.AddPath(myPath2, true);
    gp.DrawPath(new Pen(Color.Black, 4), myPath);
}

2、StartFigure
StartFigure的作用是不闭合当前图形即开始一个新图形。 后面添加到该路径的所有点都被添加到此新图形中。
 一般我们使用StartFigure绘制图形路径基本就三个步骤:
 1、实例化一个GraphicPath
 2、利用实例的StartFigure方法开启一个形状
 3、利用各种Addxxx函数增加图形元素
 4、利用实例的StartFigure方法结束一个形状
 5、在至少拥有一个Figure后,调用draw函数绘制出来即可。
 来一个实例:
 private void drawPath(Graphics gp)
 {
     // Create a GraphicsPath object.
     GraphicsPath myPath = new GraphicsPath();
     // First set of figures.
     myPath.StartFigure();
     myPath.AddArc(10, 10, 50, 50, 0, 270);
     myPath.AddLine(new Point(50, 0), new Point(100, 50));
     myPath.AddArc(50, 100, 75, 75, 0, 270);
     myPath.CloseFigure();
     myPath.StartFigure();
     myPath.AddArc(100, 10, 50, 50, 0, 270);
     // Second set of figures.
     myPath.StartFigure();
     myPath.AddArc(10, 200, 50, 50, 0, 270);
     myPath.CloseFigure();
     myPath.StartFigure();
     myPath.AddLine(new Point(60, 200), new Point(110, 250));
     myPath.AddArc(50, 300, 75, 75, 0, 270);
     myPath.CloseFigure();
     myPath.StartFigure();
     myPath.AddArc(100, 200, 50, 50, 0, 270);
     gp.DrawPath(new Pen(Color.Black, 4), myPath);
 }
效果如下:
 
二、变形函数
1、TranslateTransform与RotateTransform
这两个方法存在与Pen、Graphics、TextureBrush、PathGradientBrush、LinearGradientBrush中,当然最常见的还是Graphics中的变形,所以这里我们也将以它为例来演示这个函数的用法。
 
 gp.ResetTransform();
 Image newImage = Image.FromFile("csharp.jpg");
 Point ulCorner = new Point(0, 0);
 gp.TranslateTransform(240, 60);
 gp.ScaleTransform(0.3f, 0.3f);
 gp.RotateTransform(30.0f);
 gp.DrawImage(newImage, ulCorner);
代码一开始,我们重置了图形的变形ResetTransform,以防前面的变形对我们此次变形产生影响。
 
2、ScaleTransform
我们常用的除了上述两个函数,上面我们还是用了一个ScaleTransform函数,很简单一看就会它专用与缩放,参数接受的是一个浮点型。
三、表盘绘制
1、圆盘绘制
  graphics.FillEllipse(new SolidBrush(Color.Black), getCircleRect(centerPoint, 120));
  graphics.DrawArc(p, getCircleRect(centerPoint,outlineRadius), mapStartRadias, 360);//绘制一个由clock边框定义的椭圆

2、指针绘制
Point sNeedlePoint = new Point((int)(centerPoint.X + longNeedleLength * Math.Sin(angel_s)), (int)(centerPoint.Y - longNeedleLength * Math.Cos(angel_s)));    
graphics.DrawLine(new Pen(Color.Red, 2), centerPoint, sNeedlePoint);
//绘制指针圆柄
graphics.FillEllipse(new SolidBrush(Color.Yellow), getCircleRect(centerPoint, 6));

3、着色知识
1、渐变
关于渐变,如果我们有耐心,可以直接参考官网的说明,为了我们完成目标,我们选择最常用的两种渐变来熟悉一下他们的用法。
(1)、PathGradientBrush
1、初始化PathGradientBrush
 2、设定CenterColor
 3、设定SurroundColors
 4、利用draw绘制渐变
 
GraphicsPath gp = new GraphicsPath();
gp.AddEllipse(centerPoint.X,centPoint.Y,100,50);
Color[] surroundColor = new Color[] { Color.White};
PathGradientBrush pb = new PathGradientBrush(gp);
pb.CenterColor = Color.Blue;
pb.SurroundColors = surroundColor;
graphics.FillPath(pb, gp);
(2)、LinearGradientBrush
我们首先来看看它的构造函数:
public LinearGradientBrush(Point point1, Point point2, Color color1, Color color2);
public LinearGradientBrush(PointF point1, PointF point2, Color color1, Color color2);
public LinearGradientBrush(Rectangle rect, Color color1, Color color2, LinearGradientMode linearGradientMode);
public LinearGradientBrush(Rectangle rect, Color color1, Color color2, float angle);
public LinearGradientBrush(RectangleF rect, Color color1, Color color2, LinearGradientMode linearGradientMode);
public LinearGradientBrush(RectangleF rect, Color color1, Color color2, float angle);
public LinearGradientBrush(Rectangle rect, Color color1, Color color2, float angle, bool isAngleScaleable);
public LinearGradientBrush(RectangleF rect, Color color1, Color color2, float angle, bool isAngleScaleable);
很显然,这个类的构造函数决定了我们一般有四种方式来使用它;
 1、初始化LinearGradientBrush时设定好渐变的起点和终点及对应的两种颜色
 2、初始化LinearGradientBrush时设定好的矩形、对应的两种颜色及渐变角度
 3、初始化LinearGradientBrush时设定好的矩形、对应的两种颜色及渐变方向(本质同第二种,只是把典型的角度做成了枚举LinearGradientMode )
 4、初始化LinearGradientBrush时设定好的矩形、对应的两种颜色、渐变角度及缩放
所以,我们列举了典型了应用,代码如下:
 private void drawPath(Graphics gp)
 {
	LinearGradientBrush linGrBrush = new LinearGradientBrush(
	new Point(0, 10),
    new Point(220, 10),
	Color.FromArgb(255, 255, 0, 0),   // Opaque red
	Color.FromArgb(255, 0, 0, 255));  // Opaque blue
	
	Pen pen = new Pen(linGrBrush);
	
	gp.DrawLine(pen, 200, 10, 500, 10);
	gp.FillEllipse(linGrBrush, 240, 20, 200, 100);
	gp.FillRectangle(linGrBrush, 0, 155, 500, 30);
	
	
	Point pt=new Point(0, 0);
	Brush b = new LinearGradientBrush(button1.ClientRectangle, Color.Green, Color.Honeydew, 75);//最后一个参数渐变颜色方向或角度,button1窗体中有一个隐藏的按钮
	gp.FillRectangle(b, button1.ClientRectangle);
 }

这里尤其要注意,渐变的起点和终点,比如上图的椭圆,起点x=240,长200,颜色的起点一点要能够覆盖这段长度,那么颜色的起点x=230,终点x=450,可算是万无一失。我们看下面的矩形渐变就对渐变起点终点一目了然了。
4、 指针着色
Point sNeedlePoint = new Point((int)(centerPoint.X + longNeedleLength * Math.Sin(angel_s)), (int)(centerPoint.Y - longNeedleLength * Math.Cos(angel_s)));  
GraphicsPath spath1 = new GraphicsPath();
spath.AddArc(getCircleRect(centerPoint, 6), 0, 360);
Point[] points = { centerPoint, new Point((int)(centerPoint.X + 12 * Math.Sin(angel_s - Math.PI / 6)), (int)(centerPoint.Y - 12 * Math.Cos(angel_s - Math.PI / 6))), sNeedlePoint, new Point((int)(centerPoint.X + 12 * Math.Sin(angel_s + Math.PI / 6)), (int)(centerPoint.Y - 12 * Math.Cos(angel_s + Math.PI / 6))) };
GraphicsPath spath2 = new GraphicsPath();
spath2.AddLines(points);
graphics.FillPath(new SolidBrush(Color.Yellow), spath2);
graphics.FillPath(new SolidBrush(Color.Yellow) , spath1);

5、表盘着色
表盘着色,我们采用的就是fill系列的函数,如FillPath结合渐变,FillEllipse等。所以我们前面的基础要掌握。
g.FillEllipse(new SolidBrush(Color.DarkSlateGray), getCircleRect(centerPoint, 152));
g.FillEllipse(new SolidBrush(Color.Black), getCircleRect(centerPoint, 120));
 g.FillEllipse(new SolidBrush(Color.FromArgb(50, 100, 100, 255)), getCircleRect(centerPoint, 96));
GraphicsPath gp = new GraphicsPath();
gp.AddArc(getCircleRect(centerPoint, 76), 0, 360);
Color[] surroundColor = new Color[] { Color.FromArgb(100, 20, 20, 200)};
PathGradientBrush pb = new PathGradientBrush(gp);
pb.CenterColor = Color.FromArgb(60, 10, 10, 20);
pb.SurroundColors = surroundColor;
g.FillPath(pb, gp);
g.FillEllipse(new SolidBrush(Color.FromArgb(10, 10, 40)), getCircleRect(centerPoint, 50));
g.DrawArc(p, getCircleRect(centerPoint,outlineRadius), mapStartRadias, 360);//绘制边框圆

6、增加刻度和文字
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
Pen p = new Pen(Color.Black, 3);
Pen k = new Pen(Color.White, 2);
GraphicsPath spath = new GraphicsPath();
 spath.StartFigure();
for (int i = 0; i <= fullGauge / bigUnit; i++)//时刻标度0-12, 30度一格
{
    double angel = i * (Math.PI) / 6;
    Point xy1 = new Point((int)(outlineRadiusOffset * Math.Cos(angelStart + angel) + centerPoint.X), (int)(outlineRadiusOffset * Math.Sin(angelStart + angel) + centerPoint.Y));//圆心定义为(centerPoint.X,centerPoint.Y)
    Point xy2 = new Point((int)(inlineRadiusOffset * Math.Cos(angelStart + angel) + centerPoint.X), (int)(inlineRadiusOffset * Math.Sin(angelStart + angel) + centerPoint.Y));
    Point xystr = new Point((int)(strRadius * Math.Cos(angelStart + angel) + centerPoint.X), (int)(strRadius * Math.Sin(angelStart + angel) + centerPoint.Y));
    g.DrawLine(p, xy1, xy2);
    //刻度文字
    String str = ((i) * 10).ToString();
    SizeF sizeg = g.MeasureString(str, new Font("宋体", 9));
    if ((angelStart + angel) >= Math.PI * 3 / 2)
    {
        if ((angelStart + angel) == Math.PI * 3 / 2)
            g.DrawString(str, new Font("宋体", 9), new SolidBrush(k.Color), new Point(xystr.X - 10, xystr.Y - (int)((sizeg.Height / 3) * Math.Cos(angelStart + angel))));
        else
            g.DrawString(str, new Font("宋体", 9), new SolidBrush(k.Color), new Point(xystr.X - (int)((sizeg.Width) * Math.Cos(angelStart + angel)), xystr.Y - (int)((sizeg.Height / 3) * Math.Cos(angelStart + angel))));
    }
    else
        g.DrawString(str, new Font("宋体", 9), new SolidBrush(k.Color), new Point(xystr.X, xystr.Y + (int)((sizeg.Height / 3) * Math.Cos(angelStart + angel)))); //-(int)((sizeg.Width) * Math.Cos(angelStart + angel)),- (int)((sizeg.Height / 2) * Math.Cos(angelStart + angel))
}
for (int i = 0; i <= fullGauge / smallUnit; i++)//小刻标度0-60
{
    double angel = i * (Math.PI) / 30;
    Point xy1 = new Point((int)(outlineRadiusOffset * Math.Cos(angelStart + angel) + centerPoint.X), (int)(outlineRadiusOffset * Math.Sin(angelStart + angel) + centerPoint.Y));
    Point xy2 = new Point((int)(middleRadiusOffset * Math.Cos(angelStart + angel) + centerPoint.X), (int)(middleRadiusOffset * Math.Sin(angelStart + angel) + centerPoint.Y));
    if (i % 5 != 0) 
           spath.AddLine( xy1, xy2);
   
}
 spath.CloseFigure();
这里要注意,如果我们要实现警告刻度区,那就要分别来绘制刻度线。用一个if语句即可完成。
 
 刻度喜欢亮一些可以提亮点,如下图:
 
 可以选择加上一个仪表盘的标签
String strt = "仪表盘";
SizeF sizeF = g.MeasureString(strt, new Font("宋体", 9));
int strFrameEdgeOffset = 4;
g.FillRectangle(new SolidBrush(Color.Gray), new Rectangle(centerPoint.X - ((int)sizeF.Width / 2) - strFrameEdgeOffset, centerPoint.Y + 80- strFrameEdgeOffset, (int)sizeF.Width + strFrameEdgeOffset, (int)sizeF.Height + strFrameEdgeOffset));
g.DrawString(strt, new Font("宋体", 9),new SolidBrush(Color.White),new Point(centerPoint.X-((int)sizeF.Width/2),centerPoint.Y+80));
            

 到这里,仪表盘的绘制基本完成,这个时候我们就可以和事件的联系在一起实现转动了。我这里的指针都是使用的可变化的角度,所以可以根据事件的值来变化,就如我们开头看到的那样可以转动。
 当然,你完全可以自己新建一个自定义控件,把这些代码拷贝进去,那你就有了一个表盘自定义控件了。



















