自己繪制的儀表盤


  一時興起,想寫個模擬地鐵駕駛的游戲,但是很多東西都不會,資源哪里有?例如列車前進時周圍景物的動態效果怎么做出來,速度控制桿上各個幅度代表了多大的加速度。我很佩服也很羡慕《申城脈動》的作者,能寫出那樣的一個游戲和獲得那么多的資源。地鐵族上面資料不少的,但不會找出來,廢話不多說。

  里面要用到的速度儀表盤,上網看見別人畫了不少很炫的,但提供下載沒有。對GDI+不太了解的我只能自己寫。幸虧看到一篇博文,是別人學生時代的實驗報告的(呵呵!這就是差別)。里面的代碼我沒看,我只看了一幅圖片就夠了。之前只困惑於儀表盤上面的刻度是怎么畫出來的,那篇文章里面有一幅圖就是一幅三角函數的圖。之前一直沒想到這里要用回初中時學的三角函數。

  下面將從控件的屬性,還有繪制的過程做一個介紹。從而介紹完這個儀表盤的制作。

數據類型

屬性名稱

描述

int

LongGraduateLength

長刻度長度

int

ShortGraduateLength

短刻度長度

float

Short2Long

短刻度轉長刻度進率

float

ShortgraduateSpan

短刻度間隔,實際數值的間隔

float

Range

量程

int

Angle

整個刻度的角度,角度制的度數

int

Width

控件寬度,與高度相等

int

Height

控件高度,與寬度相等

float

Value

當前值

  繪制控件都是在OnPaint事件里執行GDI+的代碼。

        protected override void OnPaint(PaintEventArgs e)
        {
            base.OnPaint(e);
            DrawGraduation(e.Graphics);
            DrawArc(e.Graphics);
        }

第一個方法是畫表盤的刻度,以及上面的示數;第二個方法是畫表盤的指針,包括當前的值。

先介紹第一個方法

 1         private void DrawGraduation(Graphics g)
 2         { 
 3             int startDec = 270 - (360 - Angle) / 2;
 4             int endDec = 270 + (360 - Angle) / 2 - 360;//計算出起始角度和終止角度
 5             float dealta = Angle * ShortgraduateSpan / Range; //計算出每個刻度間的角度間隔
 6             StringFormat sf=new StringFormat(){ Alignment= StringAlignment.Center,LineAlignment= StringAlignment.Center};
 7             
 8             for (float i = startDec; i >= endDec; i -= dealta)//通過循環畫出表盤上的刻度
 9             {
10                 if ((i - startDec)*Range/Angle % Short2Long == 0) //判斷此位置畫的是大刻度還是小刻度
11                 {
12                     //畫大刻度
13                     g.DrawLine(Pens.White,
14                         Radius + (float)Math.Cos(Degree2Radian(i)) * Radius,
15                         Radius - (float)Math.Sin(Degree2Radian(i)) * Radius,
16                         Radius + (float)Math.Cos(Degree2Radian(i)) * (Radius - LongGraduateLength),
17                         Radius - (float)Math.Sin(Degree2Radian(i)) * (Radius - LongGraduateLength)
18                         );
19                     //寫刻度上的文字
20                     g.DrawString(((startDec - i) * Range / Angle).ToString(), this.Font, Brushes.White,
21                         Radius + (float)Math.Cos(Degree2Radian(i)) * (Radius - LongGraduateLength-15),
22                         Radius - (float)Math.Sin(Degree2Radian(i)) * (Radius - LongGraduateLength-15),
23                         sf
24                         );//sf是文字的格式,這里設置成居中
25                 }
26                 else
27                     //畫小刻度
28                     g.DrawLine(Pens.White,
29                     Radius + (float)Math.Cos(Degree2Radian(i)) * Radius,
30                     Radius - (float)Math.Sin(Degree2Radian(i)) * Radius,
31                     Radius + (float)Math.Cos(Degree2Radian(i)) * (Radius - ShortGraduateLength),
32                     Radius - (float)Math.Sin(Degree2Radian(i)) * (Radius - ShortGraduateLength)
33                     );
34             }
35         }

