【WPF】PID控制器(MVVM模式)

2915b39fc32582ee924b41419d9e16f7.png

程序框架

8739b6fa85901c91976727299dfbd34d.png

主界面

部分代码:

  1. PID控制器

namespace PidControllerWpf.Models
{
    public class PidController
    {
        public float ProportionalGain { get; private set; } = 0;  //比例增益
        public float IntegralGain { get; private set; } = 0; //积分增益
        public float DerivativeGain { get; private set; } = 0; //微分增益
        public float IntegralTerm { get; private set; } = 0; //积分项


        private float MaxPv = 0; 
        private float MinPv = 0; 
        //构造函数  
        public PidController(float minValue, float maxValue)
        {   //Pv值范围
            this.MaxPv = maxValue; 
            this.MinPv = minValue; 
            //PID参数初始化
            this.ProportionalGain = -0.8f; 
            this.IntegralGain = 1.0f; 
            this.DerivativeGain = 0.1f; 
        }
        //PID调节   pv:实际值      setpoint:目标值              
        public void ControlPv(ref float pv, float setpoint, System.TimeSpan deltaTime)
        {
            float error = setpoint - pv; //误差


            float proportionalTerm = this.ProportionalGain * error;   //比例项
            this.IntegralTerm += this.IntegralGain * error * (float)deltaTime.TotalSeconds; //积分项累加
            float derivativeTerm = this.DerivativeGain * error / (float)deltaTime.TotalSeconds; //微分项
            
            float output = proportionalTerm + this.IntegralTerm + derivativeTerm; //输出
            //超限纠正
            if (output >= this.MaxPv)
            {
                output = this.MaxPv; 
            }
            else if (output <= this.MinPv)
            {
                output = this.MinPv; 
            }
            pv = output; //更新实际值
        }
    }
}

2.带变量的命令 

<!-- 视图模型-命令  Commands -->
<!--  VariablesCommand.cs 带变量的命令 -->
using System; 
using System.Windows.Input;
using PidControllerWpf.ViewModels; 


namespace PidControllerWpf.Commands
{
    //带变量的命令
    public class VariablesCommand : ICommand
    {
        public PidVM PidVM { get; set; } //PID视图模型
        //构造函数:初始化PID视图模型
        public VariablesCommand(PidVM pidVM)
        {
            this.PidVM = pidVM; 
        }
        //事件处理器:可执行改变
        public event EventHandler CanExecuteChanged; 
        //
        public bool CanExecute(object parameter)
        {
            return true;
        }
        //执行:   parameter命令参数
        public void Execute(object parameter)
        {
            double delta = 1;      // 过程变量该变量  Delta for process variable
            string parameterString = parameter as string; //命令的参数
            if (parameterString == "IncreaseSP")//增加设定点位置
            {
                this.PidVM.ChangeSetpoint(delta);
            }
            else if (parameterString == "DecreaseSP")//减小设定点位置
            {
                this.PidVM.ChangeSetpoint(-delta);
            }
            else if (parameterString == "IncreasePV")
            {
                this.PidVM.ChangeProcessVariable(delta);//增加过程变量
            }
            else if (parameterString == "DecreasePV")
            {
                this.PidVM.ChangeProcessVariable(-delta);//减小过程变量
            }
            else
            {
                System.Windows.MessageBox.Show($"Incorrect parameter: {parameterString}", "Error"); 
            }
        }
    }
}






<!-- 定时器命令 TimerCommand.cs -->
using System.Windows; 
using System.Windows.Input; 
using PidControllerWpf.Views; 
using PidControllerWpf.UserControls; 
using PidControllerWpf.ViewModels; 


namespace PidControllerWpf.Commands
{   //定时器命令
    class TimerCommand : ICommand
    {
        private PidVM PidVM { get; set; } //要处理的PID视图模型
        //构造函数:传入PID视图模型
        public TimerCommand(PidVM PidVM)
        {
            this.PidVM = PidVM; 
        }
        //可执行改变事件处理器
        public event System.EventHandler CanExecuteChanged; 
        
