<Window x:Class="_9696999.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:_9696999"
mc:Ignorable="d"
Title="MainWindow" Height="650" Width="1200">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- 通讯控制栏 -->
<GroupBox Header="通讯控制" Grid.Row="0" Margin="10">
<StackPanel Orientation="Horizontal" Height="40">
<TextBlock Text="MX-COMPONENT站号:" Margin="10,0,0,0" VerticalAlignment="Center"/>
<TextBox x:Name="txtStationNumber" Width="50" Margin="10,0,0,0" VerticalAlignment="Center" Text="1"/>
<Button x:Name="btnConnect" Content="连接PLC" Width="80" Margin="10" Click="Connect_Click"/>
<Button x:Name="btnDisconnect" Content="断开连接" Width="80" Margin="10" Click="Disconnect_Click" IsEnabled="False"/>
<TextBlock x:Name="tbConnectionStatus" Text="未连接" Margin="10" VerticalAlignment="Center"/>
</StackPanel>
</GroupBox>
<!-- 数据监控区域 -->
<GroupBox Header="点位数据监控" Grid.Row="1" Margin="10">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- 第一列 -->
<StackPanel Grid.Column="0">
<!-- 电机点位数据监控 -->
<GroupBox Header="电机点位监控" Margin="5">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- 监控点位配置栏 -->
<StackPanel Grid.Column="0" Margin="5">
<TextBlock Text="监控点位配置" FontWeight="Bold" Margin="0,0,0,10"/>
<TextBlock Text="监控点位名称:" Margin="0,5"/>
<TextBox x:Name="txtPointName" Margin="0,2,0,10" Width="180"/>
<!-- 新增地址栏 -->
<TextBlock Text="地址:" Margin="0,5"/>
<TextBox x:Name="txtPointAddress" Margin="0,2,0,10" Width="180"/>
<TextBlock Text="标准值:" Margin="0,5"/>
<TextBox x:Name="txtStandardValue" Margin="0,2,0,10" Width="180"/>
<Button Content="添加监控点" Width="100" Margin="0,10" Click="AddMonitorPoint_Click"/>
</StackPanel>
<!-- 点位数据列表 -->
<ListView x:Name="lvMotorData" Grid.Column="1" Margin="5" ItemsSource="{Binding MotorDataList}">
<ListView.View>
<GridView>
<GridViewColumn Header="监控点位名称" Width="150">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding PointName}" VerticalAlignment="Center"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<!-- 新增地址列 -->
<GridViewColumn Header="地址" Width="100">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Address}" VerticalAlignment="Center"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="标准值" Width="100">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding StandardValue}" VerticalAlignment="Center"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="实际值" Width="100">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding ActualValue}" VerticalAlignment="Center"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="状态" Width="80">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Status}" VerticalAlignment="Center"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</Grid>
</GroupBox>
<!-- 通讯日志 -->
<GroupBox Header="通讯日志" Margin="5">
<TextBox x:Name="txtCommunicationLog" IsReadOnly="True"
VerticalScrollBarVisibility="Auto" Height="150"/>
</GroupBox>
</StackPanel>
<!-- 第二列 -->
<StackPanel Grid.Column="1">
<!-- 设备节拍时间监控 -->
<GroupBox Header="设备节拍时间监控" Margin="5">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- 监控点位配置栏 -->
<StackPanel Grid.Column="0" Margin="5">
<TextBlock Text="节拍监控配置" FontWeight="Bold" Margin="0,0,0,10"/>
<TextBlock Text="部位别名称:" Margin="0,5"/>
<TextBox x:Name="txtSectionName" Margin="0,2,0,10" Width="180"/>
<TextBlock Text="地址:" Margin="0,5"/>
<TextBox x:Name="txtSectionAddress" Margin="0,2,0,10" Width="180"/>
<Button Content="添加监控部位" Width="120" Margin="0,10" Click="AddSection_Click"/>
</StackPanel>
<!-- 节拍时间柱状图 -->
<Canvas x:Name="canvasRhythmChart" Grid.Column="1" Margin="5"
Background="White" Height="200"/>
</Grid>
</GroupBox>
<!-- 伺服电机负载率监控 -->
<GroupBox Header="伺服电机负载率监控" Margin="5">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- 监控点位配置栏 -->
<StackPanel Grid.Column="0" Margin="5">
<TextBlock Text="负载监控配置" FontWeight="Bold" Margin="0,0,0,10"/>
<TextBlock Text="伺服电机名称:" Margin="0,5"/>
<TextBox x:Name="txtMotorName" Margin="0,2,0,10" Width="180"/>
<TextBlock Text="地址:" Margin="0,5"/>
<TextBox x:Name="txtMotorAddress" Margin="0,2,0,10" Width="180"/>
<Button Content="添加监控电机" Width="120" Margin="0,10" Click="AddMotor_Click"/>
</StackPanel>
<!-- 负载率柱状图 -->
<Canvas x:Name="canvasLoadChart" Grid.Column="1" Margin="5"
Background="White" Height="200"/>
</Grid>
</GroupBox>
</StackPanel>
</Grid>
</GroupBox>
</Grid>
</Window>using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using ActUtlType64Lib;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Timers;
using System.Windows.Threading;
using System.Collections.ObjectModel;
namespace _9696999
{
public partial class MainWindow : Window
{
private PlcMonitorViewModel _viewModel;
private ActUtlType64 _plc;
private DispatcherTimer _readTimer;
private bool _isConnected = false;
// 存储上一次的值用于比较变化
private ObservableCollection<float> _lastMotorValues = new ObservableCollection<float>();
private ObservableCollection<float> _lastRhythmValues = new ObservableCollection<float>();
private ObservableCollection<float> _lastLoadValues = new ObservableCollection<float>();
public MainWindow()
{
InitializeComponent();
_viewModel = new PlcMonitorViewModel();
DataContext = _viewModel;
InitializePlc();
}
private void InitializePlc()
{
try
{
_plc = new ActUtlType64();
// 不再在此处设置站号,将在连接时根据用户输入设置
}
catch (Exception ex)
{
MessageBox.Show($"初始化PLC通信失败: {ex.Message}\n请确保已安装MX Component运行时库。");
}
}
// 连接PLC
private void Connect_Click(object sender, RoutedEventArgs e)
{
// 验证站号输入
if (!int.TryParse(txtStationNumber.Text, out int stationNumber) || stationNumber < 1 || stationNumber > 255)
{
MessageBox.Show("请输入有效的站号 (1-255)");
return;
}
try
{
// 设置站号
_plc.ActLogicalStationNumber = stationNumber;
int result = _plc.Open();
if (result == 0) // 返回0表示成功
{
_isConnected = true;
tbConnectionStatus.Text = $"已连接 (站号: {stationNumber})";
btnConnect.IsEnabled = false;
btnDisconnect.IsEnabled = true;
txtStationNumber.IsEnabled = false; // 连接后禁用站号输入
StartMonitoring();
AddLog($"PLC连接成功 (站号: {stationNumber})");
}
else
{
MessageBox.Show($"连接失败,错误代码: {result}");
AddLog($"PLC连接失败,错误代码: {result}");
}
}
catch (Exception ex)
{
MessageBox.Show($"连接失败: {ex.Message}");
AddLog($"PLC连接异常: {ex.Message}");
}
}
// 启动监控定时器
private void StartMonitoring()
{
_readTimer = new DispatcherTimer
{
Interval = TimeSpan.FromMilliseconds(200) // 200ms采集周期
};
_readTimer.Tick += async (s, e) => await ReadPlcDataAsync();
_readTimer.Start();
}
// 异步读取PLC数据
private async System.Threading.Tasks.Task ReadPlcDataAsync()
{
if (!_isConnected) return;
try
{
await System.Threading.Tasks.Task.Run(() =>
{
// 读取电机点位数据
ReadMotorData();
// 读取节拍时间数据
ReadRhythmData();
// 读取负载率数据
ReadLoadData();
});
}
catch (Exception ex)
{
AddLog($"读取数据异常: {ex.Message}");
}
}
// 读取电机点位数据
private void ReadMotorData()
{
for (int i = 0; i < _viewModel.MotorDataList.Count; i++)
{
short value = 0;
// 使用配置的地址读取数据
int result = _plc.ReadDeviceRandom2(_viewModel.MotorDataList[i].Address, 1, out value);
if (result == 0)
{
float actualValue = value;
Application.Current.Dispatcher.Invoke(() =>
{
var data = _viewModel.MotorDataList[i];
data.ActualValue = actualValue;
// 检查值是否发生变化
if (_lastMotorValues.Count <= i)
{
_lastMotorValues.Add(actualValue);
// 第一次读取时也设置状态
data.Status = (actualValue == data.StandardValue) ? "正常" : "异常";
// 如果是异常状态,记录日志
if (data.Status == "异常")
{
AddLog($"点位 {data.PointName} 异常: 实际值 {actualValue} ≠ 标准值 {data.StandardValue}");
}
}
else if (_lastMotorValues[i] != actualValue)
{
// 值发生变化,更新状态并记录日志
data.Status = (actualValue == data.StandardValue) ? "正常" : "异常";
if (data.Status == "异常")
{
AddLog($"点位 {data.PointName} 异常: 实际值 {actualValue} ≠ 标准值 {data.StandardValue}");
}
_lastMotorValues[i] = actualValue;
}
});
}
else
{
AddLog($"读取点位 {_viewModel.MotorDataList[i].PointName} 失败,错误代码: {result}");
}
}
}
// 读取节拍时间数据并绘制柱状图
private void ReadRhythmData()
{
for (int i = 0; i < _viewModel.RhythmDataList.Count; i++)
{
short value = 0;
int result = _plc.ReadDeviceRandom2(_viewModel.RhythmDataList[i].Address, 1, out value);
if (result == 0)
{
float rhythmValue = value / 10.0f; // 数值转换:25 → 2.5
Application.Current.Dispatcher.Invoke(() =>
{
// 使用转换后的值,而不是原始值
_viewModel.RhythmDataList[i].Value = rhythmValue;
DrawBarChart(canvasRhythmChart, _viewModel.RhythmDataList, true);
});
}
else
{
AddLog($"读取节拍数据 {_viewModel.RhythmDataList[i].Name} 失败,错误代码: {result}");
}
}
}
// 读取负载率数据并绘制柱状图
private void ReadLoadData()
{
for (int i = 0; i < _viewModel.LoadDataList.Count; i++)
{
short value = 0;
int result = _plc.ReadDeviceRandom2(_viewModel.LoadDataList[i].Address, 1, out value);
if (result == 0)
{
float loadValue = value;
Application.Current.Dispatcher.Invoke(() =>
{
_viewModel.LoadDataList[i].Value = value;
DrawBarChart(canvasLoadChart, _viewModel.LoadDataList, true);
});
}
else
{
AddLog($"读取负载数据 {_viewModel.LoadDataList[i].Name} 失败,错误代码: {result}");
}
}
}
// 绘制柱状图
private void DrawBarChart(Canvas canvas, ObservableCollection<ChartData> dataList, bool showValue)
{
// 确保在UI线程上执行
if (!Application.Current.Dispatcher.CheckAccess())
{
Application.Current.Dispatcher.Invoke(() => DrawBarChart(canvas, dataList, showValue));
return;
}
canvas.Children.Clear();
if (dataList.Count == 0) return;
double canvasWidth = canvas.ActualWidth;
double canvasHeight = canvas.ActualHeight;
double barWidth = (canvasWidth - 40) / dataList.Count - 10; // 为坐标轴留出空间
double maxValue = GetMaxValue(dataList);
// 绘制Y轴
Line yAxis = new Line
{
X1 = 30,
Y1 = 20,
X2 = 30,
Y2 = canvasHeight - 30,
Stroke = Brushes.Black,
StrokeThickness = 2
};
canvas.Children.Add(yAxis);
// 绘制X轴
Line xAxis = new Line
{
X1 = 30,
Y1 = canvasHeight - 30,
X2 = canvasWidth - 10,
Y2 = canvasHeight - 30,
Stroke = Brushes.Black,
StrokeThickness = 2
};
canvas.Children.Add(xAxis);
// 绘制Y轴刻度
for (int i = 0; i <= 5; i++)
{
double yPos = canvasHeight - 30 - (i * (canvasHeight - 50) / 5);
double value = i * maxValue / 5;
// 刻度线
Line tick = new Line
{
X1 = 25,
Y1 = yPos,
X2 = 30,
Y2 = yPos,
Stroke = Brushes.Black,
StrokeThickness = 1
};
canvas.Children.Add(tick);
// 刻度值
TextBlock tickValue = new TextBlock
{
Text = value.ToString("F1"),
FontSize = 10,
Foreground = Brushes.Black
};
Canvas.SetLeft(tickValue, 5);
Canvas.SetTop(tickValue, yPos - 8);
canvas.Children.Add(tickValue);
}
for (int i = 0; i < dataList.Count; i++)
{
double barHeight = (dataList[i].Value / maxValue) * (canvasHeight - 60);
double x = 40 + i * (barWidth + 10);
double y = canvasHeight - 30 - barHeight;
// 绘制柱形
System.Windows.Shapes.Rectangle rect = new System.Windows.Shapes.Rectangle
{
Width = barWidth,
Height = barHeight,
Fill = Brushes.SteelBlue,
Stroke = Brushes.Black,
StrokeThickness = 1
};
Canvas.SetLeft(rect, x);
Canvas.SetTop(rect, y);
canvas.Children.Add(rect);
// 绘制数值
if (showValue)
{
TextBlock text = new TextBlock
{
Text = dataList[i].Value.ToString("F1"),
FontSize = 10,
Foreground = Brushes.Black
};
Canvas.SetLeft(text, x + barWidth / 2 - 10);
Canvas.SetTop(text, y - 20);
canvas.Children.Add(text);
}
// 绘制名称
TextBlock nameText = new TextBlock
{
Text = dataList[i].Name,
FontSize = 10,
Foreground = Brushes.Black,
TextAlignment = TextAlignment.Center,
Width = barWidth + 10
};
Canvas.SetLeft(nameText, x - 5);
Canvas.SetTop(nameText, canvasHeight - 25);
canvas.Children.Add(nameText);
}
}
// 获取最大值用于比例计算
private float GetMaxValue(ObservableCollection<ChartData> dataList)
{
float max = 0;
foreach (var item in dataList)
{
if (item.Value > max) max = item.Value;
}
return max > 0 ? max : 1; // 避免除以零
}
// 添加日志
private void AddLog(string message)
{
Application.Current.Dispatcher.Invoke(() =>
{
txtCommunicationLog.AppendText($"{DateTime.Now:HH:mm:ss} {message}\n");
txtCommunicationLog.ScrollToEnd();
});
}
// 断开连接
private void Disconnect_Click(object sender, RoutedEventArgs e)
{
try
{
_plc.Close();
_isConnected = false;
_readTimer.Stop();
tbConnectionStatus.Text = "未连接";
btnConnect.IsEnabled = true;
btnDisconnect.IsEnabled = false;
txtStationNumber.IsEnabled = true; // 断开连接后允许修改站号
AddLog("PLC连接已断开");
}
catch (Exception ex)
{
MessageBox.Show($"断开连接失败: {ex.Message}");
AddLog($"断开连接异常: {ex.Message}");
}
}
// 添加监控点
private void AddMonitorPoint_Click(object sender, RoutedEventArgs e)
{
if (string.IsNullOrWhiteSpace(txtPointName.Text) ||
string.IsNullOrWhiteSpace(txtPointAddress.Text) || // 检查地址
string.IsNullOrWhiteSpace(txtStandardValue.Text))
{
MessageBox.Show("请填写完整的监控点信息");
return;
}
if (!float.TryParse(txtStandardValue.Text, out float standardValue))
{
MessageBox.Show("标准值必须是有效的数字");
return;
}
_viewModel.MotorDataList.Add(new MotorPointData
{
PointName = txtPointName.Text,
Address = txtPointAddress.Text, // 添加地址
StandardValue = standardValue,
ActualValue = 0,
Status = "未知"
});
// 清空输入框
txtPointName.Text = "";
txtPointAddress.Text = "";
txtStandardValue.Text = "";
}
// 添加节拍监控部位
private void AddSection_Click(object sender, RoutedEventArgs e)
{
if (string.IsNullOrWhiteSpace(txtSectionName.Text) ||
string.IsNullOrWhiteSpace(txtSectionAddress.Text))
{
MessageBox.Show("请填写完整的部位信息");
return;
}
_viewModel.RhythmDataList.Add(new ChartData
{
Name = txtSectionName.Text,
Address = txtSectionAddress.Text,
Value = 0
});
// 清空输入框
txtSectionName.Text = "";
txtSectionAddress.Text = "";
}
// 添加负载监控电机
private void AddMotor_Click(object sender, RoutedEventArgs e)
{
if (string.IsNullOrWhiteSpace(txtMotorName.Text) ||
string.IsNullOrWhiteSpace(txtMotorAddress.Text))
{
MessageBox.Show("请填写完整的电机信息");
return;
}
_viewModel.LoadDataList.Add(new ChartData
{
Name = txtMotorName.Text,
Address = txtMotorAddress.Text,
Value = 0
});
// 清空输入框
txtMotorName.Text = "";
txtMotorAddress.Text = "";
}
// 窗口关闭时清理资源
protected override void OnClosed(EventArgs e)
{
base.OnClosed(e);
if (_isConnected)
{
_plc.Close();
_readTimer.Stop();
}
}
}
}using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections.ObjectModel;
using System.ComponentModel;
namespace _9696999
{
public class MotorPointData : INotifyPropertyChanged
{
private string? _pointName;
private string? _address;
private float _standardValue;
private float _actualValue;
private string? _status;
public string? PointName
{
get => _pointName;
set { _pointName = value; OnPropertyChanged(nameof(PointName)); }
}
public string? Address
{
get => _address;
set { _address = value; OnPropertyChanged(nameof(Address)); }
}
public float StandardValue
{
get => _standardValue;
set { _standardValue = value; OnPropertyChanged(nameof(StandardValue)); }
}
public float ActualValue
{
get => _actualValue;
set { _actualValue = value; OnPropertyChanged(nameof(ActualValue)); }
}
public string? Status
{
get => _status;
set { _status = value; OnPropertyChanged(nameof(Status)); }
}
public event PropertyChangedEventHandler? PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
// 图表数据模型
public class ChartData
{
public string? Name { get; set; }
public string? Address { get; set; }
public float Value { get; set; }
}
// 主ViewModel
public class PlcMonitorViewModel
{
public ObservableCollection<MotorPointData> MotorDataList { get; set; }
public ObservableCollection<ChartData> RhythmDataList { get; set; }
public ObservableCollection<ChartData> LoadDataList { get; set; }
public PlcMonitorViewModel()
{
MotorDataList = new ObservableCollection<MotorPointData>();
RhythmDataList = new ObservableCollection<ChartData>();
LoadDataList = new ObservableCollection<ChartData>();
}
}
}
上面代码我想在优化一下,在三个配置点位部分添加一键保存配置(存到电脑D盘中)和一键删除配置
最新发布