WPF:Modbus 状态监控

环境: VS2022+.Net Framework4.8

NuGet包:EasyModbusTCP 5.6.0

程序名:WPFA_EasyModbus


一、功能概述

  • 这是一个基于 WPF (.NET) 的 Modbus TCP 工业设备点位状态监控与控制系统,主要用于与 PLC/工业控制器通过 Modbus TCP 协议通讯,实时监控和操控现场设备的数字量点位。
功能模块 说明
连接管理 输入 IP 和端口,连接/断开 Modbus TCP 服务器(默认 192.168.0.7:8234)
离散输入监控 轮询读取 8 路离散输入(DI0~DI7),如"开关灯/称重电源""拍照""测厚""称重""去皮"等按钮/信号状态
线圈状态监控 轮询读取 9 路线圈(CO1、CO3、CO4、CO5、CO8、CO9、CO11、CO13、CO14),如"拍照灯""按钮灯""伸缩杆抬起/压下""照明灯""测厚启动""称重启动"等
线圈写控制 每个线圈点位提供 ON/OFF 按钮,可远程写单个线圈
LED 指示灯 绿色=ON,灰色=OFF,通过 BoolToBrushConverter 或 Color 属性绑定实现
运行日志 带时间戳的日志输出,支持清空
断线自动重连 轮询线程中发现连接断开时自动重连
资源释放 窗口关闭时停止轮询、断开连接、释放资源

二、技术栈分析

技术 用途
WPF (.NET Framework / .NET) UI 框架,XAML 声明式界面
C# 主要开发语言
MVVM 模式 UI 与逻辑解耦的架构模式
EasyModbus 第三方 Modbus TCP 客户端库,负责 ModbusClient 的读写
数据绑定 (DataBinding) {Binding} 实现 UI 与 ViewModel 的自动同步
ICommand / Relay Command 命令模式,替代事件处理
INotifyPropertyChanged 属性变更通知机制,驱动 UI 更新
ObservableCollection 集合变更通知,用于 ItemsControl 绑定
IValueConverter BoolToBrushConverter 布尔→画刷转换
Task / CancellationToken 后台轮询线程与取消机制
Dispatcher 跨线程更新 UI(后台线程 → UI 线程)
IDisposable 资源释放模式
StringFormat / RelativeSource XAML 高级绑定技巧(如 {0}:True 参数拼接、AncestorType=Window 找上级 DataContext)

三、MVVM 设计框架原理与应用详解

1、 MVVM 基本原理

  • MVVM(Model-View-ViewModel)是 MVC 的 WPF 特化版本,核心思想是通过数据绑定把视图和逻辑分离,让 UI 开发者只关心 XAML,逻辑开发者只关心 ViewModel。
    View:只负责界面呈现和绑定,几乎不含业务代码
    ViewModel:持有状态(属性)和行为(命令),通过 INotifyPropertyChanged 通知 View 更新
    Model:数据实体和业务服务(如 BitStatusItem、ModbusService)
关键机制:
  • 数据绑定:View 用 {Binding Xxx} 连接到 ViewModel 的属性,属性变化自动刷新 UI。
  • 命令绑定:View 用 Command="{Binding XxxCommand}" 调用 ViewModel 的方法。
  • 双向绑定:TextBox Text="{Binding IpAddress, UpdateSourceTrigger=PropertyChanged}" 用户输入自动写回 ViewModel。

2、具体应用

(1)Model 层
角色
BitStatusItem 单个点位数据模型(Address / Name / Value / ValueText / Color),实现 INotifyPropertyChanged
ModbusService 业务服务模型:封装 EasyModbus 客户端、连接、轮询、读写线圈,并持有两个 ObservableCollection
  • BitStatusItem.Value 的 setter 是典型的模型自通知

    public bool Value
    {
    get => _value;
    set
    {
    if (_value != value)
    {
    _value = value;
    OnPropertyChanged(); // 通知 Value 变化
    OnPropertyChanged(nameof(ValueText)); // 联动通知 ValueText
    OnPropertyChanged(nameof(Color)); // 联动通知 Color
    }
    }
    }

这样 UI 上绑定的 {Binding ValueText}、{Binding Color} 会自动刷新,无需手动更新界面。

(2)ViewModel 层

MainViewModel 是核心,它:

  • 持有 Model:_service = new ModbusService()

  • 暴露集合给 View:

    public ObservableCollection DiscreteInputs => _service.DiscreteInputs;
    public ObservableCollection Coils => _service.Coils;

  • 暴露属性:IpAddress、Port、IsConnected、IsPolling、LogText,均通过 SetProperty 触发通知

  • 暴露命令(ICommand):

    • ConnectCommand / DisconnectCommand
    • StartPollCommand / StopPollCommand
    • WriteCoilCommand
    • ClearLogCommand

MainViewModel 继承自 ViewModelBase,后者实现了 INotifyPropertyChanged 和泛型 SetProperty辅助方法:

复制代码
protected bool SetProperty<T>(ref T field, T value, [CallerMemberName] string name = null)
{
    if (Equals(field, value)) return false;
    field = value;
    OnPropertyChanged(name);
    return true;
}

CallerMemberName 让调用处写 SetProperty(ref _port, value) 即可自动传入属性名 "Port",减少硬编码。

(3)View 层

MainWindow.xaml 中几乎没有业务逻辑,全部是声明式绑定:

① 绑定 ViewModel 作为 DataContext
复制代码
<Window.DataContext>
    <vm:MainViewModel/>