        public bool CanExecute(object parameter)
        {
            return true; 
        }
        //执行 定时器命令:  带参数      开启,停止,重启
        public void Execute(object parameter)
        {
            string parameterString = parameter as string; //命令参数
            if (parameterString == "Start")
            {
                Start(); //开启
            }
            else if (parameterString == "Restart")
            {
                Restart(); //重启
            }
            else if (parameterString == "Stop")
            {
                Stop(); //停止
            }
            else
            {
                System.Windows.MessageBox.Show($"Incorrect parameter: {parameterString}", "Error"); //不正确的参数
            }
        }
        //开启定时器
        private void Start()
        {
            try
            {
                this.PidVM.TimerGraph.Start(); //开启更新画布定时器
                this.PidVM.GraphCanvasVM.IsTimerEnabled = true; //已启用更新画布定时器
            }
            catch (System.Exception e)
            {
                System.Windows.MessageBox.Show(e.Message, "Exception");
            }
        }
        //重启
        private void Restart()
        {
            try
            {
                this.PidVM.TimerGraph.Stop(); //停止更新图画布
                
                GraphCanvasVM gcvm = this.PidVM.GraphCanvasVM; //获取图画布视图句柄
                gcvm.IsTimerEnabled = false; //设置图画布视图的  定时器已禁用


                Graph2D.MinTimeGraph = Graph2D.InitMinTimeGraph; //时间轴最小值
                Graph2D.MaxTimeGraph = Graph2D.InitMaxTimeGraph; //时间轴最大值


                gcvm.Setpoint = 0; //设置目标点为0
                PidVM.TextBlockVM.SetPointTextBlock = gcvm.Setpoint.ToString(); 
                
                gcvm.ProcessVariable = 0; //设置过程变量为0
                PidVM.TextBlockVM.ProcessVariableTextBlock = gcvm.ProcessVariable.ToString(); 


                gcvm.Time = 0; //设置时间轴t=0
                PidVM.TextBlockVM.TimeTextBlock = gcvm.Time.ToString();


                // Set reference point to be able to change SP while timer isn't enabled
                Point refpoint = new Point(gcvm.SetpointLeft, gcvm.SetpointTop + 2.5); //参考点
                gcvm.SpRefPoint = refpoint; //设置参考点


                gcvm.ClearListOfLines(); //清空曲线
            }
            catch (System.Exception e)
            {
                System.Windows.MessageBox.Show(e.Message, "Exception");
            }
        }
        //停止
        private void Stop()
        {
            try
            {
                this.PidVM.TimerGraph.Stop(); 
                GraphCanvasVM gcvm = this.PidVM.GraphCanvasVM; //获取画布句柄
                gcvm.IsTimerEnabled = false; //定时器已禁用


                // Set reference point to be able to change SP while timer isn't enabled
                //设置参考点能够改变SP,当定时器禁用时
                Point refpoint = new Point(gcvm.SetpointLeft, gcvm.SetpointTop + 2.5); //
                gcvm.SpRefPoint = refpoint; 
            }
            catch (System.Exception e)
            {
                System.Windows.MessageBox.Show(e.Message, "Exception");
            }
        }
    }
}




<!-- 切换页面命令  带参数的   RedirectCommand.cs -->
using System.Windows; 
using System.Windows.Input; 
using PidControllerWpf.ViewModels; 


namespace PidControllerWpf.Commands
{
    public class RedirectCommand : ICommand//带参数的命令:切换视图
    {
        private MainWindowVM MainWindowVM; //主窗口视图模型
        //构造函数
        public RedirectCommand(MainWindowVM mainWindowVM)
        {
            this.MainWindowVM = mainWindowVM; //传入主窗口视图模型
        }


        public event System.EventHandler CanExecuteChanged; //可执行改变事件
        //判断可执行情况
        public bool CanExecute(object parameter)
        {
            return true; 
        }
        //执行切换视图命令:  2D画布 与  条状图
        public void Execute(object parameter)
        {
            string parameterString = parameter as string; 
            if (parameterString == "Graph2D")
            {
                this.MainWindowVM.OpenGraph2D(); //打开2D画布
            }
            else if (parameterString == "BarCharts")
            {
                this.MainWindowVM.OpenBarCharts(); //打开条状图
            }
            else 
            {
                System.Windows.MessageBox.Show($"Incorrect parameter: {parameterString}", "Error"); 
            }
        }
    }
}

3. PID视图模型

using System.Windows.Input;
using System.Windows.Threading; 
using PidControllerWpf.Views; 
using PidControllerWpf.UserControls; 
using PidControllerWpf.Models; 
using PidControllerWpf.Commands;


namespace PidControllerWpf.ViewModels
{
    /// <summary>
    /// Allows to connect UI and PidController 
    /// </summary>
    public class PidVM  //PID视图模型
    {
        public DispatcherTimer TimerGraph { get; private set; } = null;  //更新图画布定时器


        public TextBlockVM TextBlockVM { get; private set; }  //文本框视图模型
        public GraphCanvasVM GraphCanvasVM { get; private set; }  //图形画布视图模型
        
        private PidController PidController { get; set; } = null; //PID控制器
        
        public ICommand VariablesCommand { get; private set; } //带变量的命令
        public ICommand TimerCommand { get; private set; } //定时更新画布命令


