WPF自定義控件(1)——儀表盤設計[1]


0、小敘閑言

又接手一個新的項目了,再來一次上位機開發。網上有很多控件庫,做儀表盤(gauge)的也不少,功能也很強大,但是個人覺得庫很臃腫,自己就計划動手來寫一個控件庫,一是為學習,二是為了項目。下面是我花了一下午的時間做出來的,先看效果: 


這個表盤當前還比較丑,后面會一步一步地完善它的,包括各種美化,相信自己能做到的,加油!!這也是我個人第一次寫博客,我會堅持下去,同時也會盡力表述清楚每一個技術細節。源碼地址:https://github.com/wj-data/MyGauge

1、表盤總體設計

 一個表盤,就簡單來看,應該由四個部分組成,即:表盤外輪廓、刻度(包括小刻度和大刻度)、刻度值、指針。在制作的過程中,略微用了一些數學知識,只要用心思考,都很容易的。設計外觀的過程中,用到了對應如下知識點。當然也包括一些C#和WPF的基礎知識,如果有不清楚的地方,可以看看劉鐵猛老師的《深入淺出WPF》

表盤外輪廓 刻度 刻度值 指針
Path路徑繪圖 直線 TextBlock控件 Path路徑繪圖

2、表盤外輪廓

 初步設計,外輪廓由三段組成:yellow、green、red,借助WPF強大的繪圖功能,做了一個漸變色,稍微美化了一下,如下圖。(此圓的半徑為:200px)

明顯可以看出來,這個圓由三段弧組成的,如果觀察仔細的話,可以隱約看到2根小白線,就是三段弧的分界處。

1.黃色弧繪制

代碼如下:

 1 <Path StrokeThickness="30" Width="420" Height="400" StrokeStartLineCap="Round">
 2     <Path.Data>
 3         <PathGeometry Figures="M 0,200 A 200,200 0 0 1 58.57864,58.57864"/>
 4     </Path.Data>
 5     <Path.Stroke>
 6         <LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
 7             <LinearGradientBrush.GradientStops>
 8                 <GradientStop Offset="0" Color="Green"/>
 9                 <GradientStop Offset="1.0" Color="Yellow"/>
10             </LinearGradientBrush.GradientStops>
11         </LinearGradientBrush>
12     </Path.Stroke>
13 </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.綠色和紅色弧繪制

 有了上面黃色弧繪制作為基礎,綠色和紅色都是同樣的道理,下面直接給出繪制三段弧的代碼

 1 <Path Stroke="Yellow" StrokeThickness="30" Width="420" Height="400" StrokeStartLineCap="Round">
 2     <Path.Data>
 3         <PathGeometry Figures="M 0,200 A 200,200 0 0 1 58.57864,58.57864"/>
 4     </Path.Data>
 5 </Path>
 6 <Path Stroke="Green" StrokeThickness="30" Width="420" Height="400">
 7     <Path.Data>
 8         <PathGeometry Figures="M 58.57864,58.57864 A 200,200 0 0 1 341.42136,58.57864" />
 9     </Path.Data>
10 </Path>
11 <Path Stroke="Red" StrokeThickness="30" Width="420" Height="400" StrokeEndLineCap="Round">
12     <Path.Data>
13         <PathGeometry Figures="M 341.42136,58.57864 A 200,200 0 0 1 400,200" />
14     </Path.Data>
15 </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#的后台代碼:

 1 public MainWindow()
 2 {
 3     InitializeComponent();
 4     this.DrawScale();
 5 }
 6 /// <summary>
 7 /// 畫表盤的刻度
 8 /// </summary>
 9 private void DrawScale()
