WPF以其丰富灵活的控件样式设计,相较于WinForm而言,一直是工控组态软件的宠儿。经过前两文章的学习,已经对WPF开发工控组态软件有了一个基本的了解, 今天继续学习温度计的开发,仅供学习分享使用,如有不足之处,还请指正。
涉及知识点
在本示例中,主要知识点如下:
- WPF阴影效果,线性渐变的设置,主要设置温度计的边框,填充等效果,形成一种金属质感。
- WPF依赖属性设置,主要设置最大温度,最低温度,和当前温度值
- WPF线条绘制,主要用于刻度
温度计截图
本示例主要实现功能为自定义刻度值,以及水银条随着当前温度值变化。具体如下所示:
温度计源码
示例源码分为以下2个部分:
1. Thermometer控件
Thermometer控件布局
<UserControl x:Class="WpfControl.UserControls.Thermometer" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:WpfControl.UserControls" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="150"> <Grid> <Grid.RowDefinitions> <RowDefinition></RowDefinition> <RowDefinition Height="auto"></RowDefinition> </Grid.RowDefinitions> <Rectangle StrokeThickness="7" RadiusX="40" RadiusY="15" Fill="White" /> <Rectangle StrokeThickness="7" RadiusX="40" RadiusY="15"> <Rectangle.Effect> <DropShadowEffect ShadowDepth="0" Direction="0" BlurRadius="7" /> </Rectangle.Effect> <Rectangle.Stroke> <LinearGradientBrush StartPoint="0,1" EndPoint="1,0"> <LinearGradientBrush.RelativeTransform> <RotateTransform Angle="40" CenterX="0.5" CenterY="0.5" /> </LinearGradientBrush.RelativeTransform> <GradientStop Color="Black" /> <GradientStop Color="White" Offset="0.7" /> </LinearGradientBrush> </Rectangle.Stroke> </Rectangle> <TextBlock Text="℃" HorizontalAlignment="Center" VerticalAlignment="Top" FontWeight="Bold" FontSize="20" Margin="0, 20" Foreground="#555"/> <Canvas Name="MainCanvas" Width="75" Margin="0,70" /> <Border Width="10" RenderTransformOrigin="0.5,0.5" CornerRadius="5" Margin="0,50"> <Border.Effect> <DropShadowEffect ShadowDepth="0" Direction="0" Color="White" /> </Border.Effect> <Border.Background> <LinearGradientBrush StartPoint="0,0" EndPoint="1,0"> <GradientStop Color="lightGray" Offset="0" /> <GradientStop Color="White" Offset="0.4" /> <GradientStop Color="lightGray" Offset="1" /> </LinearGradientBrush> </Border.Background> <Border Height="75" VerticalAlignment="Bottom" Name="BorValue"> <Border.Background> <LinearGradientBrush StartPoint="0,0" EndPoint="1,0"> <GradientStop Color="#CD3333" /> <GradientStop Color="#FFC0CB" Offset="0.4" /> <GradientStop Color="#CD3333" Offset="1" /> </LinearGradientBrush> </Border.Background> </Border> </Border> <Border Height="25" Width="25" CornerRadius="15" VerticalAlignment="Bottom" Margin="0 0 0 30"> <Border.Effect> <DropShadowEffect Direction="0" ShadowDepth="0" /> </Border.Effect> <Border.Background> <RadialGradientBrush Center="0.3,0.2" GradientOrigin="0.4,0.4"> <GradientStop Color="White" Offset="0" /> <GradientStop Color="#CD3333" Offset="1" /> </RadialGradientBrush> </Border.Background> </Border> <TextBox Grid.Row="1" Text="{Binding Value, UpdateSourceTrigger=PropertyChanged}" HorizontalContentAlignment="Center" BorderThickness="0" BorderBrush="AliceBlue" VerticalAlignment="Bottom" FontSize="20" Name="ThermometerValue" /> </Grid> </UserControl>
依赖属性设置及后台生成刻度源码,如下所示:
namespace WpfControl.UserControls { /// <summary> /// Thermometer.xaml 的交互逻辑 /// </summary> public partial class Thermometer : UserControl { public int Minmum { get { return (int)GetValue(MinmumProperty); } set { SetValue(MinmumProperty, value); } } public static readonly DependencyProperty MinmumProperty = DependencyProperty.Register("Minmum", typeof(int), typeof(Thermometer), new PropertyMetadata(1, new PropertyChangedCallback(OnPropertyValueChanged))); public int Maxmum { get { return (int)GetValue(MaxmumProperty); } set { SetValue(MaxmumProperty, value); } } public static readonly DependencyProperty MaxmumProperty = DependencyProperty.Register("Maxmum", typeof(int), typeof(Thermometer), new PropertyMetadata(10, new PropertyChangedCallback(OnPropertyValueChanged))); public double Value { get { return (double)GetValue(ValueProperty); } set { SetValue(ValueProperty, value);} } public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(double), typeof(Thermometer), new PropertyMetadata(0.0, new PropertyChangedCallback(OnPropertyValueChanged))); /// <summary> /// 当属性值发生变化的时候直接更新UI内容 /// </summary> /// <param name="d"></param> /// <param name="e"></param> private static void OnPropertyValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { (d as Thermometer)?.RefreshComponet(); } private double step = 10; public Thermometer() { InitializeComponent(); this.DataContext = this; } /// <summary> /// 刷新温度计上面的内容适应定义大小 /// </summary> /// <exception cref="NotImplementedException"></exception> private void RefreshComponet() { // 两种方式触发:尺寸变化、区间变化 var h = this.MainCanvas.ActualHeight;//通过这个判断界面元素是否加载 if (h == 0) return; double w = 75; // 类型 double stepCount = Maxmum - Minmum;// 在这个区间内多少个间隔 step = h / (Maxmum - Minmum);// 每个间隔距离 this.MainCanvas.Children.Clear(); for (int i = 0; i <= stepCount; i++) { Line line = new Line(); line.Y1 = i * step; line.Y2 = i * step; line.Stroke = Brushes.Black; line.StrokeThickness = 1; this.MainCanvas.Children.Add(line); if (i % 10 == 0) { line.X1 = 15; line.X2 = w - 15; // 添加文字 TextBlock text = new TextBlock { Text = (Maxmum - i).ToString(), Width = 20, TextAlignment = TextAlignment.Center, FontSize = 9, Margin = new Thickness(0, -5, -4, 0) }; Canvas.SetLeft(text, w - 15); Canvas.SetTop(text, i * step); this.MainCanvas.Children.Add(text); // 添加文字 text = new TextBlock { Text = (Maxmum - i).ToString(), Width = 20, TextAlignment = TextAlignment.Center, FontSize = 9, Margin = new Thickness(-4, -5, 0, 0) }; Canvas.SetLeft(text, 0); Canvas.SetTop(text, i * step); this.MainCanvas.Children.Add(text); } else if (i % 5 == 0) { line.X1 = 20; line.X2 = w - 20; } else { line.X1 = 25; line.X2 = w - 25; } } ValueChanged(); } private void ValueChanged() { // 限定值的变化范围 var value = this.Value; if (this.Value < this.Minmum) value = this.Minmum; if (this.Value > this.Maxmum) value = this.Maxmum; // 温度值与Border的高度的一个转换 var newValue = value - this.Minmum; newValue *= step; newValue += 20; // 动画 DoubleAnimation doubleAnimation = new DoubleAnimation(newValue, TimeSpan.FromMilliseconds(500)); this.BorValue.BeginAnimation(HeightProperty, doubleAnimation); } } }
2. 控件调用
用户控件不可以独立展示,需要以窗口为载体,作为窗口的一部分展示,页面调用如下所示:
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfControl" xmlns:UserControls="clr-namespace:WpfControl.UserControls" x:Class="WpfControl.TestWindow3" mc:Ignorable="d" Title="工控组态软件--温度计" Height="450" Width="1000" Loaded="Window_Loaded"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition></ColumnDefinition> <ColumnDefinition></ColumnDefinition> <ColumnDefinition></ColumnDefinition> <ColumnDefinition></ColumnDefinition> <ColumnDefinition></ColumnDefinition> <ColumnDefinition></ColumnDefinition> <ColumnDefinition></ColumnDefinition> <ColumnDefinition></ColumnDefinition> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition></RowDefinition> </Grid.RowDefinitions> <UserControls:Thermometer Grid.Column="0" Grid.Row="0" x:Name="t1" Width="100" Height="370" HorizontalAlignment="Center" VerticalAlignment="Center"/> <UserControls:Thermometer Grid.Column="1" Grid.Row="0" x:Name="t2" Width="100" Height="370" HorizontalAlignment="Center" VerticalAlignment="Center"/> <UserControls:Thermometer Grid.Column="2" Grid.Row="0" x:Name="t3" Width="100" Height="370" HorizontalAlignment="Center" VerticalAlignment="Center"/> <UserControls:Thermometer Grid.Column="3" Grid.Row="0" x:Name="t4" Width="100" Height="370" HorizontalAlignment="Center" VerticalAlignment="Center"/> <UserControls:Thermometer Grid.Column="4" Grid.Row="0" x:Name="t5" Width="100" Height="370" HorizontalAlignment="Center" VerticalAlignment="Center"/> <UserControls:Thermometer Grid.Column="5" Grid.Row="0" x:Name="t6" Width="100" Height="370" HorizontalAlignment="Center" VerticalAlignment="Center"/> <UserControls:Thermometer Grid.Column="6" Grid.Row="0" x:Name="t7" Width="100" Height="370" HorizontalAlignment="Center" VerticalAlignment="Center"/> <UserControls:Thermometer Grid.Column="7" Grid.Row="0" x:Name="t8" Width="100" Height="370" HorizontalAlignment="Center" VerticalAlignment="Center"/> </Grid> </Window>
控件赋值,在窗口加载时,为控件赋初始值,如下所示:
namespace WpfControl { /// <summary> /// TestWindow3.xaml 的交互逻辑 /// </summary> public partial class TestWindow3 : Window { public TestWindow3() { InitializeComponent(); } private void Window_Loaded(object sender, RoutedEventArgs e) { var controls = new Thermometer[8] { t1, t2 , t3, t4 , t5, t6 , t7, t8 }; for (int i = 0; i < 8; i++) { controls[i].Maxmum = 100; controls[i].Minmum = -20; controls[i].Value = 10*(i+1); } } } }
注意:在实际业务中,可以通过对应的传输协议【如:Modbus等】从硬件获取,并实时的显示在页面中。