0、小叙闲言

又接手一个新的项目了,再来一次上位机开发。网上有很多控件库,做仪表盘(gauge)的也不少,功能也很强大,但是个人觉得库很臃肿,自己就计划动手来写一个控件库,一是为学习,二是为了项目。下面是我花了一下午的时间做出来的,先看效果: 


这个表盘当前还比较丑,后面会一步一步地完善它的,包括各种美化,相信自己能做到的,加油!!这也是我个人第一次写博客,我会坚持下去,同时也会尽力表述清楚每一个技术细节。源码地址:https://github.com/wj-data/MyGauge

1、表盘总体设计

一个表盘,就简单来看,应该由四个部分组成,即:表盘外轮廓、刻度(包括小刻度和大刻度)、刻度值、指针。在制作的过程中,略微用了一些数学知识,只要用心思考,都很容易的。设计外观的过程中,用到了对应如下知识点。当然也包括一些C#和WPF的基础知识,如果有不清楚的地方,可以看看刘铁猛老师的《深入浅出WPF》

表盘外轮廓 刻度 刻度值 指针
Path路径绘图 直线 TextBlock控件 Path路径绘图

2、表盘外轮廓

初步设计,外轮廓由三段组成:yellow、green、red,借助WPF强大的绘图功能,做了一个渐变色,稍微美化了一下,如下图。(此圆的半径为:200px)

明显可以看出来,这个圆由三段弧组成的,如果观察仔细的话,可以隐约看到2根小白线,就是三段弧的分界处。

1.黄色弧绘制

代码如下:

 <Path StrokeThickness="30" Width="420" Height="400" StrokeStartLineCap="Round">
<Path.Data>
<PathGeometry Figures="M 0,200 A 200,200 0 0 1 58.57864,58.57864"/>
</Path.Data>
<Path.Stroke>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<LinearGradientBrush.GradientStops>
<GradientStop Offset="0" Color="Green"/>
<GradientStop Offset="1.0" Color="Yellow"/>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Path.Stroke>
</Path>

其中最为关键的代码是第3行。对其数据点的解释如下表。有不明白的地方,先记下来,后面也会用到,会慢慢理解的。

M 0,200 A 200,200 0 0 1 58.57864,58.57864

M是Path绘图的起点标记

弧的起点坐标为(0,200)

A(arc)是弧的标记

(200,200)表示x轴半径:200;y轴半径:200

圆弧旋转角度[0](有起点和终点,个人感觉这个值并没有什么用)

优势弧的标记[0](否,弧角度小于180)

正负角度标记[1](顺时针画圆)

表示终点,用数学公式计算出来的

(58.57864,58.57864)的计算方式如下:

黄色弧占1/4,故其角度为180*1/4=45度,黄色点的坐标计算如下图。

2.绿色和红色弧绘制

有了上面黄色弧绘制作为基础,绿色和红色都是同样的道理,下面直接给出绘制三段弧的代码

 <Path Stroke="Yellow" StrokeThickness="30" Width="420" Height="400" StrokeStartLineCap="Round">
<Path.Data>
<PathGeometry Figures="M 0,200 A 200,200 0 0 1 58.57864,58.57864"/>
</Path.Data>
</Path>
<Path Stroke="Green" StrokeThickness="30" Width="420" Height="400">
<Path.Data>
<PathGeometry Figures="M 58.57864,58.57864 A 200,200 0 0 1 341.42136,58.57864" />
</Path.Data>
</Path>
<Path Stroke="Red" StrokeThickness="30" Width="420" Height="400" StrokeEndLineCap="Round">
<Path.Data>
<PathGeometry Figures="M 341.42136,58.57864 A 200,200 0 0 1 400,200" />
</Path.Data>
</Path>

代码的重点在加粗的数字部分,为保证代码简洁,结构清晰,去掉了渐变色的处理,后面再加上。上述代码的效果如下图:

3、表盘刻度绘制

对于表盘刻度,是由许多直线段组成,同样可以用XAML语言绘制出来。但是这样,代码量有点大,同时我们也要手动输入许多坐标值,方法很笨,完全没有发挥出C#的功力。下面我先用XAML语言写出一个刻度(小刻度),以说明原理,然后用C#语言在后台绘出所有刻度,这样便于后期代码的维护和仪表盘的个性化定做。在20度角的直线刻度两个坐标的计算如下图所示。直线刻度的起点是在圆心为(200,200),半径为180的圆上;终点是在圆心为(200,200),半径为170的圆上。

