在做NetAnalyzer时,需要使用一个 指针仪表 ,网上看了一下,也有人做过,但是大部分都是收费的,本着自力更生的原则,于是决定自己设计一个,今天拿出来有读者分享一下。
首先是截图:
该仪表是以控件形式提供
在开始之前还要赘述一点关于GDI+中角度的计算
如下图:
在WinForm中左上角的点位(0,0),即原点,而其起始角则是图中划线处开始的,即为 rad=0;
在绘图时,尤其是做过扇形统计图的人应该比较清楚。
——————————————————–
接下来就是正式开始
首先新建控件,设置为witdth=height=150 ,可以自己定义,我在这里时可以自适应的
将背景颜色设置为Transparent(透明色),方便以后使用时减少干扰
在该仪表中主要分为两部分:背景部分(外框,刻度,单位等一些列基本不需要频繁变化的部分),前景部分(指针部分)
所以为了不是两个图层不相互影响,我们将背景绘制在控件的BackgroundImage 属性上,而指针部分则需要一个pictrueBox控件作为载体。
首先画背景
在绘制背景时,又分为外框、刻度,指针固定中心等
// 绘制背景 用来总体控制背景的绘制 private void DrawBackImg() { Bitmap bit = new Bitmap(this.Width, this.Height); Graphics gp = Graphics.FromImage(bit); gp.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; #region 在这里可以扩展需要绘制的背景项目 //外框 drawFrame(gp); // 画刻度 DrawRuling(gp); //画点 drawPoint(gp); //绘制单位 DrawUnitStr(gp); #endregion //当绘制完成后,直接直接设置为背景 this.BackgroundImage = bit; } //绘制单位 private void DrawUnitStr(Graphics gp) { int cer = _diameter / 2; gp.DrawString(_unitStr, new Font("宋体", 10), new SolidBrush(_frameColor), new PointF(cer, (float)(cer - cer * 0.3)), strFormat); } /// <summary> /// 画外框 /// </summary> /// <param name="gp"></param> private void drawFrame(Graphics gp) { Pen pen = new Pen(_frameColor, 2); Rectangle rec = new Rectangle(5, 5, _diameter - 10, _diameter - 10); gp.DrawEllipse(pen, rec); } // 画刻度 此次较为复杂,主要是在绘制刻度值时需要处理 private void DrawRuling(Graphics gp) { //刻度 int cerX = _diameter / 2; int cerY = _diameter / 2; //这里需要注意,因外在上面的图中标识了rad=0的位置,而我们的仪表时270度的,0点在135度处, //为了符合该效果所以起始位置设为135度。 float start = 135; float sweepShot = 0; int dx = 0; int dy = 0; int soildLenght = 8; Pen linePen = new Pen(_frameColor, 1); float span = (float)(_maxValue / 30); float sp = 0; //用于右边数字右对齐 StringFormat stf = new StringFormat(); stf.Alignment = StringAlignment.Far; StringFormat stfMid = new StringFormat(); stfMid.Alignment = StringAlignment.Center; stfMid.LineAlignment = StringAlignment.Center; for (int i = 0; i <= 30; i++) { //注意此处,C#提供的三角函数计算中使用的弧度值,而此处获取的是角度值,需要转化 double rad = (sweepShot + start) * Math.PI / 180; float radius = _diameter / 2 - 5; int px = (int)(cerX + radius * Math.Cos(rad)); int py = (int)(cerY + radius * Math.Sin(rad)); if (sweepShot % 15 == 0) { linePen.Width = 2; //计算刻度中的粗线 dx = (int)(cerX + (radius - soildLenght) * Math.Cos(rad)); dy = (int)(cerY + (radius - soildLenght) * Math.Sin(rad)); //绘制刻度值,注意字串对其方式 string str = sp.ToString("f0"); if (sweepShot <= 45) { gp.DrawString(str, new Font("宋体", 9), new SolidBrush(_frameColor), new PointF(dx, dy - 5)); } else if (sweepShot > 45 && sweepShot < 135) { gp.DrawString(str, new Font("宋体", 9), new SolidBrush(_frameColor), new PointF(dx, dy)); } else if (sweepShot == 135) { gp.DrawString(str, new Font("宋体", 9), new SolidBrush(_frameColor), new PointF(dx, dy + 10), stfMid); } else if (sweepShot > 135 && sweepShot < 225) { gp.DrawString(str, new Font("宋体", 9), new SolidBrush(_frameColor), new PointF(dx, dy), stf); } else if (sweepShot >= 225) { gp.DrawString(str, new Font("宋体", 9), new SolidBrush(_frameColor), new PointF(dx, dy - 5), stf); } } else { //计算刻度中细线 linePen.Width = 1; dx = (int)(cerX + (radius - soildLenght + 2) * Math.Cos(rad)); dy = (int)(cerY + (radius - soildLenght + 2) * Math.Sin(rad)); } //绘制刻度线 gp.DrawLine(linePen, new Point(px, py), new Point(dx, dy)); sp += span; sweepShot += 9; } } //画中间的点 private void drawPoint(Graphics gp) { Pen p = new Pen(_frameColor); int tmpWidth = 6; int px = _diameter / 2 - tmpWidth; gp.DrawEllipse(p, new Rectangle(px, px, 2 * tmpWidth, 2 * tmpWidth)); //在画点时,我使用了指针的颜色,这样看起来,更真实一点 gp.FillEllipse(new SolidBrush(_pinColor), new Rectangle(px + 2, px + 2, 2 * tmpWidth - 4, 2 * tmpWidth - 4)); }
——————————————-
画指针
绘制指正时,最大的问题就是界面闪速,除了在控件构造方法里添加如下代码:
SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.ResizeRedraw | ControlStyles.AllPaintingInWmPaint, true); UpdateStyles();
绘制方式也需要调整,方法如下:
//为了方式绘制指针时产生的闪烁,PictureBox添加该事件方法 private void pic_Paint(object sender, PaintEventArgs e) { DrawForeImg(e.Graphics); } //使用方法 public double ChangeValue { get { return _changeValue; } set { if (value <= _maxValue) _changeValue = value; else { //完成自适应性 MaxValue = value; _changeValue = value; } //通过该方法,可以使指针自动绘制(其实就是强制重绘) pic.Invalidate(); } } //指针的具体画法 private void DrawForeImg(Graphics gp) { Bitmap bit = new Bitmap(this.Width, this.Height); Graphics g = Graphics.FromImage(bit); g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; //画针 DrawPin(g); DrawString(g); //注意此处的绘制方式,这样可以有效减少界面的闪烁问题。 gp.DrawImage(bit, new Point(0, 0)); g.Dispose(); } //画针 private void DrawPin(Graphics g) { int cer = _diameter / 2; float start = 135; float sweepShot = (float)(_changeValue / _maxValue * 270); Pen linePen = new Pen(_pinColor, 1); Pen NxPen = new Pen(_pinColor, 2); Pen xPen = new Pen(_pinColor, 5); double rad = (sweepShot + start) * Math.PI / 180; float radius = _diameter / 2 - 5; int dx = (int)(cer + (_PinLen) * Math.Cos(rad)); int dy = (int)(cer + (_PinLen) * Math.Sin(rad)); int px = (int)(cer + (_PinLen * 0.4) * Math.Cos(rad)); int py = (int)(cer + (_PinLen * 0.4) * Math.Sin(rad)); int nx = (int)(cer - (NxPinLen) * Math.Sin(rad)); int ny = (int)(cer - (NxPinLen) * Math.Cos(rad)); g.DrawLine(linePen, new Point(cer, cer), new Point(dx, dy)); g.DrawLine(NxPen, new Point(cer, cer), new Point(px, py)); g.DrawLine(xPen, new Point(cer, cer), new Point(ny, nx)); } //绘制在仪表下面的值 private void DrawString(Graphics g) { int cer = _diameter / 2; string str = _changeValue.ToString("F2"); g.DrawString(str, new Font("宋体", 9), new SolidBrush(_pinColor), new PointF(cer, (float)(cer + cer * 0.4)), strFormat); }