由于在实际项目中需要实时显示采集到的空气温湿度,土壤温湿度值,需要用比较显眼并且清楚明了的方式来展示,这里我们准备采用温度计的方式来进行。一方面是因为大家都熟悉这个,知道怎么去看;同时,温度计本身也比较好封装。以下就是封装好的效果及其调用代码(水银柱和刻度线都是有动画效果的,看上去比较逼真):
调用代码如下:
1: var data = new DataNotify();
2: data.MaxData = 30;
3: data.MinData = -15;
4:
5: data.MinRange = -15;
6: data.MaxRange = 75;
7:
8: data.CurrentData = 40;
9:
10: data.Title = "空气温度";
11: data.Unit = "℃";
12: data.ThemeSet = Theme.Red;
13:
14: var uc = new TemperatureControl(data);
15: uc.Margin = new Thickness(-620, 0, 10, 10);
16: Test.Children.Add(uc);
17:
18:
19: var data1 = new DataNotify();
20: data1.MaxData = 100;
21: data1.MinData = 0;
22: data1.MinRange = 0;
23: data1.MaxRange = 100;
24: data1.CurrentData = 83;
25: data1.Title = "空气湿度";
26: data1.Unit = "%";
27: data1.ThemeSet = Theme.Blue;
28:
29: var uc1 = new TemperatureControl(data1);
30: uc1.Margin = new Thickness(-207, 0, 10, 10);
31: Test.Children.Add(uc1);
32:
33: var data2 = new DataNotify();
34: data2.MaxData = 60;
35: data2.MinData = -10;
36: data2.MinRange = -10;
37: data2.MaxRange = 100;
38: data2.CurrentData = 36;
39: data2.Title = "土壤温度";
40: data2.Unit = "℃";
41: data2.ThemeSet = Theme.Orange;
42:
43: var uc2 = new TemperatureControl(data2);
44: uc2.Margin = new Thickness(213, 0, 10, 10);
45: Test.Children.Add(uc2);
46:
47: var data3 = new DataNotify();
48: data3.MaxData = 60;
49: data3.MinData = -10;
50: data3.MinRange = -10;
51: data3.MaxRange = 100;
52: data3.CurrentData = 36;
53: data3.Title = "土壤湿度";
54: data3.Unit = "%";
55: data3.ThemeSet = Theme.Mo;
56:
57: var uc3 = new TemperatureControl(data3);
58: uc3.Margin = new Thickness(633, 0, 10, 10);
59: Test.Children.Add(uc3);
由于调用代码相当简单,我就不再这里赘述了,下面着重讲解其实现方式。
首先在Silverlight项目中创建一个用户控件,我们命名为“TemperatureControl.xaml”,然后创建一个“ResData.xaml”资源文件项目,用于放置样式定义。
然后开始对项目进行布局,组织xaml代码,得到的页面显示如下:
XAML文件代码如下:
1:
2: xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3: xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4: xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
5: xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
6: mc:Ignorable="d"
7: x:Class="TinyFrame.Silverlight.TemperatureControl"
8: Loaded="UserControl_Loaded"
9: Width="200" Height="227">
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
通过第一副图的对比,我们能轻易的知道上图中那些空白的位置放什么东西。
由于在XAML中我采用了Binding来进行数据绑定,那么就展示下我用于数据绑定的类:
1: public class DataNotify : INotifyPropertyChanged
2: {
3: private double minData = 0; //最小值
4: public double MinData
5: {
6: get
7: {
8: return minData;
9: }
10: set
11: {
12: if (value != minData)
13: {
14: minData = value;
15: Notify("MinData");
16: }
17: }
18: }
19:
20: private double maxData = 1; //最大值
21: public double MaxData
22: {
23: get
24: {
25: return maxData;
26: }
27: set
28: {
29: if (value != maxData)
30: {
31: maxData = value;
32: Notify("MaxData");
33: }
34: }
35: }
36:
37: private double minRange = 0; //刻度最小范围
38: public double MinRange
39: {
40: get { return minRange; }
41: set
42: {
43: if (minRange != value)
44: {
45: minRange = value;
46: Notify("MinRange");
47: }
48: }
49: }
50:
51: private double maxRange = 90; //刻度最大范围
52: public double MaxRange
53: {
54: get { return maxRange; }
55: set
56: {
57: if (maxRange != value)
58: {
59: maxRange = value;
60: Notify("MaxRange");
61: }
62: }
63: }
64:
65: private double currentData; //当前值
66: public double CurrentData
67: {
68: get
69: {
70: return currentData;
71: }
72: set
73: {
74: if (value != currentData)
75: {
76: currentData = value;
77: Notify("CurrentData");
78: }
79: }
80: }
81:
82: private Brush isOK; //指标是否正常
83: public Brush IsOK
84: {
85: get
86: {
87: if (currentData >= minData && currentData <= maxData)
88: return new SolidColorBrush(Colors.Black);
89: else
90: return new SolidColorBrush(Colors.Red);
91: }
92: }
93:
94: private string title; //标题
95: public string Title
96: {
97: get
98: {
99: return title;
100: }
101: set
102: {
103: if (value != title)
104: {
105: title = value;
106: Notify("Title");
107: }
108: }
109: }
110:
111: private string unit; //单位
112: public string Unit
113: {
114: get
115: {
116: return unit;
117:
118: }
119: set
120: {
121: if (value != unit)
122: {
123: unit = value;
124: Notify("Unit");
125: }
126: }
127: }
128:
129: private Theme themeSet;
130: public Theme ThemeSet
131: {
132: get
133: {
134: return themeSet;
135: }
136: set
137: {
138: if (value != themeSet)
139: {
140: themeSet = value;
141: Notify("ThemeSet");
142: }
143: }
144: }
145:
146: private string bgImg; //背景默认值
147: public string BgImg
148: {
149: get
150: {
151: return bgImg;
152: }
153: set
154: {
155: if (value != bgImg)
156: {
157: bgImg = value;
158: Notify("BgImg");
159: }
160: }
161: }
162:
163: private string bgStep; //水银柱块默认值
164: public string BgStep
165: {
166: get
167: {
168: return bgStep;
169: }
170: set
171: {
172: if (value != bgStep)
173: {
174: bgStep = value;
175: Notify("BgStep");
176: }
177: }
178: }
179:
180: private string lineColor; //刻度范围条的颜色
181: public string LineColor
182: {
183: get
184: {
185: return lineColor;
186: }
187: set
188: {
189: if (lineColor != value)
190: {
191: lineColor = value;
192: Notify("LineColor");
193: }
194: }
195: }
196:
197: public event PropertyChangedEventHandler PropertyChanged;
198:
199: public void Notify(string propertyName)
200: {
201: if (PropertyChanged != null)
202: PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
203: }
204: }
上面的代码中,封装了温度计的所有需要的属性,包括刻度值,最大最小范围值,当前值,主题,标题,指标正常与否等。
最后就是具体的事件组织方法:
1: public partial class TemperatureControl : UserControl
2: {
3: public TemperatureControl(DataNotify dataNotify)
4: {
5: InitializeComponent();
6: this.dataNotify = dataNotify;
7:
8: //绑定数据上下文
9: this.DataContext = dataNotify;
10:
11: //左侧刻度线的最大值最小值
12: double tempMinData = dataNotify.MinData;
13: if (timer == null)
14: timer = new DispatcherTimer();
15: timer.Interval = new TimeSpan(100);
16: timer.Tick += (sender, e) =>
17: {
18: tempMinData++;
19: bRange.Dispatcher.BeginInvoke((Action)(() =>
20: {
21: bRange.Margin = GetThicknessByMaxMin();
22: bRange.Height = (tempMinData - dataNotify.MinData) * THeight / (dataNotify.MaxRange - dataNotify.MinRange);
23: txtMax.Text = tempMinData.ToString("0.0");
24: txtMin.Text = dataNotify.MinData.ToString("0.0");
25: }));
26: if (tempMinData == dataNotify.MaxData)
27: timer.Stop();
28: };
29: timer.Start();
30:
31: //当前值的显示
32: double tempMinRange = dataNotify.MinRange;
33: if (timerStep == null)
34: timerStep = new DispatcherTimer();
35: timerStep.Interval = new TimeSpan(100);
36: timerStep.Tick += (sender, e) =>
37: {
38: tempMinRange++;
39: double value;
40: if (dataNotify.MinRange < 0)
41: value = (THeight / (dataNotify.MaxRange - dataNotify.MinRange)) * tempMinRange + (Math.Abs(dataNotify.MinRange)) * (120 / (dataNotify.MaxRange - dataNotify.MinRange));
42: else
43: value = (THeight / (dataNotify.MaxRange - dataNotify.MinRange)) * tempMinRange;
44: if (value < 0)
45: value = 0;
46: bStep.Height = value;
47: if (Math.Abs(tempMinRange - dataNotify.CurrentData) < 0.6)
48: {
49: timerStep.Stop();
50: }
51: };
52: timerStep.Start();
53: }
54:
55: private readonly DispatcherTimer timer = null;
56: private readonly DispatcherTimer timerStep = null;
57: public DataNotify dataNotify;
58:
59: private const int THeight = 120; //水银柱高度为120
60: private const int FHeight = 30; //水银球高度为30
61:
62: //动态设置水银柱的Margin属性,以便于适应 带有正负值的场景
63: private Thickness GetThicknessByMaxMin()
64: {
65: double range = dataNotify.MaxRange - dataNotify.MinRange;
66: if (dataNotify.MinRange < 0)
67: return new Thickness(0, 0, 0, FHeight + Math.Abs(dataNotify.MinRange)*THeight/range + (dataNotify.MinData) * THeight / range);
68: else
69: return new Thickness(0, 0, 0, FHeight + (dataNotify.MinData) * THeight / range);
70: }
71:
72: private void UserControl_Loaded(object sender, RoutedEventArgs e)
73: {
74: //设置温度计的主题
75: switch (dataNotify.ThemeSet)
76: {
77: case Theme.Red:
78: dataNotify.BgImg = "Image/tem-red.png";
79: dataNotify.BgStep = "Image/step-red.png";
80: dataNotify.LineColor = "Red";
81: break;
82: case Theme.Blue:
83: dataNotify.BgImg = "Image/tem-blue.png";
84: dataNotify.BgStep = "Image/step-blue.png";
85: dataNotify.LineColor = "Blue";
86: break;
87:
88: case Theme.Mo:
89: dataNotify.BgImg = "Image/tem-mo.png";
90: dataNotify.BgStep = "Image/step-mo.png";
91: dataNotify.LineColor = "#00ACAE";
92: break;
93:
94: case Theme.Yellow:
95: dataNotify.BgImg = "Image/tem-yellow.png";
96: dataNotify.BgStep = "Image/step-yellow.png";
97: dataNotify.LineColor = "#849C00";
98: break;
99:
100: case Theme.Orange:
101: dataNotify.BgImg = "Image/tem-orange.png";
102: dataNotify.BgStep = "Image/step-orange.png";
103: dataNotify.LineColor = "#C88600";
104: break;
105:
106: case Theme.Green:
107: dataNotify.BgImg = "Image/tem-green.png";
108: dataNotify.BgStep = "Image/step-green.png";
109: dataNotify.LineColor = "#178A00";
110: break;
111:
112: default:
113: dataNotify.BgImg = "Image/tem-red.png";
114: dataNotify.BgStep = "Image/step-red.png";
115: dataNotify.LineColor = "Red";
116: break;
117: }
118:
119: Draw(dataNotify.MinRange,dataNotify.MaxRange);
120: }
121:
122: //根据用户输入的最大刻度值,最小刻度值,来划刻度线
123: private void Draw(double minRange,double maxRange)
124: {
125: var step = (maxRange - minRange) / 120;
126: Line redLine = new Line();
127: redLine.X1 = 0;
128: redLine.Y1 = 0;
129: redLine.X2 = 0;
130: redLine.Y2 = 120;
131: redLine.StrokeThickness = 1;
132: redLine.Stroke = new SolidColorBrush(Colors.Black);
133: canvas1.Children.Add(redLine);
134: int j = 6;
135: for (int i = 0; i <= 6; i++)
136: {
137: Line line = new Line();
138: line.X1 = 0;
139: line.Y1 = i * 20;
140:
141: line.X2 =10;
142: line.Y2 = i * 20;
143:
144: TextBlock tb = new TextBlock();
145: tb.Margin = new Thickness(12,i*20-8,0,0);
146: tb.FontSize = 9;
147: tb.Text = (((maxRange - minRange) / 6) * j + minRange).ToString("0");
148:
149: line.StrokeThickness = 1;
150: line.Stroke = new SolidColorBrush(Colors.Black);
151: canvas1.Children.Add(line);
152: canvas1.Children.Add(tb);
153: for (int x = 0; x < 30; x++)
154: {
155: Line lineInner = new Line();
156: lineInner.X1 = 0;
157: lineInner.Y1 = (x + 1) * 4;
158:
159: lineInner.X2 = 6;
160: lineInner.Y2 = (x + 1) * 4;
161:
162: lineInner.StrokeThickness = 1;
163: lineInner.Stroke = new SolidColorBrush(Colors.Black);
164: canvas1.Children.Add(lineInner);
165: }
166:
167: j--;
168: }
169: }
170:
171: }
上面我加了一部分注释,解释的比较清楚了。由于写这个温度计的时候,我们时刻需要测量好屏幕上的水银柱高度和当前值的对应关系,所以里面有比较多的运算,具体的运算方式还希望能够自己推敲。代码我将在下一篇中附上。