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

【WPF】PID控制器(MVVM模式)_第1张图片

程序框架

【WPF】PID控制器(MVVM模式)_第2张图片

主界面

部分代码:

  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.带变量的命令 



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"); 
            }
        }
    }
}







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");
            }
        }
    }
}





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
{
    /// 
    /// Allows to connect UI and PidController 
    /// 
    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



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



        


        
        


        
        


        
        
        
    

6.主窗体交互逻辑


using System.Windows;
using PidControllerWpf.ViewModels;
using PidControllerWpf.UserControls;

namespace PidControllerWpf.Views
{
    /// 
    /// Interaction logic for MainWindow.xaml
    /// 
    public partial class MainWindow : Window
    {
        private MainWindowVM MainWindowVM { get; set; }


        public MainWindow()
        {
            InitializeComponent();
      
            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.用户控件




    
        
            
            
            
        
        
            
            
            
        
    
        
            
        
    
        
            


        
        
        
            
            
            
                
                    
                        
                    
                
            
            
            
            
                
                    
                        
                    
                
            
        
    







    
        


        
        
        


        
        
        


        
        
        
        
        
        
        


        
        
        


        
        
        


        
        
        
    
        

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
{
    /// 
    /// Interaction logic for Graph2D.xaml
    /// 
    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
        /// 
        /// Draws coordinates and grid for a graph 绘制图形的坐标和网格
        /// 
        public static void DrawCoordinates()
        {
            ClearUiElements(); 
            DrawGridHorizontal(); 
            DrawGridVertical();
        }
        //绘制曲线
        public static void DrawLine(List 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 itemstoremove = new List();
            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

你可能感兴趣的:(wpf)