        public static double DelaySeconds = 0.1;  //延迟0.1秒
        //构造函数
        public PidVM(ref TextBlockVM textBlockVM, ref GraphCanvasVM graphCanvasVM)
        {
            try
            {
                InitializeCommands(); 
                InitializeVM(ref textBlockVM, ref graphCanvasVM); 
                InitializeModelss(); 


                TimerGraph = new DispatcherTimer(); 
                TimerGraph.Tick += new System.EventHandler((o, e) => 
                {
                    GraphCanvasVM.Time += DelaySeconds; //更新Time轴
                    TextBlockVM.TimeTextBlock = $"{System.Math.Round(GraphCanvasVM.Time, 3)}"; //显示时间文本


                    AdjustPv(); //调整Pv值,更新文本框视图模型的过程变量,更新画布视图模型的过程变量
                    UpdatePidParams(); //更新PID参数
                }); 
                TimerGraph.Interval = System.TimeSpan.FromSeconds(DelaySeconds);//设置画布更新定时器间隔
            }
            catch (System.Exception e)
            {
                System.Windows.MessageBox.Show(e.Message, "Exception");
            }
        }


        #region Initialize instances 
        //初始化命令!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
        private void InitializeCommands()
        {
            this.VariablesCommand = new VariablesCommand(this);//初始化为带参数命令
            this.VariablesCommand = new VariablesCommand(this);
            this.TimerCommand = new TimerCommand(this);
            this.TimerCommand = new TimerCommand(this);
            this.TimerCommand = new TimerCommand(this);
        }
        //初始化视图模型
        private void InitializeVM(ref TextBlockVM textBlockVM, ref GraphCanvasVM graphCanvasVM)
        {
            this.TextBlockVM = textBlockVM;
            this.GraphCanvasVM = graphCanvasVM;
        }
        //初始化PID控制器:2D图的Pv范围
        private void InitializeModelss()
        {
            float minPv = (float)(Graph2D.MinPvGraph); 
            float maxPv = (float)(Graph2D.MaxPvGraph); 
            PidController = new PidController(minPv, maxPv); 
        }
        #endregion  // Initialize instances 


        #region Change variables  改变变量
        //设置目标点+delta
        public void ChangeSetpoint(double delta=1.0f)
        {
            double value = 0; 
            try
            {
                GetSpFromTextBox(ref value); //
                value += delta;//调整目标值
                SetBoundsForValue(ref value); //设置值的边界
                UpdateSpOnGraph(value); //更新图显示
            }
            catch (System.Exception e)
            {
                System.Windows.MessageBox.Show(e.Message, "Exception");
            }
        }
        //改变过程变量Pv
        public void ChangeProcessVariable(double delta=1.0f)
        {
            double value = 0; 
            try
            {
                GetPvFromTextBox(ref value); //从文本获取Pv值
                value += delta;//改变Pv值
                SetBoundsForValue(ref value); //设置Pv范围
                UpdatePvOnGraph(value); //更新2D图画布的Pv值
            }
            catch (System.Exception e)
            {
                System.Windows.MessageBox.Show(e.Message, "Exception");
            }
        }
        #endregion  // Change variables 


        #region Methods
        //从文本框获取Sp值
        private void GetSpFromTextBox(ref double value)
        {
            value = System.Convert.ToSingle(TextBlockVM.SetPointTextBlock);
        }
        //从文本框获取Pv值
        private void GetPvFromTextBox(ref double value)
        {
            value = System.Convert.ToSingle(TextBlockVM.ProcessVariableTextBlock);
        }
        //设置值的边界
        private void SetBoundsForValue(ref double value)
        {
            if (value > Graph2D.MaxPvGraph)
            {
                value = Graph2D.MaxPvGraph;
            }
            else if (value < Graph2D.MinPvGraph)
            {
                value = Graph2D.MinPvGraph;
            }
        }
        //PID调整Pv值
        private void AdjustPv()
        {
            float pv = (float)GraphCanvasVM.ProcessVariable; //图画布的过程变量pv值
            float sp = (float)GraphCanvasVM.Setpoint; //设置的目标点sp


            PidController.ControlPv(ref pv, sp, TimerGraph.Interval); //PID调节


            TextBlockVM.ProcessVariableTextBlock = pv.ToString(); //更新pv值的文本显示
            GraphCanvasVM.ProcessVariable = (double)pv;//更新图画布的过程变量pv值
        }
        #endregion  // Methods


        #region Updating 
        //更新2D图的设置点  和文本框设置点
        private void UpdateSpOnGraph(double value)
        {
            TextBlockVM.SetPointTextBlock = value.ToString(); 
            GraphCanvasVM.Setpoint = value;
        }
        //更新(2D图显示和文本框显示的)Pv实际值 
        private void UpdatePvOnGraph(double value)
        {
            TextBlockVM.ProcessVariableTextBlock = value.ToString(); 
            GraphCanvasVM.ProcessVariable = value;
        }
        //更新(文本框显示的)PID参数
        private void UpdatePidParams()
        {
            TextBlockVM.IntegralErrorTextBlock = PidController.IntegralTerm.ToString(); //误差积分项
            TextBlockVM.ProptionalGainTextBlock = PidController.ProportionalGain.ToString(); 
            TextBlockVM.IntegralGainTextBlock = PidController.IntegralGain.ToString(); 
            TextBlockVM.DerivativeGainTextBlock = PidController.DerivativeGain.ToString(); 
        }
        #endregion  // Updating 
    }
}

