LOGO OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 开发文档 其他文档  
 
网站管理员

利用 C# 的委托和事件实现窗体间的传值

admin
2026年1月24日 10:7 本文热度 75

利用 C# 的委托和事件实现工控上位机中窗体间的传值,这是上位机开发中非常常见的需求(比如:参数设置窗体修改值后同步到主监控窗体、报警弹窗将确认状态传回主窗体)。委托 + 事件是窗体间传值的最优方案,能避免窗体间直接耦合,符合工控软件 “模块化、易维护” 的设计原则。

核心思路

窗体间传值的本质是:数据发送方(子窗体)定义事件,数据接收方(主窗体)订阅事件,子窗体触发事件时将数据传递给主窗体,全程无需子窗体知道主窗体的存在,彻底解耦。

下面以工控上位机最典型的两个场景为例,提供完整可运行的代码示例:

场景 1:子窗体(参数设置)→ 主窗体(实时监控)传值

这是最常见的场景:操作员在子窗体修改 PLC 的阈值参数,修改后同步到主窗体显示并生效。

步骤 1:定义通用委托(或直接用内置泛型委托)

为了复用,我们先定义一个带参数的委托(也可以直接用 .NET 内置的 Action<T> 泛型委托):



1
2

// 定义传递数值参数的委托(工控场景:PLC阈值、设备参数等)
public delegate void ParameterChangedDelegate(string plcAddress, float newThreshold);



步骤 2:子窗体(参数设置窗体)代码

子窗体作为数据发送方,定义事件并在用户确认修改时触发事件:



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

using System;
using System.Windows.Forms;

namespace HmiFormCommunication
{
    public partial class FrmParameterSetting : Form
    {
        // 1. 定义事件(基于自定义委托)
        public event ParameterChangedDelegate ParameterChanged;

        // 构造函数
        public FrmParameterSetting()
        {
            InitializeComponent();
            // 初始化控件(模拟PLC地址和当前阈值)
            txtPlcAddress.Text = "D100";
            txtThreshold.Text = "40.0";
        }

