由于在实际项目中需要实时显示采集到的空气温湿度,土壤温湿度值,需要用比较显眼并且清楚明了的方式来展示,这里我们准备采用温度计的方式来进行。一方面是因为大家都熟悉这个,知道怎么去看;同时,温度计本身也比较好封装。以下就是封装好的效果及其调用代码(水银柱和刻度线都是有动画效果的,看上去比较逼真):

Silverlight自定义控件开发:温度计-LMLPHP

调用代码如下:

   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代码,得到的页面显示如下:

Silverlight自定义控件开发:温度计-LMLPHP

XAML文件代码如下:

   1:  <UserControl
   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:      <Border Style="{StaticResource GridBorder}">
  12:          <Grid x:Name="LayoutRoot" Background="White">
  13:              <Grid.RowDefinitions>
  14:              <RowDefinition Height="30"/>
  15:              <RowDefinition/>
  16:              </Grid.RowDefinitions>
  17:              <Border Grid.Row="0" Style="{StaticResource TopBorder}">
  18:                  <TextBlock Style="{StaticResource TxtTitle}" Text="{Binding Title}" />
  19:              </Border>
  20:              <Border Grid.Row="1">
  21:                  <Grid>
  22:                      <Grid.ColumnDefinitions>
  23:                          <ColumnDefinition Width="40"/>
  24:                          <ColumnDefinition/>
  25:                          <ColumnDefinition/>
  26:                      </Grid.ColumnDefinitions>
  27:                      <Border Name="bRange" Style="{StaticResource RangeBorder}" BorderBrush="{Binding LineColor}"  VerticalAlignment="Bottom">
  28:                      <Grid>
  29:                          <Grid.RowDefinitions>
  30:                              <RowDefinition/>
  31:                              <RowDefinition/>
  32:                          </Grid.RowDefinitions>
  33:                              <TextBlock Grid.Row="0" Style="{StaticResource TxtCommon}" Name="txtMax" Foreground="{Binding lineColor}" VerticalAlignment="Top" ></TextBlock>
  34:                              <TextBlock Grid.Row="1" Style="{StaticResource TxtCommon}" Name="txtMin" Foreground="{Binding lineColor}" VerticalAlignment="Bottom"></TextBlock>
  35:                      </Grid>
  36:                      </Border>
  37:                      <Image Margin="-14,18,0,0" Source="{Binding BgImg}"  Grid.Column="1"/>
  38:                      <Border Grid.Column="2">
  39:                      <Grid>
  40:                      <TextBlock Margin="20,40,6,0" Text="{Binding CurrentData}" Foreground="{Binding IsOK}" Style="{StaticResource TxtValue}" ></TextBlock>
  41:                      <TextBlock Margin="20,65,6,0" Text="{Binding MaxData}"  Style="{StaticResource TxtValue}"></TextBlock>
  42:                      <TextBlock Margin="20,90,6,0" Text="{Binding MinData}"  Style="{StaticResource TxtValue}"></TextBlock>
  43:                      <TextBlock Style="{StaticResource TxtIntro}" Text="当前值:" Margin="-20,40,0,0" Name="i1"  />
  44:                      <TextBlock Style="{StaticResource TxtIntro}" Text="最大值:" Margin="-20,65,0,0"  Name="i2" />
  45:                      <TextBlock Style="{StaticResource TxtIntro}" Text="最小值:" Margin="-20,90,0,0"  Name="i3" />
  46:                          </Grid>
  47:                      </Border>
  48:                      <Border Style="{StaticResource BorderUnit}" Grid.Column="1" Margin="22,24,0,0">
  49:                      <TextBlock   Name="t" Text="{Binding Unit}"  />
  50:                      </Border>
  51:                      <Image x:Name="bStep" Style="{StaticResource ImgStep}" Source="{Binding BgStep}" Margin="11,0,0,27" Grid.Column="1" />
  52:                      <Canvas Height="120" Background="White" HorizontalAlignment="Left" Margin="24,47,0,0" Name="canvas1" VerticalAlignment="Top" Width="32" OpacityMask="Black" Grid.Column="1" />
  53:                  </Grid>
  54:              </Border>
  55:          </Grid>
  56:      </Border>
  57:  </UserControl>

通过第一副图的对比,我们能轻易的知道上图中那些空白的位置放什么东西。

由于在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:      }

上面我加了一部分注释,解释的比较清楚了。由于写这个温度计的时候,我们时刻需要测量好屏幕上的水银柱高度和当前值的对应关系,所以里面有比较多的运算,具体的运算方式还希望能够自己推敲。代码我将在下一篇中附上。

05-11 18:20