4. 主视图模型 MainWindowVM.cs

using System.Windows; 
using PidControllerWpf.Commands; 
using PidControllerWpf.Views; 


namespace PidControllerWpf.ViewModels
{
    public class MainWindowVM
    {   //主窗口
        private MainWindow MainWindow {get; set; }
        //切换视图命令
        public RedirectCommand RedirectCommand { get; private set; }
        //PID视图模型
        private PidVM pidVM;
        public PidVM PidVM
        {
            get { return pidVM; }
        }
        //文本框视图模型
        private TextBlockVM textBlockVM;
        public TextBlockVM TextBlockVM
        {
            get { return textBlockVM; }
        }
        //图画布视图模型
        private GraphCanvasVM graphCanvasVM;
        public GraphCanvasVM GraphCanvasVM
        {
            get { return graphCanvasVM; }
        }
        //主窗体视图模型
        public MainWindowVM(MainWindow mainWindow)
        {
            this.MainWindow = mainWindow; 


            this.textBlockVM = new TextBlockVM(); 
            this.graphCanvasVM = new GraphCanvasVM();
            this.pidVM = new PidVM(ref textBlockVM, ref graphCanvasVM); 


            this.RedirectCommand = new RedirectCommand(this); //切换图画布与条状图
        }
        //打开2D画布
        public void OpenGraph2D()
        {
            HideAllPages(); //隐藏所有页面
            this.MainWindow.Graph2D.Visibility = Visibility.Visible; //仅2D画布可见
        }
        //打开条状图
        public void OpenBarCharts()
        {
            HideAllPages(); 
            this.MainWindow.BarCharts.Visibility = Visibility.Visible; 
        }
        //隐藏画布和条状图
        private void HideAllPages()
        {
            this.MainWindow.Graph2D.Visibility = Visibility.Hidden; 
            this.MainWindow.BarCharts.Visibility = Visibility.Hidden; 
        }
    }
}

5. 主窗体界面 MainWindow.xaml

<!--###################  xaml 主界面 ##################### -->
<Window x:Class="PidControllerWpf.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:local="clr-namespace:PidControllerWpf.Views"
        xmlns:vm="clr-namespace:PidControllerWpf.ViewModels"
        xmlns:uc="clr-namespace:PidControllerWpf.UserControls"
        mc:Ignorable="d"
        WindowStartupLocation="CenterScreen"
        Title="PID Controller" 
        MaxHeight="450" MinHeight="450" MaxWidth="800" MinWidth="800">
  <!-- -->
    <Window.InputBindings>
    <!--增加或减小SP值  目标值-->
        <KeyBinding Command = "{Binding PidVM.VariablesCommand}" CommandParameter="IncreaseSP"
                    Key="W" />
        <KeyBinding Command = "{Binding PidVM.VariablesCommand}" CommandParameter="DecreaseSP"
                    Key="S" />
        <!-- 增加/减小PV值  -->
        <KeyBinding Command = "{Binding PidVM.VariablesCommand}" CommandParameter="IncreasePV"
                    Key="W" Modifiers="Ctrl" />
        <KeyBinding Command = "{Binding PidVM.VariablesCommand}" CommandParameter="DecreasePV"
                    Key="S" Modifiers="Ctrl" />
    <!-- 切换视图 -->
        <KeyBinding Command = "{Binding RedirectCommand}" CommandParameter="Graph2D"
                    Key="G" Modifiers="Alt" />
        <KeyBinding Command = "{Binding RedirectCommand}" CommandParameter="BarCharts"
                    Key="B" Modifiers="Alt" />
    </Window.InputBindings>
    <!-- -->
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height = "25" />
            <RowDefinition Height="*" />
            <RowDefinition Height = "100" />
        </ Grid.RowDefinitions >
        < Grid.ColumnDefinitions >
            <ColumnDefinition Width="250" />
            <ColumnDefinition Width = "*" />
        </ Grid.ColumnDefinitions >
    <!-- 菜单控件-->
        < uc:Menu x:Name="Menu" Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" />
    <!-- 配置控件-->
        <Border BorderThickness = "0.5" BorderBrush="Black" Grid.Column="0" Grid.Row="1"/>
        <uc:Configuration x:Name="Configuration" Grid.Row="1" Grid.Column="0" />
    <!-- 快捷键提示控件 -->
        <Border BorderThickness = "0.5" BorderBrush="Black" Grid.Column="0" Grid.Row="2"/>
        <uc:KeyboardShortcuts x:Name="KeyboardShortcuts" Grid.Column="0" Grid.Row="2" Margin="10,5,0,0" />
    <!--  图画布控件  条状图控件-->
        <Border BorderThickness = "0.5" BorderBrush="Black" Grid.Column="1" Grid.Row="1" Grid.RowSpan="2"/>
        <uc:Graph2D x:Name="Graph2D" Grid.Row="1" Grid.Column="1" Grid.RowSpan="2" />
        <uc:BarCharts x:Name="BarCharts" Grid.Row="1" Grid.Column="1" Grid.RowSpan="2" Visibility="Hidden" />
    </Grid>