畫刻度線還考了一回初中的三角函數。

只要有刻度線所在的角的角度,刻度線的起點和終點的坐標都可以確定下來。起點就是半徑上圓心的另一端。通過sin*R可以得到起點的相對圓心的縱坐標;cos*R可以得到起點相對圓心的橫坐標。終點的同樣道理,通過sin*(R-L)可以得到終點的相對圓心的縱坐標;cos*(R-L)可以得到終點相對圓心的橫坐標。

.NET Framework提供的三角函數有點坑,方法的描述上是說角度值,但是實際上要傳是弧度值。所以我這里還要做一個角度制和弧度制的轉換。

        private double Degree2Radian(double degree)
        {
            return degree * Math.PI / 180;
        }

 

下面到另一個方法

 1         private void DrawArc(Graphics g)
 2         {
 3             //示數和外部的圓圈
 4             for(float i=0;i<3;i+=0.1f)
 5                 g.DrawEllipse(Pens.White, Radius - 25+i, Radius - 25+i, 50-2*i, 50-2*i);
 6             g.DrawString(this.Value.ToString(), new Font(this.Font.FontFamily, 20,FontStyle.Bold), Brushes.White, Radius, Radius, new StringFormat() { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center });
 7 
 8             //旋轉畫布,形成一定的傾角
 9             Matrix matrix = new Matrix();
10             matrix.RotateAt(Value * Angle / Range - (270 - (360 - Angle) / 2), new PointF(Radius, Radius), MatrixOrder.Append);
11             g.Transform = matrix;
12             
13             //繪制指針上面的主體矩形
14             Rectangle rec = new Rectangle(Radius + 25, Radius - 3,Radius-55-ShortGraduateLength, 6);
15             g.DrawRectangle(Pens.White, rec);
16             g.FillRectangle(Brushes.White, rec);
17             
18             //繪制指針上的三角形
19             g.DrawPolygon(Pens.White,CreateTriangle(Radius*2 -30-ShortGraduateLength,Radius ,6,10,false));
20             g.FillPolygon(Brushes.White, CreateTriangle(Radius*2 -30-ShortGraduateLength, Radius , 6, 10, false));
21 
22             //繪制指針末端的小矩形
23             rec = new Rectangle(Radius * 2 - 25 - ShortGraduateLength,Radius-1,20,2);
24             g.DrawRectangle(Pens.White, rec);
25             g.FillRectangle(Brushes.White, rec);
26         }

這里指針實際上有四個圖形組成,指針起始的一個圓形,主體的那個粗的矩形,達到漸細效果的三角形和末端的一個小矩形

  .NET Framework又沒有提供畫三角形的方法,所以畫三角形它只能通過畫多邊形的方法來繪制。

  我這里定義了一個只符合這里使用的方法,用於繪制一個等腰三角形,傳入底邊的中點坐標,底邊和高的長度,是水平還是垂直,來構造一個三角形,最后返回三個頂點的坐標

 1         private Point[] CreateTriangle(int x, int y,int bottom, int height, bool isHorizontal)
 2         {
 3             Point p1, p2, ph;
 4             if (isHorizontal)
 5             {
 6                 p1 = new Point(x - bottom/2, y);
 7                 p2 = new Point(x + bottom/2, y);
 8                 ph = new Point(x, y + height);
 9             }
10             else
11             {
12                 p1 = new Point(x, y - bottom/2);
13                 p2 = new Point(x, y + bottom/2);
14                 ph = new Point(x + height, y);
15             }
16             return new Point[] {p1,p2,ph };
17         }

附一幅完成的效果圖,最后把整個控件的源碼粘上來。