        // 确认按钮点击事件(触发参数变更事件)
        private void btnConfirm_Click(object sender, EventArgs e)
        {
            // 1. 校验输入(工控软件必须做数据校验)
            if (!float.TryParse(txtThreshold.Text, out float newThreshold))
            {
                MessageBox.Show("请输入有效的数值!", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }
            if (string.IsNullOrWhiteSpace(txtPlcAddress.Text))
            {
                MessageBox.Show("PLC地址不能为空!", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }

            // 2. 触发事件,传递新参数(空值保护:避免无订阅者时报错)
            ParameterChanged?.Invoke(txtPlcAddress.Text, newThreshold);

            // 3. 提示并关闭子窗体
            MessageBox.Show("参数设置成功!", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
            this.Close();
        }

        // 取消按钮
        private void btnCancel_Click(object sender, EventArgs e)
        {
            this.Close();
        }
    }
}



步骤 3:主窗体(监控窗体)代码

主窗体作为数据接收方,创建子窗体时订阅事件,接收并处理传递过来的参数:



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57

using System;
using System.Windows.Forms;

namespace HmiFormCommunication
{
    public partial class FrmMainMonitor : Form
    {
        // 当前PLC阈值(主窗体维护的核心参数)
        private float _currentThreshold = 40.0f;
        private string _plcAddress = "D100";

        public FrmMainMonitor()
        {
            InitializeComponent();
            // 初始化UI显示
            lblCurrentAddress.Text = $"PLC地址:{_plcAddress}";
            lblCurrentThreshold.Text = $"当前阈值:{_currentThreshold}℃";
        }

        // 打开参数设置窗体按钮
        private void btnSetParameter_Click(object sender, EventArgs e)
        {
            // 1. 创建子窗体实例
            FrmParameterSetting frmSetting = new FrmParameterSetting();

            // 2. 订阅子窗体的事件(核心:接收子窗体传递的数据)
            frmSetting.ParameterChanged += FrmSetting_ParameterChanged;

            // 3. 显示子窗体(模态显示,阻止主窗体操作)
            frmSetting.ShowDialog();

            // 4. 子窗体关闭后,可取消订阅(避免内存泄漏,工控软件必做)
            frmSetting.ParameterChanged -= FrmSetting_ParameterChanged;
        }

        // 事件处理方法:接收子窗体传递的参数并更新主窗体
        private void FrmSetting_ParameterChanged(string plcAddress, float newThreshold)
        {
            // 1. 更新主窗体的参数
            _plcAddress = plcAddress;
            _currentThreshold = newThreshold;

            // 2. 更新UI显示
            lblCurrentAddress.Text = $"PLC地址:{_plcAddress}";
            lblCurrentThreshold.Text = $"当前阈值:{_currentThreshold}℃";

            // 3. 工控场景:同步更新PLC的实际阈值(此处模拟)
            UpdatePlcThreshold(plcAddress, newThreshold);
        }

        // 模拟更新PLC阈值(实际为Modbus/OPC写操作)
        private void UpdatePlcThreshold(string address, float value)
        {
            lblStatus.Text = $"[{DateTime.Now:HH:mm:ss}] 已同步PLC地址{address}阈值为:{value}℃";
        }
    }
}



场景 2:主窗体→子窗体传初始值 + 子窗体回传确认状态

进阶场景:主窗体打开报警确认弹窗时,先传递报警信息给子窗体,子窗体确认 / 取消后将状态传回主窗体。

关键修改(子窗体):



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

// 1. 定义接收初始值的构造函数
public FrmAlarmConfirm(string alarmId, string alarmMsg)
{
    InitializeComponent();
    // 显示主窗体传递的初始报警信息
    lblAlarmId.Text = $"报警ID:{alarmId}";
    lblAlarmMsg.Text = $"报警信息:{alarmMsg}";
}

// 2. 定义回传确认状态的事件
public event Action<bool> AlarmConfirmed; // 用内置Action泛型委托,简化代码

// 确认按钮
private void btnConfirm_Click(object sender, EventArgs e)
{
    AlarmConfirmed?.Invoke(true); // 传回“已确认”
    this.Close();
}

// 取消按钮
private void btnCancel_Click(object sender, EventArgs e)
{
    AlarmConfirmed?.Invoke(false); // 传回“取消”
    this.Close();
}



关键修改(主窗体):



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

// 打开报警确认弹窗
private void btnShowAlarm_Click(object sender, EventArgs e)
{
    // 1. 主窗体传递初始报警信息给子窗体
    FrmAlarmConfirm frmAlarm = new FrmAlarmConfirm("TEMP001", "烘箱温度超限(85℃)");

    // 2. 订阅子窗体的确认状态事件
    frmAlarm.AlarmConfirmed += FrmAlarm_AlarmConfirmed;

    // 3. 显示子窗体
    frmAlarm.ShowDialog();

    // 4. 取消订阅
    frmAlarm.AlarmConfirmed -= FrmAlarm_AlarmConfirmed;
}

// 接收子窗体的确认状态
private void FrmAlarm_AlarmConfirmed(bool isConfirmed)
{
    if (isConfirmed)
    {
        lblStatus.Text = $"[{DateTime.Now:HH:mm:ss}] 报警已确认,正在清除报警...";
        // 实际逻辑:发送清除报警指令给PLC
    }
    else
    {
        lblStatus.Text = $"[{DateTime.Now:HH:mm:ss}] 用户取消了报警确认";
    }
}



关键细节(工控上位机必注意)

  1. 1. 事件订阅 / 取消订阅:子窗体关闭后必须用 -= 取消订阅,否则会导致窗体对象无法释放,工控软件 7*24 运行时极易引发内存泄漏;
  2. 2. 线程安全:如果传值后需要更新 UI,若事件触发在非 UI 线程(如通信线程),需用 Invoke 跨线程更新:


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    // 主窗体事件处理方法中加线程安全判断
    private void FrmSetting_ParameterChanged(string plcAddress, float newThreshold)
    {
        if (this.InvokeRequired)
        {
            this.Invoke(new ParameterChangedDelegate(FrmSetting_ParameterChanged), plcAddress, newThreshold);
            return;
        }
        // 后续更新UI逻辑...
    }



  3. 3. 数据校验:工控场景下传递的参数(如 PLC 地址、阈值)必须做严格校验,避免非法值写入设备;
  4. 4. 泛型委托复用:优先使用 .NET 内置的 Action<T>/Func<T> 泛型委托,减少自定义委托的冗余代码。

总结

委托和事件实现窗体间传值的核心要点:

  1. 1. 解耦设计:发送方(子窗体)只定义事件,不依赖接收方(主窗体);接收方订阅事件,接收并处理数据;
  2. 2. 双向传值:主→子可通过构造函数 / 属性传初始值,子→主通过事件回传结果;
  3. 3. 工控适配:必须注意线程安全、内存泄漏(取消订阅)、数据校验,适配 7*24 小时运行的场景;
  4. 4. 简化实现:优先使用内置泛型委托(Action<T>/Func<T>),减少自定义委托的代码量。

这种方式相比直接引用窗体对象、静态变量传值更安全、更易维护,是工控上位机窗体间通信的标准最佳实践。


阅读原文:https://mp.weixin.qq.com/s/nZLtBzhaoT50CH3TBowO_A


该文章在 2026/1/24 10:07:15 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2026 ClickSun All Rights Reserved