</Window>


        <uc:Menu x:Name="Menu" Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" />


        <Border BorderThickness="0.5" BorderBrush="Black" Grid.Column="0" Grid.Row="1"/>
        <uc:Configuration x:Name="Configuration" Grid.Row="1" Grid.Column="0" />


        <Border BorderThickness="0.5" BorderBrush="Black" Grid.Column="0" Grid.Row="2"/>
        <uc:KeyboardShortcuts x:Name="KeyboardShortcuts" Grid.Column="0" Grid.Row="2" Margin="10,5,0,0" />


        <Border BorderThickness="0.5" BorderBrush="Black" Grid.Column="1" Grid.Row="1" Grid.RowSpan="2"/>
        <uc:Graph2D x:Name="Graph2D" Grid.Row="1" Grid.Column="1" Grid.RowSpan="2" />
        <uc:BarCharts x:Name="BarCharts" Grid.Row="1" Grid.Column="1" Grid.RowSpan="2" Visibility="Hidden" />
    </Grid>
</Window>

6.主窗体交互逻辑

<!-- 主窗口交互逻辑 -->
using System.Windows;
using PidControllerWpf.ViewModels;
using PidControllerWpf.UserControls;
<!-- -->
namespace PidControllerWpf.Views
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private MainWindowVM MainWindowVM { get; set; }


        public MainWindow()
        {
            InitializeComponent();
      <!--Loaded:当元素被布局、呈现并准备好进行交互时发生。-->
            Loaded += (o, e) => 
            {
                this.MainWindowVM = new MainWindowVM(this); //主窗口视图模型
        //关联UI与视图模型
                this.DataContext = this.MainWindowVM; 
                Menu.DataContext = this.MainWindowVM; 
                Configuration.DataContext = this.MainWindowVM; 
                Graph2D.DataContext = this.MainWindowVM; 
            }; 
        }
    }
}

7.用户控件

<!--################## 用户控件 UserControls######################## -->
<!--Graph2D.xaml  -->
<UserControl x:Class="PidControllerWpf.UserControls.Graph2D"
        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:PidControllerWpf.Views"
        xmlns:vm="clr-namespace:PidControllerWpf.ViewModels"
        mc:Ignorable="d">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="25" />
            <RowDefinition Height="*" />
            <RowDefinition Height="50" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="50" />
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="25" />
        </Grid.ColumnDefinitions>
    <!-- 过程变量标签Value -->
        <Canvas Name="ProcessVariableCanvas" Grid.Row="1" Grid.Column="0">
            <Label Name="ValueLabel" Content="Value">
                <Label.LayoutTransform>
                    <RotateTransform Angle="-90" />
                </Label.LayoutTransform>
            </Label>
        </Canvas>
    <!-- 时间变量标签 Time-->
        <Canvas Name="TimeValuesCanvas" Grid.Row="2" Grid.Column="1" >
            <Label Name="TimeLabel" Content="Time" />
        </Canvas>


        <!-- 图画布 Graph canvas -->
        <Border BorderThickness="0.5" BorderBrush="Black" Grid.Column="1" Grid.Row="1"/>
        <Canvas Name="GraphCanvas" Grid.Row="1" Grid.Column="1" >
            
            <!--  过程变量椭圆 PID Process variable ellipse-->
            <Ellipse Width="5" Height="5" Fill="Blue" x:Uid="ProcessVariableEllipse">
                <Ellipse.RenderTransform>
                    <TransformGroup>
                        <TranslateTransform X="{Binding GraphCanvasVM.ProcessVariableLeft, Mode=OneWay}" 
                            Y="{Binding GraphCanvasVM.ProcessVariableTop, Mode=OneWay}" />
                    </TransformGroup>
                </Ellipse.RenderTransform>
            </Ellipse>
            
            <!--  参考点椭圆 Setpoint ellipse-->
            <Ellipse Width="5" Height="5" Fill="Red" x:Uid="SetpointEllipse">
                <Ellipse.RenderTransform>
                    <TransformGroup>
                        <TranslateTransform X="{Binding GraphCanvasVM.SetpointLeft, Mode=OneWay}" 
                            Y="{Binding GraphCanvasVM.SetpointTop, Mode=OneWay}" />
                    </TransformGroup>
                </Ellipse.RenderTransform>
            </Ellipse>
        </Canvas>
    </Grid>
</UserControl>




<!-- 配置控件 Configuration.xaml  -->
<UserControl x:Class="PidControllerWpf.UserControls.Configuration"
        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:PidControllerWpf.Views"
        xmlns:vm="clr-namespace:PidControllerWpf.ViewModels"
        mc:Ignorable="d">
    <Canvas>
        <TextBlock Text="Configuration" FontSize="15" Height="25" Width="100" 
            Canvas.Left="25" Canvas.Top="10" />


        <!-- 参考点信息文本 Set point info-->
        <TextBlock Text="Set point:" Height="25" Width="100" 
            Canvas.Left="25" Canvas.Top="35" />
        <TextBlock Name="SetPoint" 
            Text="{Binding TextBlockVM.SetPointTextBlock, Mode=TwoWay}" 
            Height="25" Width="100" Canvas.Left="150" Canvas.Top="35" />


        <!--过程变量信息 Process variable info-->
        <TextBlock Text="Process variable:" Height="25" Width="100" 
            Canvas.Left="25" Canvas.Top="60" />
        <TextBlock Name="ProcessVariable" 
            Text="{Binding TextBlockVM.ProcessVariableTextBlock, Mode=TwoWay}" 
            Height="25" Width="100" Canvas.Left="150" Canvas.Top="60" />


        <!--积分误差信息 Integral error info-->
        <TextBlock Text="Integral error:" Height="25" Width="100" 
            Canvas.Left="25" Canvas.Top="85" />
        <TextBlock Name="IntegralError" 
            Text="{Binding TextBlockVM.IntegralErrorTextBlock, Mode=TwoWay}" 
            Height="25"  Width="100" Canvas.Left="150" Canvas.Top="85" />
        
        <!-- 比例增益信息 Proportional gain info-->
        <TextBlock Text="Proportional gain:" Height="25" Width="100" 
            Canvas.Left="25" Canvas.Top="110" />
        <TextBlock Name="ProportionalGain" 
            Text="{Binding TextBlockVM.ProptionalGainTextBlock, Mode=TwoWay}" 
            Height="25" Width="100" Canvas.Left="150" Canvas.Top="110" />


        <!--积分增益信息 Integral gain info-->
        <TextBlock Text="Integral gain:" Height="25" Width="100" 
            Canvas.Left="25" Canvas.Top="135" />
        <TextBlock Name="IntegralGain" 
            Text="{Binding TextBlockVM.IntegralGainTextBlock, Mode=TwoWay}" 
            Height="25" Width="100" Canvas.Left="150" Canvas.Top="135" />


        <!--微分增益信息  Derivative gain info-->
        <TextBlock Text="Derivative gain:" Height="25" Width="100" 
            Canvas.Left="25" Canvas.Top="160" />
        <TextBlock Name="DerivativeGain" 
            Text="{Binding TextBlockVM.DerivativeGainTextBlock, Mode=TwoWay}" 
            Height="25" Width="100" Canvas.Left="150" Canvas.Top="160" />


        <!--时间信息 Time info-->
        <TextBlock Text="Time:" Height="25" Width="100" 
            Canvas.Left="25" Canvas.Top="185" />
        <TextBlock Name="Time" 
            Text="{Binding TextBlockVM.TimeTextBlock, Mode=TwoWay}" 
            Height="25" Width="100"  Canvas.Left="150" Canvas.Top="185" />
    <!--开始  停止 重启         按钮 -->
        <Button Content="Start" Height="25" Width="50" Canvas.Left="25" Canvas.Top="210"
            Command="{Binding PidVM.TimerCommand}" CommandParameter="Start" />
        <Button Content="Stop" Height="25" Width="50" Canvas.Left="80" Canvas.Top="210"
            Command="{Binding PidVM.TimerCommand}" CommandParameter="Stop" />
        <Button Content="Restart" Height="25" Width="50" Canvas.Left="135" Canvas.Top="210"
            Command="{Binding PidVM.TimerCommand}" CommandParameter="Restart" />
    </Canvas>
</UserControl>


<!-- 快捷键提示控件 KeyboardShortcuts.xaml  -->
<UserControl x:Class="PidControllerWpf.UserControls.KeyboardShortcuts"
        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:PidControllerWpf.Views"
        xmlns:vm="clr-namespace:PidControllerWpf.ViewModels"
        mc:Ignorable="d">
    <Grid>
    <!-- 垂直布局堆叠面板 :三个标签-->
        <StackPanel Orientation="Vertical">
            <Label Content="Keyboard shortcuts" />
            <Label Content="W - Increase setpoint" />
            <Label Content="S - Decrease setpoint" />
        </StackPanel>
    </Grid>
</UserControl>