</Window.DataContext>
② 属性双向绑定
复制代码
<TextBox Text="{Binding IpAddress, UpdateSourceTrigger=PropertyChanged}"/>
<TextBox Text="{Binding Port, UpdateSourceTrigger=PropertyChanged}"/>
③ 命令绑定按钮
复制代码
<Button Content="连接" Command="{Binding ConnectCommand}"/>
<Button Content="开始轮询" Command="{Binding StartPollCommand}"/>
④ 集合绑定
复制代码
<ItemsControl ItemsSource="{Binding DiscreteInputs}">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding Name}"/>
            <TextBlock Text="{Binding ValueText}"/>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>
⑤ 跨层级命令绑定
  • (ItemsControl 内每个 DataTemplate 的 DataContext 是 BitStatusItem,要访问 MainViewModel 的 WriteCoilCommand 必须向上找):

这里 RelativeSource AncestorType=Window 沿可视化树向上找 Window,取其 DataContext(即 MainViewModel)的 WriteCoilCommand,并把 Address:True 作为参数传过去。

⑥ 值转换器(布尔→颜色):
复制代码
<Border Background="{Binding IsConnected, Converter={StaticResource BoolToBrush}}"/>
(4)命令模式 RelayCommand
复制代码
public class RelayCommand : ICommand
{
    private readonly Action<object> _execute;
    private readonly Predicate<object> _canExecute;
    public bool CanExecute(object p) => _canExecute == null || _canExecute(p);
    public void Execute(object p) => _execute(p);
    public event EventHandler CanExecuteChanged
    {
        add    { CommandManager.RequerySuggested += value; }
        remove { CommandManager.RequerySuggested -= value; }
    }
}

RelayCommand 让 ViewModel 用一个 Lambda 即可定义按钮行为,不必为每个按钮写 Click 事件处理函数。

例:

复制代码
WriteCoilCommand = new RelayCommand(param =>
{
    if (param is string s && s.Split(':').Length == 2)
    {
        if (int.TryParse(s.Split(':')[0], out int addr) &&
            bool.TryParse(s.Split(':')[1], out bool val))
        {
            _service.WriteCoil(addr, val);
        }
    }
});

XAML 传参 CommandParameter="{Binding Address, StringFormat={}{0}:True}",产生如 "1:True" 字符串,ViewModel 解析后调用服务。

3 线程与 MVVM 的配合

Modbus 轮询是后台线程,而 UI 只能在 UI 线程更新,因此:

复制代码
System.Windows.Application.Current?.Dispatcher.Invoke(() => item.Value = v);

Dispatcher.Invoke 把"设置属性"的操作切到 UI 线程执行。由于 BitStatusItem 实现了 INotifyPropertyChanged,一旦 UI 线程设值,WPF 绑定系统会自动刷新对应控件。

日志同理:

复制代码
private void OnLog(string msg)
{
    var dispatcher = System.Windows.Application.Current?.Dispatcher;
    dispatcher.Invoke(() => LogText += msg + Environment.NewLine);
}

4 资源释放的 MVVM 处理

View 不直接调业务方法,而是通过 ViewModel 暴露的 DisposeService() 释放,避免 View 依赖 ModbusService:

复制代码
// MainWindow.xaml.cs
protected override void OnClosed(EventArgs e)
{
    (DataContext as ViewModels.MainViewModel)?.DisposeService();
    base.OnClosed(e);
}

// MainViewModel
public void DisposeService() => _service.Dispose();

这样 View 只知道"要在关闭时通知 ViewModel",不知道具体怎么释放,符合 MVVM 解耦原则。

5 MVVM 在本程序中的优势总结

优势 体现
UI 与逻辑彻底解耦 XAML 中无一行 C# 业务代码,ViewModel 不引用任何 WPF 控件
可测试性 可对 MainViewModel、ModbusService 写单元测试,无需启动 UI
可维护性 修改界面不影响逻辑,修改逻辑不影响界面
数据驱动 属性改变自动刷新 UI,避免手动 textBox.Text = ...
命令驱动 用 ICommand 替代事件,便于统一管理启用/禁用状态
可扩展性 增加新点位只需在 ModbusService 初始化列表增加一行;增加新功能只需在 ViewModel 加命令,XAML 加绑定

四、源码

复制代码
https://download.csdn.net/download/dalong10/93471541
相关推荐
He BianGu16 小时前
【WPF-VisionMaster】机器视觉通用平台V5.0版本发行说明
opencv·c#·wpf·halcon·机器视觉·visionmaster
fogota4 天前
Emoji与Segoe MDL2 Assets的比较
c#·wpf
fogota4 天前
C# WPF 按钮加载字体图标 / 动物头像
c#·.net·wpf
fogota4 天前
C# WPF 按钮加载系统图标 / 动物头像
c#·.net·wpf
军训猫猫头4 天前
C# 封装 MySQL 自动写入与查询工具类(基于 MySqlConnector)
mysql·c#·.net·wpf
gis开发之家5 天前
Spring Boot 4 定时任务与异步线程池:从 @Scheduled 到高并发任务编排(生产级实战)
java·spring boot·后端·wpf·spring boot4
AlanBruce6 天前
摩尔信使MThings EdgeWeb使用指南
上位机·plc·modbus·mthings·摩尔信使
AlanBruce6 天前
摩尔信使MThings逻辑控制各组件使用指南
自动化·上位机·modbus·mthings·摩尔信使
AlanBruce6 天前
摩尔信使MThings DL/T 698.45数据配置使用指南
上位机·plc·modbus·mthings·摩尔信使