根据上述计算出的结果,写出直线的XAML语言的代码和效果如下:

<Line Stroke="Green" StrokeThickness="2" X1="30.85533" Y1="138.43637" X2="40.25225" Y2="141.85658"/>

1.小刻度绘制

有了上面的基础知识,所有的小刻度可以很容易绘制出来,先看C#的后台代码:

 public MainWindow()
{
InitializeComponent();
this.DrawScale();
}
/// <summary>
/// 画表盘的刻度
/// </summary>
private void DrawScale()
{
for (int i = ; i <= ; i += )
{
//添加刻度线
Line lineScale = new Line(); lineScale.Stroke = new SolidColorBrush(Color.FromRgb(0xFF, 0x00, ));//使用红色的线
lineScale.StrokeThickness = ;//线条的粗细为1
//直线刻度的起点,注意角度转为弧度制
lineScale.X1 = - * Math.Cos(i * Math.PI / );
lineScale.Y1 = - * Math.Sin(i * Math.PI / );
//直线刻度的终点,注意角度转为弧度制
lineScale.X2 = - * Math.Cos(i * Math.PI / );
lineScale.Y2 = - * Math.Sin(i * Math.PI / );
//将直线画在Canvas画布上
this.gaugeCanvas.Children.Add(lineScale);
}
}

同样,代码的关键点还是在第19,20,22,23行,180-170=10,这个10表示的就是小刻度的长度。画出所有刻度后,效果如下:

2.大刻度绘制

大刻度是每5个小刻度就出现一次,长度为20px,有了画小刻度的基础,实现在刻度非常容易,只需对DrawScale函数稍加修改,如下面的粗体代码所示

         private void DrawScale()
{
for (int i = ; i <= ; i += )
{
//添加刻度线
Line lineScale = new Line(); if (i % == )//说明已经画了5个小刻度了,加一个大刻度
{
lineScale.X1 = - * Math.Cos(i * Math.PI / );
lineScale.Y1 = - * Math.Sin(i * Math.PI / );
lineScale.Stroke = new SolidColorBrush(Color.FromRgb(0x00, 0xFF, ));
lineScale.StrokeThickness = ;
}
else
{
lineScale.X1 = - * Math.Cos(i * Math.PI / );
lineScale.Y1 = - * Math.Sin(i * Math.PI / );
lineScale.Stroke = new SolidColorBrush(Color.FromRgb(0xFF, 0x00, ));
lineScale.StrokeThickness = ;
}
//直线刻度的终点,注意角度转为弧度制
lineScale.X2 = - * Math.Cos(i * Math.PI / );
lineScale.Y2 = - * Math.Sin(i * Math.PI / );
//将直线画在Canvas画布上
this.gaugeCanvas.Children.Add(lineScale);
}
}

最终实现的效果如下图所示,已经越来越接近了。

4、表盘刻度值添加

刻度值是用文本块表示的(TextBlock控件)。控制好文本块在表盘中的坐标就行,实现也很容易,这里有要注意的一点是,由于所有控件是的坐标起点是以左上角为零点,当角度超过90度的时候,坐标应当有所补偿。直接说可能说不清楚,在代码中理解,依旧是对DrawScale()函数进行修改如下:

 private void DrawScale()
{
for (int i = ; i <= ; i += )
{
//添加刻度线
Line lineScale = new Line(); if (i % == )
{
lineScale.X1 = - * Math.Cos(i * Math.PI / );
lineScale.Y1 = - * Math.Sin(i * Math.PI / );
lineScale.Stroke = new SolidColorBrush(Color.FromRgb(0x00, 0xFF, ));
lineScale.StrokeThickness = ; //添加刻度值
TextBlock txtScale = new TextBlock();
txtScale.Text = (i).ToString();
txtScale.FontSize = ;
if (i <= )//对坐标值进行一定的修正
{
Canvas.SetLeft(txtScale, - * Math.Cos(i * Math.PI / ));
}
else
{
Canvas.SetLeft(txtScale, - * Math.Cos(i * Math.PI / ));
}
Canvas.SetTop(txtScale, - * Math.Sin(i * Math.PI / ));
this.gaugeCanvas.Children.Add(txtScale);
}
else
{
lineScale.X1 = - * Math.Cos(i * Math.PI / );
lineScale.Y1 = - * Math.Sin(i * Math.PI / );
lineScale.Stroke = new SolidColorBrush(Color.FromRgb(0xFF, 0x00, ));
lineScale.StrokeThickness = ;
} lineScale.X2 = - * Math.Cos(i * Math.PI / );
lineScale.Y2 = - * Math.Sin(i * Math.PI / ); this.gaugeCanvas.Children.Add(lineScale);
}
}