<!-- 菜单控件 Menu.xaml -->
<UserControl x:Class="PidControllerWpf.UserControls.Menu"
        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:PidControllerWpf.Views"
        xmlns:vm="clr-namespace:PidControllerWpf.ViewModels"
        mc:Ignorable="d">
    <Grid>
    <!-- 菜单  -->
        <Menu>
      <!-- 文件 -->
            <MenuItem Header="File">
        <!-- 新建 -->
                <MenuItem Header="New" />
        <!-- 打开 -->
                <MenuItem Header="Open">
          <!-- 2D图画布   命令RedirectCommand:参数Graph2D -->
                    <MenuItem Header="2D Graphs" Command="{Binding RedirectCommand}" CommandParameter="Graph2D" />
          <!-- 条状图  命令RedirectCommand:参数 BarCharts-->
                    <MenuItem Header="Bar Charts" Command="{Binding RedirectCommand}" CommandParameter="BarCharts" />
                </MenuItem>
        <!-- 分隔线 -->
                <Separator />
        <!-- 保存 -->
                <MenuItem Header="Save" />
        <!-- 保存所有 -->
                <MenuItem Header="Save All" />
        <!-- 分隔线-->
                <Separator />
        <!-- 关闭 -->
                <MenuItem Header="Close" />
        <!-- 关闭所有  -->
                <MenuItem Header="Close All" />
        <!-- 分割线 -->
                <Separator />
        <!-- 退出 -->
                <MenuItem Header="Exit" />
            </MenuItem>
      <!--编辑 -->
            <MenuItem Header="Edit">
                <MenuItem Header="Undo" />
                <MenuItem Header="Redo" />
                <Separator />
                <MenuItem Header="Settings" />
            </MenuItem>
            <!-- 帮助 -->
            <MenuItem Header="Help">
                <MenuItem Header="Docs">
                    <MenuItem Header="About" />
                    <MenuItem Header="How to use" />
                    <MenuItem Header="For developers" />
                </MenuItem>
                <MenuItem Header="GitHub repository" />
            </MenuItem>
        </Menu>
    </Grid>
</UserControl>

8. 用户控件Graph2D.xaml.cs 交互逻辑

using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Shapes;
using PidControllerWpf.ViewModels;


namespace PidControllerWpf.UserControls
{
    /// <summary>
    /// Interaction logic for Graph2D.xaml
    /// </summary>
    public partial class Graph2D : UserControl
    {
        private MainWindowVM MainWindowVM { get; set; } //主窗口视图句柄


        public static Canvas _GraphCanvas = null; //图画布句柄
        public static Canvas _ProcessVariableCanvas = null; //过程变量画布
        public static Canvas _TimeValuesCanvas = null; //时间至画布
        
        public const double InitMinTimeGraph = 0;  //初始最小时间
        public const double InitMaxTimeGraph = 10; //初始最大时间


        public static double MinPvGraph { get; set; } = 0.0; //最小过程变量
        public static double MaxPvGraph { get; set; } = 50.0; //最大过程变量


        public static int NumPvGraph { get; set; } = 10; //过程变量数


        public static double MinTimeGraph { get; set; } = InitMinTimeGraph; //最小时间
        public static double MaxTimeGraph { get; set; } = InitMaxTimeGraph; //最大时间


        public static int NumLinesTimeAxis { get; set; } = 10; //时间轴 条数


        public static double GraphWidth { get; set; } = 0.0; //图宽度
        public static double GraphHeight { get; set; } = 0.0; //图高度
        //构造函数
        public Graph2D()
        {
            InitializeComponent(); 


            _GraphCanvas = GraphCanvas; //图画布句柄
            _ProcessVariableCanvas = ProcessVariableCanvas; //过程变量画布句柄
            _TimeValuesCanvas = TimeValuesCanvas; //时间变量画布句柄
            //在布局、呈现元素并准备好进行交互时发生
            Loaded += (o, e) => 
            {
                this.MainWindowVM = (MainWindowVM)(this.DataContext); 


                GetActualGraphSizes(); //获取实际图尺寸
                PassSpAndPvToVM(); //传递参考点和过程变量 给视图模型
                DrawCoordinates();//绘制坐标轴
                SetLabelsForEachAxis(); //为每个轴设置标签
            }; 
        }


        #region Public methods
        /// <summary>
        /// Draws coordinates and grid for a graph 绘制图形的坐标和网格
        /// </summary>
        public static void DrawCoordinates()
        {
            ClearUiElements(); 
            DrawGridHorizontal(); 
            DrawGridVertical();
        }
        //绘制曲线
        public static void DrawLine(List<Line> lines)
        {
            foreach (var line in lines)
            {
                _GraphCanvas.Children.Add(line);
            }
        }
        //绘制线段
        public static void DrawLine(Line line)
        {
            _GraphCanvas.Children.Add(line);
        }
        #endregion  // Public methods 