10 {
11     for (int i = 0; i <= 180; i += 5)
12     {
13         //添加刻度線
14         Line lineScale = new Line();
15 
16         lineScale.Stroke = new SolidColorBrush(Color.FromRgb(0xFF, 0x00, 0));//使用紅色的線
17         lineScale.StrokeThickness = 1;//線條的粗細為1
18         //直線刻度的起點,注意角度轉為弧度制
19         lineScale.X1 = 200 - 170 * Math.Cos(i * Math.PI / 180); 20         lineScale.Y1 = 200 - 170 * Math.Sin(i * Math.PI / 180); 21         //直線刻度的終點,注意角度轉為弧度制
22         lineScale.X2 = 200 - 180 * Math.Cos(i * Math.PI / 180); 23         lineScale.Y2 = 200 - 180 * Math.Sin(i * Math.PI / 180); 24         //將直線畫在Canvas畫布上
25         this.gaugeCanvas.Children.Add(lineScale);
26     }
27 }

同樣,代碼的關鍵點還是在第19,20,22,23行,180-170=10,這個10表示的就是小刻度的長度。畫出所有刻度后,效果如下:

2.大刻度繪制

大刻度是每5個小刻度就出現一次,長度為20px,有了畫小刻度的基礎,實現在刻度非常容易,只需對DrawScale函數稍加修改,如下面的粗體代碼所示

 1         private void DrawScale()
 2         {
 3             for (int i = 0; i <= 180; i += 5)
 4             {
 5                 //添加刻度線
 6                 Line lineScale = new Line();
 7 
 8                 if (i % 25 == 0)//說明已經畫了5個小刻度了,加一個大刻度
 9  { 10                     lineScale.X1 = 200 - 160 * Math.Cos(i * Math.PI / 180); 11                     lineScale.Y1 = 200 - 160 * Math.Sin(i * Math.PI / 180); 12                     lineScale.Stroke = new SolidColorBrush(Color.FromRgb(0x00, 0xFF, 0)); 13                     lineScale.StrokeThickness = 3; 14                 }
15                 else
16                 {
17                     lineScale.X1 = 200 - 170 * Math.Cos(i * Math.PI / 180);
18                     lineScale.Y1 = 200 - 170 * Math.Sin(i * Math.PI / 180);
19                     lineScale.Stroke = new SolidColorBrush(Color.FromRgb(0xFF, 0x00, 0));
20                     lineScale.StrokeThickness = 1;
21                 }
22                 //直線刻度的終點,注意角度轉為弧度制
23                 lineScale.X2 = 200 - 180 * Math.Cos(i * Math.PI / 180);
24                 lineScale.Y2 = 200 - 180 * Math.Sin(i * Math.PI / 180);
25                 //將直線畫在Canvas畫布上
26                 this.gaugeCanvas.Children.Add(lineScale);
27             }
28         }

最終實現的效果如下圖所示,已經越來越接近了。

4、表盤刻度值添加

刻度值是用文本塊表示的(TextBlock控件)。控制好文本塊在表盤中的坐標就行,實現也很容易,這里有要注意的一點是,由於所有控件是的坐標起點是以左上角為零點,當角度超過90度的時候,坐標應當有所補償。直接說可能說不清楚,在代碼中理解,依舊是對DrawScale()函數進行修改如下:

 1 private void DrawScale()
 2 {
 3     for (int i = 0; i <= 180; i += 5)
 4     {
 5         //添加刻度線
 6         Line lineScale = new Line();
 7 
 8         if (i % 25 == 0)
 9         {
10             lineScale.X1 = 200 - 160 * Math.Cos(i * Math.PI / 180);
11             lineScale.Y1 = 200 - 160 * Math.Sin(i * Math.PI / 180);
12             lineScale.Stroke = new SolidColorBrush(Color.FromRgb(0x00, 0xFF, 0));
13             lineScale.StrokeThickness = 3;
14 
15             //添加刻度值
16             TextBlock txtScale = new TextBlock();
17             txtScale.Text = (i).ToString();
18             txtScale.FontSize = 10;
19             if (i <= 90)//對坐標值進行一定的修正
20             {
21                 Canvas.SetLeft(txtScale, 200 - 155 * Math.Cos(i * Math.PI / 180));
22             }
23             else
24             {
25                 Canvas.SetLeft(txtScale, 190 - 155 * Math.Cos(i * Math.PI / 180));
26             }
27             Canvas.SetTop(txtScale, 200 - 155 * Math.Sin(i * Math.PI / 180));
28             this.gaugeCanvas.Children.Add(txtScale);
29         }
30         else
31         {
32             lineScale.X1 = 200 - 170 * Math.Cos(i * Math.PI / 180);
33             lineScale.Y1 = 200 - 170 * Math.Sin(i * Math.PI / 180);
34             lineScale.Stroke = new SolidColorBrush(Color.FromRgb(0xFF, 0x00, 0));
35             lineScale.StrokeThickness = 1;
36         }
37 
38         lineScale.X2 = 200 - 180 * Math.Cos(i * Math.PI / 180);
39         lineScale.Y2 = 200 - 180 * Math.Sin(i * Math.PI / 180);
40 
41         this.gaugeCanvas.Children.Add(lineScale);
42     }
43 }

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

