C# WPF 上位机开发:信号采集校准平台

By jerryxjr1220 at 2023-10-20 • 0人收藏 • 348人看过

screenshots.gif

<hc:GlowWindow x:Class="WPFCalibrationController.Views.MainWindow"
        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:model="clr-namespace:WPFCalibrationController.Models"
        xmlns:vm="clr-namespace:WPFCalibrationController.ViewModels"
        xmlns:view="clr-namespace:WPFCalibrationController.Views"
        xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
        xmlns:local="clr-namespace:WPFCalibrationController"
        xmlns:hc="https://handyorg.github.io/handycontrol"
        xmlns:scott="clr-namespace:ScottPlot.WPF;assembly=ScottPlot.WPF"
        d:DataContext="{d:DesignInstance Type=vm:MainViewModel}"
        mc:Ignorable="d"
        Title="Signal Calibration Platform" Height="500" Width="800" 
        FontFamily="JetBrains Mono" 
        FontSize="{StaticResource TextFontSize}">
    <Window.Resources>
        <vm:ReverseBooleanConverter x:Key="ReverseBooleanConverter" />
    </Window.Resources>
    <hc:SimplePanel>
        <ScrollViewer x:Name="scrollViewer" VerticalScrollBarVisibility="Auto">
            <StackPanel HorizontalAlignment="Center">
                <TextBlock Text="Signal Calibration Controller" Style="{StaticResource TextBlockDefaultPrimary}"
                           FontSize="20" Margin="20"/>
                <WrapPanel>
                    <TextBlock Text="Signal Count Setting" Style="{StaticResource TextBlockDefaultSuccess}" Margin="20, 5"/>
                    <ScrollBar Orientation="Horizontal" Minimum="50" Maximum="500" Width="200" Value="{Binding PointCount, Mode=TwoWay}"
                               Style="{StaticResource ScrollBarBaseStyle}" Background="Green" ViewportSize="{StaticResource HeadFontSize}" Foreground="White" 
                               ToolTip="{Binding PointCount}" />
                    <ToggleButton Content="Measure" Style="{StaticResource ToggleButtonSwitch}" IsChecked="False"
                                  x:Name="togglebutton" FontSize="24" Width="70" Margin="20, 0" Foreground="{StaticResource PrimaryBrush}" Checked="ToggleButton_OnChecked" Unchecked="ToggleButton_OnUnchecked"/>
                </WrapPanel>
                <WrapPanel>
                    <TextBox hc:InfoElement.Title="K" hc:InfoElement.TitlePlacement="Left" Width="100" Margin="20, 5"
                             Style="{StaticResource TextBoxExtend}" Foreground="{StaticResource PrimaryBrush}"
                             Text="{Binding K}" IsReadOnly="True"/>
                    <TextBox hc:InfoElement.Title="B" hc:InfoElement.TitlePlacement="Left" Width="100" Margin="20, 5"
                             Style="{StaticResource TextBoxExtend}" Foreground="{StaticResource PrimaryBrush}"
                             Text="{Binding B}" IsReadOnly="True"/>
                    <Button Content="Calculate" Style="{StaticResource ButtonPrimary}" Command="{Binding CalculateCommand}" CommandParameter="{Binding PointCount}"
                            />
                </WrapPanel>
                <scott:WpfPlot x:Name="scottPlot" Width="600" Height="320" />
                
            </StackPanel>
        </ScrollViewer>
    </hc:SimplePanel>
</hc:GlowWindow>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using ScottPlot;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Threading;
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.Mvvm.Messaging.Messages;
using HandyControl.Controls;
using ScottPlot.Legends;
using ScottPlot.Plottables;
using WPFCalibrationController.Models;
using WPFCalibrationController.ViewModels;
using MessageBox = HandyControl.Controls.MessageBox;

namespace WPFCalibrationController.Views;