覺得這個控件一點兒也不通用,只能滿足極個別的需求。大家盡管提一下意見,讓我改進改進。

  1     public class Dashboard:Control
  2     {
  3         public Dashboard()
  4         {
  5             this.Width = 300;
  6             this.Height = 300;
  7             this.BackColor = Color.Black;
  8             DoubleBuffered = true;
  9             this.Font = new Font(this.Font.FontFamily, 15f);
 10         }
 11 
 12         private int longGraduateLength=20,shortGraduateLength=10,angle=300;
 13         private float short2Long=2,shortgraduateSpan=5,range=100;
 14         private Color lineColor=Color.White;
 15 
 16         public int LongGraduateLength
 17         {
 18             get { return longGraduateLength; }
 19             set 
 20             {
 21                 longGraduateLength = value < 1 ? 1 : value;
 22             }
 23         }
 24 
 25         public int ShortGraduateLength
 26         {
 27             get { return shortGraduateLength; }
 28             set
 29             {
 30                 shortGraduateLength = value < 1 ? 1 : value;
 31             }
 32         }
 33 
 34         public float Short2Long
 35         {
 36             get { return short2Long; }
 37             set
 38             {
 39                 short2Long = value < 1 ? 1 : value;
 40             }
 41         }
 42 
 43         public float ShortgraduateSpan
 44         {
 45             get { return shortgraduateSpan; }
 46             set
 47             {
 48                 shortgraduateSpan = value < 1 ? 1 : value;
 49             }
 50         }
 51 
 52         public float Range
 53         {
 54             get { return range; }
 55             set
 56             {
 57                 range = value < 1 ? 1 : value;
 58             }
 59         }
 60 
 61         public int Angle
 62         {
 63             get { return angle; }
 64             set
 65             {
 66                 angle = value < 1 ? 1 : value;
 67             }
 68         }
 69 
 70         public Color LineColor
 71         {
 72             get { return lineColor; }
 73             set { lineColor = value; }
 74         }
 75 
 76         public new int Width
 77         {
 78             get { return base.Width; }
 79             set 
 80             {
 81                 if (value != base.Height) base.Height = value;
 82                 base.Width = value;
 83             }
 84         }
 85 
 86         public new int Height
 87         {
 88             get { return base.Height; }
 89             set
 90             {
 91                 if (value != base.Width) base.Width = value;
 92                 base.Height = value;
 93             }
 94         }
 95 
 96         private float currValue;
 97         public float Value
 98         {
 99             get { return currValue; }
100             set
101             {
102                 float preValue = currValue;
103                 if (value < 0) currValue = 0;
104                 else if (value > Range) currValue = Range;
105                 else currValue = value;
106                 if (preValue != currValue)
107                 {
108                     if(ValueChanged!=null) ValueChanged(this, new EventArgs());
109                     this.Refresh();
110                 }
111                 
112             }
113         }
114 
115         protected override void OnPaint(PaintEventArgs e)
116         {
117             base.OnPaint(e);
118             //刻度 示數 指針 示數
119             DrawGraduation(e.Graphics);
120             DrawArc(e.Graphics);
121         }
122 
123         private int Radius
124         {
125             get { return Width / 2; }
126         }
127 
128         private Point core;
129         private Point Core
130         {
131             get 
132             {
133                 if (core.X != this.Radius || core.Y != this.Radius)
134                     core = new Point(this.Radius, this.Radius);
135                 return core;
136             }
137         }
138 
139         private void DrawGraduation(Graphics g)
140         { 
141             //換算角度范圍
142             //半徑*sin=外點
143             //(半徑-刻度長)*sin=內點
144             //內點標數
145             int startDec = 270 - (360 - Angle) / 2;
146             int endDec = 270 + (360 - Angle) / 2-360;
147             float dealta=Angle*ShortgraduateSpan/Range;
148             StringFormat sf=new StringFormat(){ Alignment= StringAlignment.Center,LineAlignment= StringAlignment.Center};
149             
150             for (float i = startDec; i >= endDec; i -= dealta)
151             {
152                 if ((i - startDec)*Range/Angle % Short2Long == 0)
153                 {
154                     g.DrawLine(Pens.White,
155                         Radius + (float)Math.Cos(Degree2Radian(i)) * Radius,
156                         Radius - (float)Math.Sin(Degree2Radian(i)) * Radius,
157                         Radius + (float)Math.Cos(Degree2Radian(i)) * (Radius - LongGraduateLength),
158                         Radius - (float)Math.Sin(Degree2Radian(i)) * (Radius - LongGraduateLength)
159                         );
160                     g.DrawString(((startDec - i) * Range / Angle).ToString(), this.Font, Brushes.White,
161                         Radius + (float)Math.Cos(Degree2Radian(i)) * (Radius - LongGraduateLength-15),
162                         Radius - (float)Math.Sin(Degree2Radian(i)) * (Radius - LongGraduateLength-15),
163                         sf
164                         );
165                 }
166                 else
167                     g.DrawLine(Pens.White,
168                     Radius + (float)Math.Cos(Degree2Radian(i)) * Radius,
169                     Radius - (float)Math.Sin(Degree2Radian(i)) * Radius,
170                     Radius + (float)Math.Cos(Degree2Radian(i)) * (Radius - ShortGraduateLength),
171                     Radius - (float)Math.Sin(Degree2Radian(i)) * (Radius - ShortGraduateLength)
172                     );
173             }
174         }
175 
176         private void DrawArc(Graphics g)
177         {
178             for(float i=0;i<3;i+=0.1f)
179             g.DrawEllipse(Pens.White, Radius - 25+i, Radius - 25+i, 50-2*i, 50-2*i);
180             g.DrawString(this.Value.ToString(), new Font(this.Font.FontFamily, 20,FontStyle.Bold), Brushes.White, Radius, Radius, new StringFormat() { Alignment = StringAlignment.Center, LineAlignment = StringAlignment.Center });
181 
182 
183             Matrix matrix = new Matrix();
184             matrix.RotateAt(Value * Angle / Range - (270 - (360 - Angle) / 2), new PointF(Radius, Radius), MatrixOrder.Append);
185             g.Transform = matrix;
186             
187             Rectangle rec = new Rectangle(Radius + 25, Radius - 3,Radius-55-ShortGraduateLength, 6);
188             g.DrawRectangle(Pens.White, rec);
189             g.FillRectangle(Brushes.White, rec);
190             
191             g.DrawPolygon(Pens.White,CreateTriangle(Radius*2 -30-ShortGraduateLength,Radius ,6,10,false));
192             g.FillPolygon(Brushes.White, CreateTriangle(Radius*2 -30-ShortGraduateLength, Radius , 6, 10, false));
193 
194             rec = new Rectangle(Radius * 2 - 25 - ShortGraduateLength,Radius-1,20,2);
195             g.DrawRectangle(Pens.White, rec);
196             g.FillRectangle(Brushes.White, rec);
197         }
198 
199         private Point[] CreateTriangle(int x, int y,int bottom, int height, bool isHorizontal)
200         {
201             Point p1, p2, ph;
202             if (isHorizontal)
203             {
204                 p1 = new Point(x - bottom/2, y);
205                 p2 = new Point(x + bottom/2, y);
206                 ph = new Point(x, y + height);
207             }
208             else
209             {
210                 p1 = new Point(x, y - bottom/2);
211                 p2 = new Point(x, y + bottom/2);
212                 ph = new Point(x + height, y);
213             }
214             return new Point[] {p1,p2,ph };
215         }
216 
217         private double Degree2Radian(double degree)
218         {
219             return degree * Math.PI / 180;
220         }
221 
222         protected override void OnResize(EventArgs e)
223         {
224             base.OnResize(e);
225             this.Refresh();
226         }
227 
228         public delegate void OnValueChanged(object sender, EventArgs e);
229 
230         public event OnValueChanged ValueChanged;
231     }
儀表盤完整代碼

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM