程序框架
主界面
部分代码:
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.RowDefinitions >
< Grid.ColumnDefinitions >
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