/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : GlowWindow
{
    private MainViewModel mainViewModel;

    private DispatcherTimer timer;

    public MainWindow()
    {
        InitializeComponent();

        #region 注册并接受消息

        //WeakReferenceMessenger.Default.Register<CustomizedMessage>(this, (o, m) => { MessageBox.Show("Received!"); });

        #endregion
        
        mainViewModel = new MainViewModel();
        
        scottPlot.Plot.XLabel("TimeStamp");
        scottPlot.Plot.YLabel("Value");
        var sm = scottPlot.Plot.Add.Scatter(mainViewModel.MeasuredSignal.SignalIds!, mainViewModel.MeasuredSignal.SignalValues!, 
            getColor(mainViewModel.MeasuredSignal.SignalColor));
        sm.Label = mainViewModel.MeasuredSignal.SignalName;
        var sr = scottPlot.Plot.Add.Scatter(mainViewModel.ReferenceSignal.SignalIds!, mainViewModel.ReferenceSignal.SignalValues!,
            getColor(mainViewModel.ReferenceSignal.SignalColor));
        sr.Label = mainViewModel.ReferenceSignal.SignalName;
        
        scottPlot.Plot.Legend();
        var legend = scottPlot.Plot.GetLegend();
        legend.Alignment = Alignment.UpperLeft;
        

        timer = new DispatcherTimer();
        timer.Interval = TimeSpan.FromMilliseconds(500);
        timer.Tick += (sender, args) =>
        {
            mainViewModel.GeneratePoints(1);
            scottPlot.Plot.Clear();
            scottPlot.Plot.Add.Scatter(mainViewModel.MeasuredSignal.SignalIds!, mainViewModel.MeasuredSignal.SignalValues!, 
                getColor(mainViewModel.MeasuredSignal.SignalColor));
            scottPlot.Plot.Add.Scatter(mainViewModel.ReferenceSignal.SignalIds!, mainViewModel.ReferenceSignal.SignalValues!,
                getColor(mainViewModel.ReferenceSignal.SignalColor));
            scottPlot.Plot.AutoScale();
            scottPlot.Refresh();
        };

        this.DataContext = mainViewModel;
    }
    
    

    private Color getColor(SignalModel.SignalColorType colorType)
    {
        switch (colorType) {
            case SignalModel.SignalColorType.Red:
                return Colors.Red;
            case SignalModel.SignalColorType.Green:
                return Colors.Green;
            case SignalModel.SignalColorType.Blue:
                return Colors.Blue;
            case SignalModel.SignalColorType.Brown:
                return Colors.Brown;
            case SignalModel.SignalColorType.Purple:
                return Colors.Purple;
            case SignalModel.SignalColorType.Yellow:
                return Colors.Yellow;
            default:
                return Colors.Black;
        }
    }

    private void ToggleButton_OnChecked(object sender, RoutedEventArgs e)
    {
        timer.Start();
    }

    private void ToggleButton_OnUnchecked(object sender, RoutedEventArgs e)
    {
        timer.Stop();
        
    }
}
using System;
using System.ComponentModel;
using System.Linq;
using System.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.Mvvm.Messaging.Messages;
using WPFCalibrationController.Models;

namespace WPFCalibrationController.ViewModels;

public partial class MainViewModel : ObservableObject
{
    [ObservableProperty]
    private SignalModel _measuredSignal;
    [ObservableProperty]
    private SignalModel _referenceSignal;

    [ObservableProperty] private double _k;
    [ObservableProperty] private double _b;
    [ObservableProperty] private int _pointCount;
    public MainViewModel()
    {
        MeasuredSignal = new SignalModel();
        MeasuredSignal.SignalName = "Measured Signal";
        MeasuredSignal.SignalDescription = "The signal measured by the instrument from end point.";
        MeasuredSignal.SignalColor = SignalModel.SignalColorType.Blue;
        
        ReferenceSignal = new SignalModel();
        ReferenceSignal.SignalName = "Reference Signal";
        ReferenceSignal.SignalDescription = "The signal generated from the original source.";
        ReferenceSignal.SignalColor = SignalModel.SignalColorType.Red;

        PointCount = 100;
        
        GeneratePoints(PointCount);
        //发送消息
        //WeakReferenceMessenger.Default.Send(new CustomizedMessage(new MessageModel()));
    }

    public void GeneratePoints(int pointCount)
    {
        var lastidmeasure = MeasuredSignal.SignalTimestamps!.Count;
        var lastidreference = ReferenceSignal.SignalTimestamps!.Count;
        for (int i = 0; i < pointCount; i++)
        {
            var rand = new Random();
            var n = rand.NextDouble();
            var r = n + rand.NextDouble() * 0.1;
            var m = n * 5 + rand.NextDouble() * 0.2;
            
            MeasuredSignal.SignalValues!.Add(m);
            MeasuredSignal.SignalTimestamps!.Add(DateTime.Now);
            MeasuredSignal.SignalIds!.Add(i + lastidmeasure);
            ReferenceSignal.SignalValues!.Add(r);
            ReferenceSignal.SignalTimestamps!.Add(DateTime.Now);
            ReferenceSignal.SignalIds!.Add(i + lastidreference);
        }
    }
    
    [RelayCommand]
    public void Calculate(int pointsCount)
    {
        var x = MeasuredSignal.SignalValues!.TakeLast(pointsCount).ToArray();
        var y = ReferenceSignal.SignalValues!.TakeLast(pointsCount).ToArray();
        var k = 0.0;
        var b = 0.0;
        var n = x.Length;
        var sumx = 0.0;
        var sumy = 0.0;
        var sumxy = 0.0;
        var sumx2 = 0.0;
        for (int i = 0; i < n; i++) {
            sumx += x[i];
            sumy += y[i];
            sumxy += x[i] * y[i];
            sumx2 += x[i] * x[i];
        }
        k = (n * sumxy - sumx * sumy) / (n * sumx2 - sumx * sumx);
        b = (sumy - k * sumx) / n;
        K = k;
        B = b;

    }
    
}

public class ReverseBooleanConverter : BooleanConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
        return !(bool)value;
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
        return !(bool)value;
    }  //转换成反向值
}

//定义消息
// public class CustomizedMessage : ValueChangedMessage<MessageModel> // PropertyChangedMessage // RequestMessage ( Async + ..)
// {
//     public CustomizedMessage(MessageModel value) : base(value)
//     {
//     }
// }
using System;
using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel;
using ScottPlot;

namespace WPFCalibrationController.Models;

public partial class SignalModel : ObservableObject
{
    [ObservableProperty]
    private string? _signalName;

    [ObservableProperty]
    private ObservableCollection<double>? _signalValues;

    [ObservableProperty]
    private string? _signalDescription;

    [ObservableProperty]
    private ObservableCollection<DateTime>? _signalTimestamps;

    [ObservableProperty]
    private ObservableCollection<double>? _signalIds;

    [ObservableProperty]
    private SignalStatusType _signalStatus;

    [ObservableProperty]
    private SignalColorType _signalColor;

    public SignalModel()
    {
        SignalName = string.Empty;
        SignalValues = new();
        SignalDescription = string.Empty;
        SignalTimestamps = new();
        SignalIds = new();
        SignalStatus = SignalStatusType.Stopped;
        SignalColor = SignalColorType.Blue;
    }

    public enum SignalStatusType
    {
        Normal,
        Stopped,
        Error
    }
    
    public enum SignalColorType 
    {
        Blue,
        Green,
        Red,
        Yellow,
        Brown,
        Purple,
        Black
    }
}


3 个回复 | 最后更新于 2023-10-20
2023-10-20   #1

再增加一个校准后的信号,可以实时展示校准后信号对比源信号的误差。

using System;
using System.ComponentModel;
using System.Linq;
using System.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.Mvvm.Messaging.Messages;
using WPFCalibrationController.Models;

namespace WPFCalibrationController.ViewModels;

public partial class MainViewModel : ObservableObject
{
    [ObservableProperty]
    private SignalModel _measuredSignal;
    [ObservableProperty]
    private SignalModel _referenceSignal;
    [ObservableProperty]
    private SignalModel _calibratedSignal;

    [ObservableProperty] private double _k;
    [ObservableProperty] private double _b;
    [ObservableProperty] private int _pointCount;
    public MainViewModel()
    {
        MeasuredSignal = new SignalModel();
        MeasuredSignal.SignalName = "Measured Signal";
        MeasuredSignal.SignalDescription = "The signal measured by the instrument from end point.";
        MeasuredSignal.SignalColor = SignalModel.SignalColorType.Blue;
        
        ReferenceSignal = new SignalModel();
        ReferenceSignal.SignalName = "Reference Signal";
        ReferenceSignal.SignalDescription = "The signal generated from the original source.";
        ReferenceSignal.SignalColor = SignalModel.SignalColorType.Red;
        
        CalibratedSignal = new SignalModel();
        CalibratedSignal.SignalName = "Calibrated Signal";
        CalibratedSignal.SignalDescription = "The signal calculated with the parameters.";
        CalibratedSignal.SignalColor = SignalModel.SignalColorType.Green;

        PointCount = 100;
        
        GeneratePoints(PointCount);
        //发送消息
        //WeakReferenceMessenger.Default.Send(new CustomizedMessage(new MessageModel()));
    }

    public void GeneratePoints(int pointCount)
    {
        var lastidmeasure = MeasuredSignal.SignalTimestamps!.Count;
        var lastidreference = ReferenceSignal.SignalTimestamps!.Count;
        //var lastidcalibrated = CalibratedSignal.SignalTimestamps!.Count;
        for (int i = 0; i < pointCount; i++)
        {
            var rand = new Random();
            var n = rand.NextDouble();
            var r = n + rand.NextDouble() * 0.1;
            var m = n * 5 + rand.NextDouble() * 0.2;
            
            MeasuredSignal.SignalValues!.Add(m);
            MeasuredSignal.SignalTimestamps!.Add(DateTime.Now);
            MeasuredSignal.SignalIds!.Add(i + lastidmeasure);
            ReferenceSignal.SignalValues!.Add(r);
            ReferenceSignal.SignalTimestamps!.Add(DateTime.Now);
            ReferenceSignal.SignalIds!.Add(i + lastidreference);

            if (K != 0)
            {
                CalibratedSignal.SignalValues!.Add(K * m + B);
                CalibratedSignal.SignalTimestamps!.Add(DateTime.Now);
                CalibratedSignal.SignalIds!.Add(i + lastidmeasure);
            }
        }
    }
    
    [RelayCommand]
    public void Calculate(int pointsCount)
    {
        var x = MeasuredSignal.SignalValues!.TakeLast(pointsCount).ToArray();
        var y = ReferenceSignal.SignalValues!.TakeLast(pointsCount).ToArray();
        var k = 0.0;
        var b = 0.0;
        var n = x.Length;
        var sumx = 0.0;
        var sumy = 0.0;
        var sumxy = 0.0;
        var sumx2 = 0.0;
        for (int i = 0; i < n; i++) {
            sumx += x[i];
            sumy += y[i];
            sumxy += x[i] * y[i];
            sumx2 += x[i] * x[i];
        }
        k = (n * sumxy - sumx * sumy) / (n * sumx2 - sumx * sumx);
        b = (sumy - k * sumx) / n;
        K = k;
        B = b;
    }
    
}

public class ReverseBooleanConverter : BooleanConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
        return !(bool)value;
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
        return !(bool)value;
    }  //转换成反向值
}

//定义消息
// public class CustomizedMessage : ValueChangedMessage<MessageModel> // PropertyChangedMessage // RequestMessage ( Async + ..)
// {
//     public CustomizedMessage(MessageModel value) : base(value)
//     {
//     }
// }
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using ScottPlot;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Threading;
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.Mvvm.Messaging.Messages;
using HandyControl.Controls;
using ScottPlot.Legends;
using ScottPlot.Plottables;
using WPFCalibrationController.Models;
using WPFCalibrationController.ViewModels;
using MessageBox = HandyControl.Controls.MessageBox;

namespace WPFCalibrationController.Views;

