WPF以其丰富灵活的控件样式设计,相较于WinForm而言,一直是工控组态软件的宠儿。经过前两文章的学习,已经对WPF开发工控组态软件有了一个基本的了解, 今天继续学习温度计的开发,仅供学习分享使用,如有不足之处,还请指正。
各位关注【老码识途】的朋友们,因出差期间,一直使用公司具有文件加密和监控功能的电脑,无法发布原创文章。现在持续两个月的出差终于结束了,又可以发布原创博文了,后续会持续更新。
涉及知识点
在本示例中,主要知识点如下:
-
WPF阴影效果,线性渐变的设置,主要设置温度计的边框,填充等效果,形成一种金属质感。
-
WPF依赖属性设置,主要设置最大温度,最低温度,和当前温度值
-
WPF线条绘制,主要用于刻度
温度计截图
本示例主要实现功能为自定义刻度值,以及水银条随着当前温度值变化。具体如下所示:
温度计源码
示例源码分为以下2个部分:
1. Thermometer控件
Thermometer控件布局
1 <UserControl x:Class="WpfControl.UserControls.Thermometer" 2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 5 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 6 xmlns:local="clr-namespace:WpfControl.UserControls" 7 mc:Ignorable="d" 8 d:DesignHeight="450" d:DesignWidth="150"> 9 <Grid> 10 <Grid.RowDefinitions> 11 <RowDefinition></RowDefinition> 12 <RowDefinition Height="auto"></RowDefinition> 13 </Grid.RowDefinitions> 14 <Rectangle StrokeThickness="7" RadiusX="40" RadiusY="15" Fill="White" /> 15 <Rectangle StrokeThickness="7" RadiusX="40" RadiusY="15"> 16 <Rectangle.Effect> 17 <DropShadowEffect ShadowDepth="0" Direction="0" BlurRadius="7" /> 18 </Rectangle.Effect> 19 <Rectangle.Stroke> 20 <LinearGradientBrush StartPoint="0,1" EndPoint="1,0"> 21 <LinearGradientBrush.RelativeTransform> 22 <RotateTransform Angle="40" CenterX="0.5" CenterY="0.5" /> 23 </LinearGradientBrush.RelativeTransform> 24 <GradientStop Color="Black" /> 25 <GradientStop Color="White" Offset="0.7" /> 26 </LinearGradientBrush> 27 </Rectangle.Stroke> 28 </Rectangle> 29 <TextBlock Text="℃" HorizontalAlignment="Center" VerticalAlignment="Top" FontWeight="Bold" FontSize="20" Margin="0, 20" Foreground="#555"/> 30 <Canvas Name="MainCanvas" Width="75" Margin="0,70" /> 31 <Border Width="10" RenderTransformOrigin="0.5,0.5" CornerRadius="5" Margin="0,50"> 32 <Border.Effect> 33 <DropShadowEffect ShadowDepth="0" Direction="0" Color="White" /> 34 </Border.Effect> 35 <Border.Background> 36 <LinearGradientBrush StartPoint="0,0" EndPoint="1,0"> 37 <GradientStop Color="lightGray" Offset="0" /> 38 <GradientStop Color="White" Offset="0.4" /> 39 <GradientStop Color="lightGray" Offset="1" /> 40 </LinearGradientBrush> 41 </Border.Background> 42 <Border Height="75" VerticalAlignment="Bottom" Name="BorValue"> 43 <Border.Background> 44 <LinearGradientBrush StartPoint="0,0" EndPoint="1,0"> 45 <GradientStop Color="#CD3333" /> 46 <GradientStop Color="#FFC0CB" Offset="0.4" /> 47 <GradientStop Color="#CD3333" Offset="1" /> 48 </LinearGradientBrush> 49 </Border.Background> 50 </Border> 51 </Border> 52 <Border Height="25" Width="25" CornerRadius="15" VerticalAlignment="Bottom" Margin="0 0 0 30"> 53 <Border.Effect> 54 <DropShadowEffect Direction="0" ShadowDepth="0" /> 55 </Border.Effect> 56 <Border.Background> 57 <RadialGradientBrush Center="0.3,0.2" GradientOrigin="0.4,0.4"> 58 <GradientStop Color="White" Offset="0" /> 59 <GradientStop Color="#CD3333" Offset="1" /> 60 </RadialGradientBrush> 61 </Border.Background> 62 </Border> 63 <TextBox Grid.Row="1" Text="{Binding Value, UpdateSourceTrigger=PropertyChanged}" HorizontalContentAlignment="Center" BorderThickness="0" BorderBrush="AliceBlue" VerticalAlignment="Bottom" FontSize="20" Name="ThermometerValue" /> 64 </Grid> 65 </UserControl>
依赖属性设置及后台生成刻度源码,如下所示:
1 namespace WpfControl.UserControls 2 { 3 /// <summary> 4 /// Thermometer.xaml 的交互逻辑 5 /// </summary> 6 public partial class Thermometer : UserControl 7 { 8 public int Minmum 9 { 10 get { return (int)GetValue(MinmumProperty); } 11 set { SetValue(MinmumProperty, value); } 12 } 13 14 public static readonly DependencyProperty MinmumProperty = 15 DependencyProperty.Register("Minmum", typeof(int), typeof(Thermometer), new PropertyMetadata(1, new PropertyChangedCallback(OnPropertyValueChanged))); 16 17 18 public int Maxmum 19 { 20 get { return (int)GetValue(MaxmumProperty); } 21 set { SetValue(MaxmumProperty, value); } 22 } 23 24 public static readonly DependencyProperty MaxmumProperty = 25 DependencyProperty.Register("Maxmum", typeof(int), typeof(Thermometer), new PropertyMetadata(10, new PropertyChangedCallback(OnPropertyValueChanged))); 26 27 28 29 public double Value 30 { 31 get { return (double)GetValue(ValueProperty); } 32 set { SetValue(ValueProperty, value);} 33 } 34 35 public static readonly DependencyProperty ValueProperty = 36 DependencyProperty.Register("Value", typeof(double), typeof(Thermometer), new PropertyMetadata(0.0, new PropertyChangedCallback(OnPropertyValueChanged))); 37 38 /// <summary> 39 /// 当属性值发生变化的时候直接更新UI内容 40 /// </summary> 41 /// <param name="d"></param> 42 /// <param name="e"></param> 43 private static void OnPropertyValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 44 { 45 (d as Thermometer)?.RefreshComponet(); 46 } 47 48 private double step = 10; 49 50 public Thermometer() 51 { 52 InitializeComponent(); 53 this.DataContext = this; 54 } 55 56 /// <summary> 57 /// 刷新温度计上面的内容适应定义大小 58 /// </summary> 59 /// <exception cref="NotImplementedException"></exception> 60 private void RefreshComponet() 61 { 62 // 两种方式触发:尺寸变化、区间变化 63 var h = this.MainCanvas.ActualHeight;//通过这个判断界面元素是否加载 64 if (h == 0) return; 65 double w = 75; 66 // 类型 67 double stepCount = Maxmum - Minmum;// 在这个区间内多少个间隔 68 step = h / (Maxmum - Minmum);// 每个间隔距离 69 70 this.MainCanvas.Children.Clear(); 71 72 for (int i = 0; i <= stepCount; i++) 73 { 74 Line line = new Line(); 75 line.Y1 = i * step; 76 line.Y2 = i * step; 77 line.Stroke = Brushes.Black; 78 line.StrokeThickness = 1; 79 this.MainCanvas.Children.Add(line); 80 81 if (i % 10 == 0) 82 { 83 line.X1 = 15; 84 line.X2 = w - 15; 85 86 // 添加文字 87 TextBlock text = new TextBlock 88 { 89 Text = (Maxmum - i).ToString(), 90 Width = 20, 91 TextAlignment = TextAlignment.Center, 92 FontSize = 9, 93 Margin = new Thickness(0, -5, -4, 0) 94 }; 95 Canvas.SetLeft(text, w - 15); 96 Canvas.SetTop(text, i * step); 97 this.MainCanvas.Children.Add(text); 98 99 // 添加文字 100 text = new TextBlock 101 { 102 Text = (Maxmum - i).ToString(), 103 Width = 20, 104 TextAlignment = TextAlignment.Center, 105 FontSize = 9, 106 Margin = new Thickness(-4, -5, 0, 0) 107 }; 108 Canvas.SetLeft(text, 0); 109 Canvas.SetTop(text, i * step); 110 this.MainCanvas.Children.Add(text); 111 } 112 else if (i % 5 == 0) 113 { 114 line.X1 = 20; 115 line.X2 = w - 20; 116 } 117 else 118 { 119 line.X1 = 25; 120 line.X2 = w - 25; 121 } 122 } 123 ValueChanged(); 124 } 125 126 127 private void ValueChanged() { 128 // 限定值的变化范围 129 var value = this.Value; 130 if (this.Value < this.Minmum) 131 value = this.Minmum; 132 if (this.Value > this.Maxmum) 133 value = this.Maxmum; 134 135 // 温度值与Border的高度的一个转换 136 var newValue = value - this.Minmum; 137 newValue *= step; 138 newValue += 20; 139 140 // 动画 141 DoubleAnimation doubleAnimation = new DoubleAnimation(newValue, TimeSpan.FromMilliseconds(500)); 142 this.BorValue.BeginAnimation(HeightProperty, doubleAnimation); 143 } 144 145 } 146 147 }
2. 控件调用
用户控件不可以独立展示,需要以窗口为载体,作为窗口的一部分展示,页面调用如下所示:
1 <Window 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 xmlns:local="clr-namespace:WpfControl" 7 xmlns:UserControls="clr-namespace:WpfControl.UserControls" x:Class="WpfControl.TestWindow3" 8 mc:Ignorable="d" 9 Title="工控组态软件--温度计" Height="450" Width="1000" Loaded="Window_Loaded"> 10 <Grid> 11 <Grid.ColumnDefinitions> 12 <ColumnDefinition></ColumnDefinition> 13 <ColumnDefinition></ColumnDefinition> 14 <ColumnDefinition></ColumnDefinition> 15 <ColumnDefinition></ColumnDefinition> 16 <ColumnDefinition></ColumnDefinition> 17 <ColumnDefinition></ColumnDefinition> 18 <ColumnDefinition></ColumnDefinition> 19 <ColumnDefinition></ColumnDefinition> 20 </Grid.ColumnDefinitions> 21 <Grid.RowDefinitions> 22 <RowDefinition></RowDefinition> 23 </Grid.RowDefinitions> 24 <UserControls:Thermometer Grid.Column="0" Grid.Row="0" x:Name="t1" Width="100" Height="370" HorizontalAlignment="Center" VerticalAlignment="Center"/> 25 <UserControls:Thermometer Grid.Column="1" Grid.Row="0" x:Name="t2" Width="100" Height="370" HorizontalAlignment="Center" VerticalAlignment="Center"/> 26 <UserControls:Thermometer Grid.Column="2" Grid.Row="0" x:Name="t3" Width="100" Height="370" HorizontalAlignment="Center" VerticalAlignment="Center"/> 27 <UserControls:Thermometer Grid.Column="3" Grid.Row="0" x:Name="t4" Width="100" Height="370" HorizontalAlignment="Center" VerticalAlignment="Center"/> 28 <UserControls:Thermometer Grid.Column="4" Grid.Row="0" x:Name="t5" Width="100" Height="370" HorizontalAlignment="Center" VerticalAlignment="Center"/> 29 <UserControls:Thermometer Grid.Column="5" Grid.Row="0" x:Name="t6" Width="100" Height="370" HorizontalAlignment="Center" VerticalAlignment="Center"/> 30 <UserControls:Thermometer Grid.Column="6" Grid.Row="0" x:Name="t7" Width="100" Height="370" HorizontalAlignment="Center" VerticalAlignment="Center"/> 31 <UserControls:Thermometer Grid.Column="7" Grid.Row="0" x:Name="t8" Width="100" Height="370" HorizontalAlignment="Center" VerticalAlignment="Center"/> 32 </Grid> 33 </Window>
控件赋值,在窗口加载时,为控件赋初始值,如下所示:
1 namespace WpfControl 2 { 3 /// <summary> 4 /// TestWindow3.xaml 的交互逻辑 5 /// </summary> 6 public partial class TestWindow3 : Window 7 { 8 public TestWindow3() 9 { 10 InitializeComponent(); 11 } 12 13 private void Window_Loaded(object sender, RoutedEventArgs e) 14 { 15 var controls = new Thermometer[8] { t1, t2 , t3, t4 , t5, t6 , t7, t8 }; 16 for (int i = 0; i < 8; i++) { 17 controls[i].Maxmum = 100; 18 controls[i].Minmum = -20; 19 controls[i].Value = 10*(i+1); 20 } 21 } 22 } 23 }
注意:在实际业务中,可以通过对应的传输协议【如:Modbus等】从硬件获取,并实时的显示在页面中。
源码下载
关注【老码识途】公众号,然后回复MCGS即可,如下所示:
备注
以上就是本篇文章的全部内容,旨在抛砖引玉,共同学习,一起进步。学习编程,从关注【老码识途】开始!!!