5、指針繪制

做表盤的指針,可以有很多方案,網上有許多人說,用圖片代替,但是圖片一旦放大后,就會變得模糊,因此,我還是自己動手,做了一個簡單的指針,同樣是采用Path方法,使用路徑繪圖,XAML代碼如下,指針主要由2條直線和一根弧組成,使用了橙色填充。

 1 <Path x:Name="indicatorPin" Fill="Orange">
 2     <Path.Data>
 3         <PathGeometry>
 4             <PathGeometry.Figures>
 5                 <PathFigure StartPoint="200,195" IsClosed="True">
 6                     <PathFigure.Segments>
 7                         <LineSegment Point="20,200"/>
 8                         <LineSegment Point="200,205"/>
 9                     </PathFigure.Segments>
10                 </PathFigure>
11             </PathGeometry.Figures>
12         </PathGeometry>
13     </Path.Data>
14 </Path>

 很多表盤中間有一個指示數值的,這里,我也用一個文本塊來仿制一下,XAML語言如下,注意,文本塊的位置要分配好。

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

 最終外觀如下圖所示:

6、讓指針轉起來

指針的轉動,很明顯,是以(200,200)為圓心,各種角度轉動,使用了RotateTransform和DoubleAnimation實現轉動動畫。動畫的時間長度根據角度大小分配,1度8個毫秒。轉動的角度大小目前是隨機生成的,在此,我將轉動動畫寫在canvas_MouseDown事件里面。C#代碼如下:

 1 private void Canvas_MouseDown(object sender, MouseButtonEventArgs e)
 2 {
 3     RotateTransform rt = new RotateTransform();
 4     rt.CenterX = 200;
 5     rt.CenterY = 200;
 6 
 7     this.indicatorPin.RenderTransform = rt;
 8 
 9     angelCurrent = angleNext;
10     Random random = new Random();
11     angleNext = random.Next(180);
12 
13     double timeAnimation = Math.Abs(angelCurrent - angleNext) * 8;
14     DoubleAnimation da = new DoubleAnimation(angelCurrent, angleNext, new Duration(TimeSpan.FromMilliseconds(timeAnimation)));
15     da.AccelerationRatio = 1;
16     rt.BeginAnimation(RotateTransform.AngleProperty, da);

最終效果如下,終於做完了,享受一下成果!!

總結心得

此表盤目前雖然很簡單,但是自己一步一步思考然后做出來的,后面如果需要添加定制更加強大的功能,相信得心應手的。第一次寫博客,比想像中的難多了,感覺很多東西都難以表述清楚。同時為了更好的表達效果,用了visio制圖,和MathType公式編輯器,還用了錄屏軟件錄制窗口視頻,然后用迅雷看看截出gif圖,最后將gif圖處理一下,發布至博客里面,着實不容易,相信后面會越來越容易的。

同時,下一目標,將此表盤美化和封裝成用戶控件,供項目調用。

下下一目標,制作圖表控件,敬請關注!!


免責聲明!

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



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