WPF工控组态软件之温度计【内附源码】

WPF以其丰富灵活的控件样式设计,相较于WinForm而言,一直是工控组态软件的宠儿。经过前两文章的学习,已经对WPF开发工控组态软件有了一个基本的了解, 今天继续学习温度计的开发,仅供学习分享使用,如有不足之处,还请指正。.

WPF工控组态软件之温度计【内附源码】

各位关注【老码识途】的朋友们,因出差期间,一直使用公司具有文件加密和监控功能的电脑,无法发布原创文章。现在持续两个月的出差终于结束了,又可以发布原创博文了,后续会持续更新。

涉及知识点

在本示例中,主要知识点如下:

  • 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等】从硬件获取,并实时的显示在页面中。

源码下载

关注【老码识途】公众号,然后回复MCGS即可,如下所示:

WPF工控组态软件之温度计【内附源码】