WPF 上位机学习日记(六)— 按钮命令绑定 + 状态管理 + 状态颜色

WPF 上位机学习日记(六)— 按钮命令绑定 + 状态管理 + 状态颜色 WPF 上位机学习日记六— 按钮命令绑定 状态管理 状态颜色今日内容今天主要做了 3 件事按钮绑定 ICommand— Start/Stop/Pause/Emergency Stop 绑定到 ViewModel状态管理 INotifyPropertyChanged— 按钮联动 4 轴状态UI 自动刷新StatusToColorConverter— 状态值自动映射到颜色Running绿, StandBy灰 等一、遇到的坑 学到的知识1. 新 .cs 文件添加后XAML 找不到问题新建StatusToColorConverter.cs后XAML 报错The name does not exist in the namespace。原因WPF 的 XAML 设计器依赖编译后的程序集来解析代码中的类。新加 .cs 文件后需要先编译一次。解决dotnet build之后再打开 XAML 文件或者直接dotnet run运行。2. App.xaml 命名空间声明问题在 App.xaml 里引用 Helpers 文件夹下的类时需要声明对应的 XML 命名空间。正确 xmlns:helpersclr-namespace:UpperMachine.Helpers helpers:StatusToColorConverter x:Key.../ 错误 xmlns:localclr-namespace:UpperMachine ← local 只到 UpperMachine local:StatusToColorConverter .../ ← 找不到在 UpperMachine.Helpers 里clr-namespace:后面跟的是C# 的命名空间路径不是文件夹路径但通常文件夹和命名空间一致。3. 线圈 vs 寄存器类型地址前缀功能码写方法数据线圈Coils0x05WriteSingleCoil(地址, true/false)1 位 ON/OFF保持寄存器Holding Registers4x06/16WriteSingleRegister(地址, 数值)16 位0~65535它们地址空间独立——线圈地址 0 和寄存器地址 0 是两回事。4. 按钮状态互锁// 状态机设计Idle → Start → Running Running → Stop → Idle Running → Pause → IdlePause 后回 Idle Running → Emergency → Idle 任何状态 → Emergency → IdleE-Stop 不检查状态用Is_Running字段 方法前置条件实现if(Is_Running)return;// Start 防重按if(!Is_Running)return;// Stop/Pause 只有运行时有效// Emergency 不设条件——急停永远可用5. Modbus Slave 同时配线圈和寄存器使用 Modbus SlaveWitte时免费版只能选一种数据类型。选 Holding Registers 就看不到 Coils 配置。所以如果用线圈 → 在 Modbus Slave 里新建 Coils 表如果用寄存器 → 改回WriteSingleRegister地址 88~91不能两个同时用免费版限制二、今天新增/修改的文件文件 1:Models/PlcAddressMap.cs— 新增线圈地址常量/* * 新增内容 * 4 个控制命令的线圈地址 * 每个命令占一个独立的线圈位0~3 * WriteSingleCoil(地址, true) 发脉冲 */// 线圈地址 // 开始publicstaticushortCoil_Start0;// 停止publicstaticushortCoil_Stop1;// 暂停publicstaticushortCoil_Pause2;// 急停publicstaticushortCoil_EmergencyStop3;文件 2:Models/AxisData.cs— 加 INotifyPropertyChanged/* * 新增内容 * 1. AxisData 实现 INotifyPropertyChanged 接口 * 2. Status 属性改为字段属性模式设值时触发 OnPropertyChanged * 3. 这样 XAML 绑定 {Binding Axis1Data.Status} 才能自动刷新 */internalclassAxisData:INotifyPropertyChanged{privateAxisStatus_status;// 私有字段存真实值publicAxisStatus Status// 公开属性供 XAML 绑定{get_status;set{_statusvalue;OnPropertyChanged();}// 赋值时通知 WPF}publicAxisParamData{get;set;}newAxisParam();// 12 个 float 参数publiceventPropertyChangedEventHandler?PropertyChanged;protectedvoidOnPropertyChanged([CallerMemberName]string?namenull)PropertyChanged?.Invoke(this,newPropertyChangedEventArgs(name));}文件 3:Helpers/StatusToColorConverter.cs— 新增状态→颜色转换器/* * 完整新增文件 * * 作用 * 把 AxisStatus 枚举转成对应的画刷颜色 * XAML 绑定Foreground{Binding Axis1Data.Status, Converter...} * * 执行流程 * Status Running → INPC 通知 → WPF 调 Convert(Running) * → switch 匹配 → 返回 StatusRunningBrush绿色 * → UI 上状态文字变绿 */usingSystem.Globalization;usingSystem.Windows;usingSystem.Windows.Data;usingUpperMachine.Models;namespaceUpperMachine.Helpers{publicclassStatusToColorConverter:IValueConverter{// Convert从数据源→UI 方向的转换// value传进来的原始值这里是 AxisStatus 枚举// 返回值赋给 Foreground 属性的画刷publicobjectConvert(objectvalue,TypetargetType,objectparameter,CultureInfoculture){// value is AxisStatus status// → 判断 value 是不是 AxisStatus 类型// → 是则赋给 status 变量否则走最后 : 后面的兜底returnvalueisAxisStatusstatus?statusswitch{AxisStatus.RunningApplication.Current.FindResource(StatusRunningBrush),AxisStatus.StandByApplication.Current.FindResource(StatusStandByBrush),AxisStatus.StoppedApplication.Current.FindResource(StatusStoppedBrush),AxisStatus.ErrorApplication.Current.FindResource(StatusErrorBrush),_Application.Current.FindResource(TextBrush)// 兜底}:Application.Current.FindResource(TextBrush);// 非 AxisStatus}// ConvertBackUI→数据源 的逆转换不需要所以抛异常publicobjectConvertBack(...)thrownewNotImplementedException();}}文件 4:App.xaml— 注册转换器 新增颜色!-- 新增命名空间第 5 行 --xmlns:helpersclr-namespace:UpperMachine.Helpers!-- ↑ 让 XAML 能找到 Helpers 文件夹下的类 --!-- 在 Resources 里添加第 10 行 --helpers:StatusToColorConverterx:KeyStatusToColorConverter/!-- ↑ 创建转换器实例取名为 StatusToColorConverter --!-- 所有 Page 都能通过 {StaticResource StatusToColorConverter} 引用它 --!-- 新增 4 个状态颜色第 29~32 行 --SolidColorBrushx:KeyStatusStandByBrushColor#90A4AE/!-- 灰色待机 --SolidColorBrushx:KeyStatusRunningBrushColor#00E676/!-- 绿色运行中 --SolidColorBrushx:KeyStatusStoppedBrushColor#E0E0E0/!-- 白色已停止 --SolidColorBrushx:KeyStatusErrorBrushColor#FF1744/!-- 红色错误/报警 --文件 5:MainViewModel.cs— 4 个按钮命令 执行方法/* * 新增内容 * 1. 4 个 ICommand 属性StartCommand, StopCommand, PauseCommand, EmergencyCommand * 2. 4 个执行方法ExecuteStart/Stop/Pause/Emergency * 3. 构造函数注册 RelayCommand * 4. Is_Running 状态字段防止按钮重按 * 5. 每个方法写对应线圈 设置 4 轴状态 */// 私有字段 privateboolIs_Running;// 运行状态锁// 公开属性第 100~106 行 publicICommandStartCommand{get;}publicICommandStopCommand{get;}publicICommandPauseCommand{get;}publicICommandEmergencyCommand{get;}// 执行方法第 228~274 行 // Start仅在 idle 状态可用// 写线圈 0Coil_Start 设 4 轴为 RunningprivatevoidExecuteStart(){if(Is_Running)return;// 已在运行 → 忽略点击if(_servicenull||!IsConnected)return;// 未连接 → 忽略_service.WriteSingleCoil(PlcAddressMap.Coil_Start,true);Is_Runningtrue;Axis1Data.StatusAxisStatus.Running;// INPC 通知 UI 刷新Axis2Data.StatusAxisStatus.Running;Axis3Data.StatusAxisStatus.Running;Axis4Data.StatusAxisStatus.Running;}// Stop仅在 running 状态可用// 写线圈 1Coil_Stop 设 4 轴为 StoppedprivatevoidExecuteStop(){if(!Is_Running)return;// 没运行 → 忽略if(_servicenull||!IsConnected)return;_service.WriteSingleCoil(PlcAddressMap.Coil_Stop,true);Is_Runningfalse;Axis1Data.StatusAxisStatus.Stopped;// UI 变 Stopped白色// ... 其他 3 轴同理}// Pause仅在 running 状态可用// 写线圈 2Coil_Pause 设 4 轴为 StandByprivatevoidExecutePause(){if(!Is_Running)return;if(_servicenull||!IsConnected)return;_service.WriteSingleCoil(PlcAddressMap.Coil_Pause,true);Is_Runningfalse;Axis1Data.StatusAxisStatus.StandBy;// UI 变 StandBy灰色// ... 其他 3 轴同理}// Emergency任何状态都可用不检查 Is_Running// 写线圈 3Coil_EmergencyStop 设 4 轴为 ErrorprivatevoidExecuteEmergency(){if(_servicenull||!IsConnected)return;_service.WriteSingleCoil(PlcAddressMap.Coil_EmergencyStop,true);Is_Runningfalse;Axis1Data.StatusAxisStatus.Error;// UI 变 Error红色// ... 其他 3 轴同理}// 构造函数第 300~307 行 publicMainViewModel(){ConnectionCommandnewRelayCommand(Connect);StartCommandnewRelayCommand(ExecuteStart);StopCommandnewRelayCommand(ExecuteStop);PauseCommandnewRelayCommand(ExecutePause);EmergencyCommandnewRelayCommand(ExecuteEmergency);}文件 6:HomePage.xaml— 按钮加 Command Status 加颜色!-- 第 227~238 行4 个按钮加 Command 绑定 --!-- Start 按钮Command 绑定到 ViewModel 的 StartCommand --ButtonGrid.Column0Command{Binding StartCommand}ContentStartBackground{StaticResource GreenBrush}ForegroundWhiteFontSize20BorderThickness1Height45Margin0,0,5,0/!-- Stop 按钮 --ButtonGrid.Column1Command{Binding StopCommand}ContentStopBackground{StaticResource RedBrush}.../!-- Pause 按钮 --ButtonGrid.Column2Command{Binding PauseCommand}ContentPause.../!-- Emergency Stop 按钮 --ButtonCommand{Binding EmergencyCommand}Content⚠ Emergency Stop.../!-- 第 39~42 行Status 文字颜色随状态变化 --TextBlockFontSize30Margin0,0,0,8!-- Status: 标签固定灰色 --RunTextStatus:Foreground{StaticResource LabelBrush}/!-- 状态值通过 Converter 转颜色 Running→绿 StandBy→灰 Stopped→白 Error→红 --RunText{Binding Axis1Data.Status}Foreground{Binding Axis1Data.Status, Converter{StaticResource StatusToColorConverter}}//TextBlock三、今日踩坑汇总#问题原因解决1XAML 找不到新加的 Converter 类没编译设计器看不到 .cs 中的类dotnet build2local:命名空间找不到类local指向UpperMachine但类在UpperMachine.Helpers加xmlns:helpersclr-namespace:UpperMachine.Helpers3App.xaml 第 10 行用local:引 converter手误忘改成helpers:local:→helpers:4按钮互锁失效Is_Running写完就设回 false保持Is_Running true直到 Stop/Pause/E-Stop5AxisData.Status 赋值后 UI 不变AxisData 没实现 INotifyPropertyChanged加 INPCStatus 用属性字段模式6Modbus Slave 不能同时配 Coils Registers免费版限制根据实际 PLC 选一种四、整体架构图用户点击 Start ↓ HomePage.xaml Button.Command{Binding StartCommand} ↓ MainViewModel.StartCommand (RelayCommand) ↓ ExecuteStart() ├── WriteSingleCoil(0, true) ← 写 PLC 线圈 ├── Is_Running true ← 锁住防重按 └── Axis1.Status AxisStatus.Running ← INPC 通知 UI ↓ AxisData.Status setter → OnPropertyChanged() ↓ WPF 收到通知 → 执行 StatusToColorConverter.Convert(Running) ↓ 返回 StatusRunningBrush绿色 #00E676 → 赋值给 Run.Foreground ↓ 用户在 UI 上看到 Status: Running ← 绿色文字五、当前状态与下一步已实现功能状态Start/Stop/Pause/急停 绑定✅ 已完成状态互锁防重按✅ 已完成Status INotifyPropertyChanged✅ 已完成Status→颜色自动转换✅ 已完成线圈写入 PLC✅ 已完成需 Modbus Slave 配合测试下一阶段计划创建子页面ParamSettingsPage、CommConfigPage 等Return Home / Manual Adjust 按钮绑定命令导航按钮跳转页面恢复轮询完善数据读取报警日志模块