        #region Private methods 
        //获取实际图的尺寸
        private void GetActualGraphSizes()
        {
            GraphWidth = GraphCanvas.ActualWidth; 
            GraphHeight = GraphCanvas.ActualHeight;
        }
        //传递sp 和 pv到视图模型
        private void PassSpAndPvToVM()
        {
            this.MainWindowVM.GraphCanvasVM.Setpoint = 0; 
            this.MainWindowVM.GraphCanvasVM.ProcessVariable = 0; 
        }
        //为每个轴设置标签
        private void SetLabelsForEachAxis()
        {
            Canvas.SetTop(ValueLabel, GraphHeight/2 - 12.5);
            Canvas.SetLeft(ValueLabel, 0);
            Canvas.SetTop(TimeLabel, (float)TimeValuesCanvas.ActualHeight - 32.5);
            Canvas.SetLeft(TimeLabel, GraphWidth / 2 - 17.5); 
        }
        //绘制水平网格
        private static void DrawGridHorizontal()
        {
            // Add horizontal lines and their labels to the canvas
            for (int i = 0; i < NumPvGraph - 1; i++)
            {
                // Horizontal line
                Line xAxis = new Line(); 
                xAxis.X1 = 0; 
                xAxis.X2 = GraphWidth; 
                xAxis.Y1 = (GraphHeight / NumPvGraph) + (i * GraphHeight / NumPvGraph);
                xAxis.Y2 = xAxis.Y1; 
                xAxis.Stroke = System.Windows.Media.Brushes.Black; 
                xAxis.StrokeThickness = 0.5;
                _GraphCanvas.Children.Add(xAxis);
                
                // Label for horizontal line 
                Label xLabel = new Label();
                xLabel.Content = $"{(MaxPvGraph - (MaxPvGraph - MinPvGraph) / NumPvGraph) - (i * (MaxPvGraph - MinPvGraph) / NumPvGraph)}"; 
                xLabel.Height = 25; 
                xLabel.Width = 50; 
                Canvas.SetTop(xLabel, xAxis.Y1 - 12.5);
                Canvas.SetLeft(xLabel, 25);
                _ProcessVariableCanvas.Children.Add(xLabel); 
            }
        }
        //绘制垂直网格
        private static void DrawGridVertical()
        {
            // Add vertical lines and their labels to the canvas
            for (int i = 0; i < NumLinesTimeAxis; i++)
            {
                // Vertical line
                Line yAxis = new Line(); 
                yAxis.X1 = (GraphWidth / NumLinesTimeAxis) + (i * GraphWidth / NumLinesTimeAxis);
                yAxis.X2 = yAxis.X1;
                yAxis.Y1 = 0;
                yAxis.Y2 = GraphHeight;
                yAxis.Stroke = System.Windows.Media.Brushes.Black; 
                yAxis.StrokeThickness = 0.5;
                _GraphCanvas.Children.Add(yAxis);


                // Label for vertical line 
                Label yLabel = new Label();
                yLabel.Content = $"{(MinTimeGraph + (MaxTimeGraph - MinTimeGraph) / NumLinesTimeAxis) + (i * (MaxTimeGraph - MinTimeGraph) / NumLinesTimeAxis)}"; 
                yLabel.Height = 25; 
                yLabel.Width = 50; 
                Canvas.SetTop(yLabel, 0);
                Canvas.SetLeft(yLabel, yAxis.X1 - 10);
                _TimeValuesCanvas.Children.Add(yLabel); 
            }
        }
        //清空UI元素
        private static void ClearUiElements()
        {
            List<UIElement> itemstoremove = new List<UIElement>();
            foreach (UIElement ui in _GraphCanvas.Children)
            {
                if (!ui.Uid.StartsWith("SetpointEllipse") && !ui.Uid.StartsWith("ProcessVariableEllipse"))
                {
                    itemstoremove.Add(ui);
                }
            }
            foreach (UIElement ui in itemstoremove)
            {
                _GraphCanvas.Children.Remove(ui);
            }
            _ProcessVariableCanvas.Children.Clear();
            _TimeValuesCanvas.Children.Clear();
        }
        #endregion  // Private methods 
    }
}

The End

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
MVVM模式下,PassWordBox的密码不能直接绑定到ViewModel的属性,因为密码是敏感信息,不应该以明文形式存储在内存中。因此,我们需要使用PasswordBox的SecureString属性来存储密码,并在ViewModel中创建一个SecureString类型的属性来接收密码。 首先,在XAML中,我们需要将PassWordBox的PasswordChanged事件与Command绑定,以便在密码发生变化时触发Command执行。例如: ``` <PasswordBox PasswordChanged="{Binding PasswordChangedCommand}" /> ``` 然后,在ViewModel中,我们需要创建一个SecureString类型的属性来接收密码,并创建一个Command来处理密码变化事件,例如: ``` public class LoginViewModel : INotifyPropertyChanged { private SecureString _securePassword; public SecureString SecurePassword { get { return _securePassword; } set { _securePassword = value; OnPropertyChanged(nameof(SecurePassword)); } } public ICommand PasswordChangedCommand => new RelayCommand<PasswordBox>((pb) => { SecurePassword = pb.SecurePassword; }); // INotifyPropertyChanged implementation... } ``` 在这个示例中,我们创建了一个SecurePassword属性来接收密码,并使用PasswordBox的SecurePassword属性将密码赋值给SecurePassword。我们还创建了一个PasswordChangedCommand来处理密码变化事件,该Command使用RelayCommand实现,并将PasswordBox作为参数传递。当密码发生变化时,Command会将SecurePassword属性设置为新密码。 需要注意的是,由于SecureString无法直接转换为字符串,因此我们需要在处理密码时使用相应的方法来转换或处理SecureString。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值