添加刻度值后的效果如下图

5、指针绘制

做表盘的指针,可以有很多方案,网上有许多人说,用图片代替,但是图片一旦放大后,就会变得模糊,因此,我还是自己动手,做了一个简单的指针,同样是采用Path方法,使用路径绘图,XAML代码如下,指针主要由2条直线和一根弧组成,使用了橙色填充。

 <Path x:Name="indicatorPin" Fill="Orange">
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigure StartPoint="200,195" IsClosed="True">
<PathFigure.Segments>
<LineSegment Point="20,200"/>
<LineSegment Point="200,205"/>
</PathFigure.Segments>
</PathFigure>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
</Path>

很多表盘中间有一个指示数值的,这里,我也用一个文本块来仿制一下,XAML语言如下,注意,文本块的位置要分配好。

<TextBlock x:Name="currentValueTxtBlock" FontSize="20" Canvas.Left="140" Canvas.Top="150"/>

最终外观如下图所示:

6、让指针转起来

指针的转动,很明显,是以(200,200)为圆心,各种角度转动,使用了RotateTransform和DoubleAnimation实现转动动画。动画的时间长度根据角度大小分配,1度8个毫秒。转动的角度大小目前是随机生成的,在此,我将转动动画写在canvas_MouseDown事件里面。C#代码如下:

 private void Canvas_MouseDown(object sender, MouseButtonEventArgs e)
{
RotateTransform rt = new RotateTransform();
rt.CenterX = ;
rt.CenterY = ; this.indicatorPin.RenderTransform = rt; angelCurrent = angleNext;
Random random = new Random();
angleNext = random.Next(); double timeAnimation = Math.Abs(angelCurrent - angleNext) * ;
DoubleAnimation da = new DoubleAnimation(angelCurrent, angleNext, new Duration(TimeSpan.FromMilliseconds(timeAnimation)));
da.AccelerationRatio = ;
rt.BeginAnimation(RotateTransform.AngleProperty, da);

最终效果如下,终于做完了,享受一下成果!!

总结心得

此表盘目前虽然很简单,但是自己一步一步思考然后做出来的,后面如果需要添加定制更加强大的功能,相信得心应手的。第一次写博客,比想像中的难多了,感觉很多东西都难以表述清楚。同时为了更好的表达效果,用了visio制图,和MathType公式编辑器,还用了录屏软件录制窗口视频,然后用迅雷看看截出gif图,最后将gif图处理一下,发布至博客里面,着实不容易,相信后面会越来越容易的。

同时,下一目标,将此表盘美化和封装成用户控件,供项目调用。

下下一目标,制作图表控件,敬请关注!!

WPF自定义控件(1)——仪表盘设计[1]的更多相关文章

  1. WPF自定义控件(2)——图表设计[1]

    0.小叙闲言 除了仪表盘控件比较常用外,还有图表也经常使用,同样网上也有非常强大的图表控件,有收费的(DEVexpress),也有免费的.但我们平时在使用时,只想简单地绘一个图,控件库里面的许多功能我 ...

  2. WPF自定义控件与样式(15)-终结篇 & 系列文章索引 & 源码共享

    系列文章目录  WPF自定义控件与样式(1)-矢量字体图标(iconfont) WPF自定义控件与样式(2)-自定义按钮FButton WPF自定义控件与样式(3)-TextBox & Ric ...

  3. WPF自定义控件与样式(12)-缩略图ThumbnailImage /gif动画图/图片列表

    一.前言 申明:WPF自定义控件与样式是一个系列文章,前后是有些关联的,但大多是按照由简到繁的顺序逐步发布的等,若有不明白的地方可以参考本系列前面的文章,文末附有部分文章链接. 本文主要针对WPF项目 ...

  4. WPF自定义控件与样式(15)-终结篇

    原文:WPF自定义控件与样式(15)-终结篇 系列文章目录  WPF自定义控件与样式(1)-矢量字体图标(iconfont) WPF自定义控件与样式(2)-自定义按钮FButton WPF自定义控件与 ...

  5. WPF自定义控件(四)の自定义控件

    在实际工作中,WPF提供的控件并不能完全满足不同的设计需求.这时,需要我们设计自定义控件. 这里LZ总结一些自己的思路,特性如下: Coupling UITemplate Behaviour Func ...

  6. WPF自定义控件(一)の控件分类

    一.什么是控件(Controls) 控件是指对数据和方法的封装.控件可以有自己的属性和方法,其中属性是控件数据的简单访问者,方法则是控件的一些简单而可见的功能.控件创建过程包括设计.开发.调试(就是所 ...

  7. 【转】WPF自定义控件与样式(12)-缩略图ThumbnailImage /gif动画图/图片列表

    一.前言 申明:WPF自定义控件与样式是一个系列文章,前后是有些关联的,但大多是按照由简到繁的顺序逐步发布的等,若有不明白的地方可以参考本系列前面的文章,文末附有部分文章链接. 本文主要针对WPF项目 ...

  8. [WPF 自定义控件]开始一个自定义控件库项目

    1. 目标 我实现了一个自定义控件库,并且打算用这个控件库作例子写一些博客.这个控件库主要目标是用于教学,希望通过这些博客初学者可以学会为自己或公司创建自定义控件,并且对WPF有更深入的了解. 控件库 ...

  9. [WPF自定义控件库]使用WindowChrome自定义RibbonWindow

    原文:[WPF自定义控件库]使用WindowChrome自定义RibbonWindow 1. 为什么要自定义RibbonWindow 自定义Window有可能是设计或功能上的要求,可以是非必要的,而自 ...

随机推荐

  1. TCP详解

    1. 数据进入协议栈的封装过程 2. TCP连接的三次握手 3. TCP连接的三次握手和关闭时的四次握手 各个状态的意义如下: LISTEN - 侦听来自远方TCP端口的连接请求: SYN-SENT ...

  2. 【转】对于HttpClient和HtmlUnit的理解

    原文地址:http://www.haohaoblog.com/?p=1327&utm_source=tuicool 做Java编程的人其实,很多不懂SEO,也不知道如何让百度收录等等,当然,对 ...

  3. 第三方库FMDB的使用

    1.FMDB简介 什么是FMDB FMDB是iOS平台的SQLite数据库框架,FMDB以OC的方式封装了SQLite的C语言API. 为什么使用FMDB 使用起来更加面向对象,省去了很多麻烦.冗余的 ...

  4. 关于zepto在chrome中触发两次的解决方案

    复现条件:chrome 55+ 1.zepto tap的实现及double fire的原因 在监听DOM根节点的时候,touchStart后通过XY的坐标偏差,与tapTime的计时判断Tap/Lon ...

  5. Java版本

    Java版本 Java版本分为J2SE(Java 2 Standard Edition,Java标准版).J2ME(Java 2 Micro Edition,Java微型版本)和J2EE(Java 2 ...

  6. 双系统win7和ubuntu14.04进入了grub rescue>

    可以跳过的废话:最近在学习caffe,需要在linux下安装cuda,sudo apt-get install cuda后,出现了由于根目录/空间不足而失败的情况. 于是想把win7下80G的一个盘格 ...

  7. 《深入理解Java虚拟机》学习笔记之内存分配

    JVM在执行Java程序的过程中会把它所管理的内存划分若干个不同的数据区域,如下图: 大致可以分为两类:线程私有区域和线程共享区域. 线程私有区域 程序计数器(Program Counter Regi ...

  8. iOS实现视频和图片的上传

    关于iOS如何实现视频和图片的上传, 我们先理清下思路 思路: #1. 如何获取图片? #2. 如何获取视频? #3. 如何把图片存到缓存路径中? #4. 如何把视频存到缓存路径中? #5. 如何上传 ...

  9. ycsb对hbase性能测试的研究

    ycsb对hbase性能测试 1.ycsb安装 下载ycsb软件 wget https://github.com/downloads/brianfrankcooper/YCSB/ycsb-0.1.4. ...

  10. DOM操作和样式操作库的封装

    一.DOM常用方法和属性复习 以下粗略的罗列一下DOM的常用方法和属性,由于不是介绍DOM的基础内容,所以就不一一详细说明各个方法和属性了(学习DOM的封装的,一般都对基础DOM比较熟悉了). 1.1 ...