/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : GlowWindow
{
    private MainViewModel mainViewModel;

    private DispatcherTimer timer;

    public MainWindow()
    {
        InitializeComponent();

        #region 注册并接受消息

        //WeakReferenceMessenger.Default.Register<CustomizedMessage>(this, (o, m) => { MessageBox.Show("Received!"); });

        #endregion
        
        mainViewModel = new MainViewModel();
        
        scottPlot.Plot.XLabel("TimeStamp");
        scottPlot.Plot.YLabel("Value");
        var sm = scottPlot.Plot.Add.Scatter(mainViewModel.MeasuredSignal.SignalIds!, mainViewModel.MeasuredSignal.SignalValues!, 
            getColor(mainViewModel.MeasuredSignal.SignalColor));
        sm.Label = mainViewModel.MeasuredSignal.SignalName;
        var sr = scottPlot.Plot.Add.Scatter(mainViewModel.ReferenceSignal.SignalIds!, mainViewModel.ReferenceSignal.SignalValues!,
            getColor(mainViewModel.ReferenceSignal.SignalColor));
        sr.Label = mainViewModel.ReferenceSignal.SignalName;
        
        scottPlot.Plot.Legend();
        var legend = scottPlot.Plot.GetLegend();
        legend.Alignment = Alignment.UpperLeft;
        

        timer = new DispatcherTimer();
        timer.Interval = TimeSpan.FromMilliseconds(500);
        timer.Tick += (sender, args) =>
        {
            mainViewModel.GeneratePoints(1);
            scottPlot.Plot.Clear();
            scottPlot.Plot.Add.Scatter(mainViewModel.MeasuredSignal.SignalIds!, mainViewModel.MeasuredSignal.SignalValues!, 
                getColor(mainViewModel.MeasuredSignal.SignalColor));
            scottPlot.Plot.Add.Scatter(mainViewModel.ReferenceSignal.SignalIds!, mainViewModel.ReferenceSignal.SignalValues!,
                getColor(mainViewModel.ReferenceSignal.SignalColor));
            scottPlot.Plot.Add.Scatter(mainViewModel.CalibratedSignal.SignalIds!, mainViewModel.CalibratedSignal.SignalValues!,
                getColor(mainViewModel.CalibratedSignal.SignalColor));
            scottPlot.Plot.AutoScale();
            scottPlot.Refresh();
        };

        this.DataContext = mainViewModel;
    }
    
    

    private Color getColor(SignalModel.SignalColorType colorType)
    {
        switch (colorType) {
            case SignalModel.SignalColorType.Red:
                return Colors.Red;
            case SignalModel.SignalColorType.Green:
                return Colors.Green;
            case SignalModel.SignalColorType.Blue:
                return Colors.Blue;
            case SignalModel.SignalColorType.Brown:
                return Colors.Brown;
            case SignalModel.SignalColorType.Purple:
                return Colors.Purple;
            case SignalModel.SignalColorType.Yellow:
                return Colors.Yellow;
            default:
                return Colors.Black;
        }
    }

    private void ToggleButton_OnChecked(object sender, RoutedEventArgs e)
    {
        timer.Start();
    }

    private void ToggleButton_OnUnchecked(object sender, RoutedEventArgs e)
    {
        timer.Stop();
        
    }
}


2023-10-20   #2

回复#1 @jerryxjr1220 :

赞一个,工业控制的路过,哈

2023-10-20   #3

回复#2 @admin :

我做了视频教程:

https://www.bilibili.com/video/BV1HM411R7fi/

登录后方可回帖

登 录
信息栏
 私人小站

本站域名

ChengXu.XYZ

投诉联系:  popdes@126.com



快速上位机开发学习,本站主要记录了学习过程中遇到的问题和解决办法及上位机代码分享

这里主要专注于学习交流和经验分享.
纯私人站,当笔记本用的,学到哪写到哪.
如果侵权,联系 Popdes@126.com

友情链接
Aardio官方
Aardio资源网


才仁机械


网站地图SiteMap

Loading...