以前在百度寫的文檔,轉移到此處
前些天在做NetAnalyzer時,需要使用一個指針儀表,網上看了一下,也有人做過,但是大部分都是收費的,本着自力更生的原則,於是決定自己設計一個,今天拿出來有讀者分享一下。
首先是截圖:
該儀表是以控件形式提供
在開始之前還要贅述一點關於GDI+中角度的計算
如下圖
在WinForm中左上角的點位(0,0),即原點,而其起始角則是圖中划線處開始的,即為 rad=0;
在繪圖時,尤其是做過扇形統計圖的人應該比較清楚。
--------------------------------------------------------
接下來就是正式開始
首先新建控件,設置為witdth=height=150 ,可以自己定義,我在這里時可以自適應的
將背景顏色設置為Transparent(透明色),方便以后使用時減少干擾
在該儀表中主要分為兩部分:背景部分(外框,刻度,單位等一些列基本不需要頻繁變化的部分),前景部分(指針部分)
所以為了不是兩個圖層不相互影響,我們將背景繪制在控件的BackgroundImage 屬性上,而指針部分則需要一個pictrueBox控件作為載體。
首先畫背景
在繪制背景時,又分為外框、刻度,指針固定中心等
1 // 繪制背景 用來總體控制背景的繪制 2 private void DrawBackImg() 3 { 4 Bitmap bit = new Bitmap(this.Width, this.Height); 5 Graphics gp = Graphics.FromImage(bit); 6 gp.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; 7 #region 在這里可以擴展需要繪制的背景項目 8 //外框 9 drawFrame(gp); 10 // 畫刻度 11 DrawRuling(gp); 12 //畫點 13 drawPoint(gp); 14 15 //繪制單位 16 17 DrawUnitStr(gp); 18 19 #endregion 20 21 //當繪制完成后,直接直接設置為背景 22 23 this.BackgroundImage = bit; 24 } 25 26 27 //繪制單位 28 29 private void DrawUnitStr(Graphics gp) 30 { 31 int cer = _diameter / 2; 32 gp.DrawString(_unitStr, new Font("宋體", 10), new SolidBrush(_frameColor), new PointF(cer, (float)(cer - cer * 0.3)), strFormat); 33 34 } 35 36 /// <summary> 37 /// 畫外框 38 /// </summary> 39 /// <param name="gp"></param> 40 private void drawFrame(Graphics gp) 41 { 42 Pen pen = new Pen(_frameColor, 2); 43 Rectangle rec = new Rectangle(5, 5, _diameter - 10, _diameter - 10); 44 gp.DrawEllipse(pen, rec); 45 } 46 // 畫刻度 此次較為復雜,主要是在繪制刻度值時需要處理 47 private void DrawRuling(Graphics gp) 48 { 49 //刻度 50 int cerX = _diameter / 2; 51 int cerY = _diameter / 2; 52 53 //這里需要注意,因外在上面的圖中標識了rad=0的位置,而我們的儀表時270度的,0點在135度處, 54 55 //為了符合該效果所以起始位置設為135度。 56 float start = 135; 57 float sweepShot = 0; 58 int dx = 0; 59 int dy = 0; 60 int soildLenght = 8; 61 Pen linePen = new Pen(_frameColor, 1); 62 float span = (float)(_maxValue / 30); 63 float sp = 0; 64 //用於右邊數字右對齊 65 StringFormat stf = new StringFormat(); 66 stf.Alignment = StringAlignment.Far; 67 68 StringFormat stfMid = new StringFormat(); 69 stfMid.Alignment = StringAlignment.Center; 70 stfMid.LineAlignment = StringAlignment.Center; 71 for (int i = 0; i <= 30; i++) 72 { 73 //注意此處,C#提供的三角函數計算中使用的弧度值,而此處獲取的是角度值,需要轉化 74 75 double rad = (sweepShot + start) * Math.PI / 180; 76 float radius = _diameter / 2 - 5; 77 int px = (int)(cerX + radius * Math.Cos(rad)); 78 int py = (int)(cerY + radius * Math.Sin(rad)); 79 if (sweepShot % 15 == 0) 80 { 81 linePen.Width = 2; 82 83 //計算刻度中的粗線 84 dx = (int)(cerX + (radius - soildLenght) * Math.Cos(rad)); 85 dy = (int)(cerY + (radius - soildLenght) * Math.Sin(rad)); 86 87 //繪制刻度值,注意字串對其方式 88 string str = sp.ToString("f0"); 89 if (sweepShot <= 45) 90 { 91 gp.DrawString(str, new Font("宋體", 9), new SolidBrush(_frameColor), new PointF(dx, dy - 5)); 92 } 93 else if (sweepShot > 45 && sweepShot < 135) 94 { 95 gp.DrawString(str, new Font("宋體", 9), new SolidBrush(_frameColor), new PointF(dx, dy)); 96 } 97 else if (sweepShot == 135) 98 { 99 gp.DrawString(str, new Font("宋體", 9), new SolidBrush(_frameColor), new PointF(dx, dy + 10), stfMid); 100 } 101 else if (sweepShot > 135 && sweepShot < 225) 102 { 103 gp.DrawString(str, new Font("宋體", 9), new SolidBrush(_frameColor), new PointF(dx, dy), stf); 104 } 105 else if (sweepShot >= 225) 106 { 107 gp.DrawString(str, new Font("宋體", 9), new SolidBrush(_frameColor), new PointF(dx, dy - 5), stf); 108 } 109 110 } 111 else 112 { 113 114 //計算刻度中細線 115 116 linePen.Width = 1; 117 dx = (int)(cerX + (radius - soildLenght + 2) * Math.Cos(rad)); 118 dy = (int)(cerY + (radius - soildLenght + 2) * Math.Sin(rad)); 119 } 120 121 //繪制刻度線 122 gp.DrawLine(linePen, new Point(px, py), new Point(dx, dy)); 123 sp += span; 124 sweepShot += 9; 125 } 126 } 127 //畫中間的點 128 private void drawPoint(Graphics gp) 129 { 130 Pen p = new Pen(_frameColor); 131 int tmpWidth = 6; 132 int px = _diameter / 2 - tmpWidth; 133 134 gp.DrawEllipse(p, new Rectangle(px, px, 2 * tmpWidth, 2 * tmpWidth)); 135 136 //在畫點時,我使用了指針的顏色,這樣看起來,更真實一點 137 gp.FillEllipse(new SolidBrush(_pinColor), new Rectangle(px + 2, px + 2, 2 * tmpWidth - 4, 2 * tmpWidth - 4)); 138 } 139 140 ------------------------------------------- 141 142 畫指針 143 144 繪制指正時,最大的問題就是界面閃速,除了在控件構造方法里添加如下代碼: 145 146 SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.ResizeRedraw | ControlStyles.AllPaintingInWmPaint, true); 147 UpdateStyles(); 148 149 繪制方式也需要調整,方法如下: 150 151 152 153 //為了方式繪制指針時產生的閃爍,PictureBox添加該事件方法 154 155 private void pic_Paint(object sender, PaintEventArgs e) 156 { 157 DrawForeImg(e.Graphics); 158 } 159 160 //使用方法 161 162 public double ChangeValue 163 { 164 get { return _changeValue; } 165 set 166 { 167 if (value <= _maxValue) 168 _changeValue = value; 169 else 170 { 171 //完成自適應性 172 MaxValue = value; 173 _changeValue = value; 174 } 175 //通過該方法,可以使指針自動繪制(其實就是強制重繪) 176 177 pic.Invalidate(); 178 } 179 } 180 181 //指針的具體畫法 182 183 private void DrawForeImg(Graphics gp) 184 { 185 Bitmap bit = new Bitmap(this.Width, this.Height); 186 Graphics g = Graphics.FromImage(bit); 187 g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality; 188 189 //畫針 190 DrawPin(g); 191 DrawString(g); 192 193 //注意此處的繪制方式,這樣可以有效減少界面的閃爍問題。 194 gp.DrawImage(bit, new Point(0, 0)); 195 g.Dispose(); 196 197 } 198 //畫針 199 private void DrawPin(Graphics g) 200 { 201 int cer = _diameter / 2; 202 float start = 135; 203 float sweepShot = (float)(_changeValue / _maxValue * 270); 204 205 Pen linePen = new Pen(_pinColor, 1); 206 Pen NxPen = new Pen(_pinColor, 2); 207 Pen xPen = new Pen(_pinColor, 5); 208 double rad = (sweepShot + start) * Math.PI / 180; 209 float radius = _diameter / 2 - 5; 210 int dx = (int)(cer + (_PinLen) * Math.Cos(rad)); 211 int dy = (int)(cer + (_PinLen) * Math.Sin(rad)); 212 213 int px = (int)(cer + (_PinLen * 0.4) * Math.Cos(rad)); 214 int py = (int)(cer + (_PinLen * 0.4) * Math.Sin(rad)); 215 216 int nx = (int)(cer - (NxPinLen) * Math.Sin(rad)); 217 int ny = (int)(cer - (NxPinLen) * Math.Cos(rad)); 218 g.DrawLine(linePen, new Point(cer, cer), new Point(dx, dy)); 219 g.DrawLine(NxPen, new Point(cer, cer), new Point(px, py)); 220 g.DrawLine(xPen, new Point(cer, cer), new Point(ny, nx)); 221 } 222 223 //繪制在儀表下面的值 224 225 private void DrawString(Graphics g) 226 { 227 int cer = _diameter / 2; 228 string str = _changeValue.ToString("F2"); 229 g.DrawString(str, new Font("宋體", 9), new SolidBrush(_pinColor), new PointF(cer, (float)(cer + cer * 0.4)), strFormat); 230 }