讲解目标:聚焦工控上位机场景,优先讲解主流的 WinForm 开发(兼容性强、工控落地广),补充 WPF 核心要点,全面掌握「常用控件、布局设计、事件处理」三大核心技能,能独立搭建符合工控规范的可视化界面。
一、 先明确:工控上位机为何优先选 WinForm?
工控场景中,界面无需炫酷动画,核心要求「稳定、易用、开发快、兼容性强」,WinForm 与 WPF 的对比及选型建议如下:
| 特性 | WinForm(优先推荐) | WPF(补充学习) |
|---|---|---|
| 兼容性 | 完美支持.NET Framework 4.8,兼容所有工控 SDK | 对老旧工控 SDK 支持较差,易出现兼容性问题 |
| 开发难度 | 低,拖拽式开发,快速落地 | 高,需掌握 XAML 语法,学习成本高 |
| 运行性能 | 轻量,占用资源少,工控机运行流畅 | 较笨重,占用内存多,低配工控机易卡顿 |
| 界面美观度 | 简洁朴素,满足工控操作需求 | 样式丰富,可自定义美化,适合高端可视化场景 |
| 工控落地场景 | 90% 以上的工业检测、PLC 通信、数据采集项目 | 少量高端产线的可视化监控、3D 展示项目 |
本章节重点讲解 WinForm,结尾补充 WPF 核心开发要点,兼顾实用性与拓展性。
二、 WinForm 核心开发:常用控件(工控必备)
WinForm 控件采用「拖拽式添加」(在 VS 右侧「工具箱」中直接拖拽到窗体),以下按工控功能分类,罗列高频使用控件及实操技巧。
1. 文本类控件(显示 / 输入数据)
| 控件名称 | 核心用途 | 工控实操设置 & 示例 |
|---|---|---|
Label(标签) |
显示静态文本(标题、参数说明、状态提示) | 1. 设置Text属性:如 "外径尺寸 (mm)"、"检测结果"2. 设置ForeColor:合格显绿色、不合格显红色3. 示例:lblResult.Text = "检测合格"; lblResult.ForeColor = Color.Green; |
TextBox(文本框) |
显示动态数据(测量值、IP 地址),少量输入参数 | 1. 工控场景多设为「只读」:txtOuterDiam.ReadOnly = true;2. 显示保留 2 位小数的测量值:txtOuterDiam.Text = 49.98.ToString("F2");3. 输入相机 IP:string cameraIP = txtCameraIP.Text; |
RichTextBox(富文本框) |
显示日志信息(通信日志、检测日志、异常信息) | 1. 追加日志并换行:rtbLog.AppendText($"【{DateTime.Now}】相机连接成功\r\n");2. 设置不可编辑:rtbLog.Enabled = false;(或ReadOnly = true)3. 清空日志:rtbLog.Clear(); |
2. 操作类控件(触发业务逻辑)
| 控件名称 | 核心用途 | 工控实操设置 & 示例 |
|---|---|---|
Button(按钮) |
触发核心操作(开始检测、停止检测、查询、保存) | 1. 设置Text属性:如 "开始检测"、"历史查询"2. 设置按钮颜色:开始(绿色)、停止(红色)3. 绑定点击事件(双击按钮自动生成):btnStart_Click(详见事件处理章节)4. 禁用 / 启用:btnStart.Enabled = false;(防止重复点击) |
3. 数据展示类控件(显示批量数据)
| 控件名称 | 核心用途 | 工控实操设置 & 示例 |
|---|---|---|
DataGridView(数据网格) |
显示历史检测数据、设备参数列表、PLC 寄存器数据 | 1. 自动列适配:dgvHistory.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.AllCells;2. 绑定数据库查询结果:dgvHistory.DataSource = dbHelper.QueryHistoryData(startTime, endTime);3. 设置列标题:在 VS 中编辑「Columns」属性,自定义列名(如 "序号"、"外径 (mm)")4. 禁止用户编辑行:dgvHistory.ReadOnly = true; |
4. 时间选择类控件(查询时段选择)
| 控件名称 | 核心用途 | 工控实操设置 & 示例 |
|---|---|---|
DateTimePicker(日期时间选择器) |
选择起始 / 结束时间,用于历史数据查询 | 1. 设置显示格式:dtpStart.Format = DateTimePickerFormat.Custom; dtpStart.CustomFormat = "yyyy-MM-dd HH:mm:ss";2. 获取选择的时间:DateTime startTime = dtpStart.Value;3. 默认显示当前时间:dtpEnd.Value = DateTime.Now; |
5. 状态显示类控件(直观展示设备状态)
| 控件名称 | 核心用途 | 工控实操设置 & 示例 |
|---|---|---|
PictureBox(图片框) |
显示设备状态(绿灯 = 正常、红灯 = 故障)、简易图像预览 | 1. 加载状态图片:picCameraStatus.Image = Properties.Resources.GreenLight;(需先添加图片资源)2. 自适应显示:picCameraStatus.SizeMode = PictureBoxSizeMode.StretchImage;3. 切换状态:故障时加载红灯图片:picCameraStatus.Image = Properties.Resources.RedLight; |
6. 容器类控件(分组管理控件,优化布局)
| 控件名称 | 核心用途 | 工控实操设置 & 示例 |
|---|---|---|
Panel(面板) |
按功能分组控件(如串口配置组、测量值显示组) | 1. 设置Dock属性(左 / 右 / 上 / 下),实现分区布局2. 设置边框样式:panelSerial.BorderStyle = BorderStyle.FixedSingle;(清晰区分分组)3. 示例:将Label+TextBox(串口参数)全部放入Panel,便于管理 |
GroupBox(分组框) |
带标题的分组控件,功能与 Panel 类似 | 1. 设置Text属性(分组标题):如 "串口配置"、"视觉参数"2. 其他设置与 Panel 一致,更适合需要显示分组名称的场景 |
三、 WinForm 核心开发:布局设计(工控规范,避免控件错位)
工控上位机界面要求「拉伸后不错位、分辨率适配性强」,核心掌握「Dock 属性」+「Anchor 属性」,摒弃绝对坐标布局(易错位)。
1. 核心布局原则(工控必备)
- 分区布局 :按功能划分为 4 个核心区域,用
Panel隔离- 顶部操作区(
Dock=Top):放Button(开始 / 停止)、DateTimePicker(查询时间) - 左侧参数区(
Dock=Left):放Label+TextBox(测量值、设备状态) - 右侧显示区(
Dock=Fill):放CogDisplay(视觉图像)、PictureBox(预览图) - 底部数据区(
Dock=Bottom):放DataGridView(历史数据)
- 顶部操作区(
- 自适应优先 :所有控件优先设置
Dock或Anchor,不手动设置固定坐标(Location)和固定大小(Size,核心控件除外) - 简洁易用:控件间距均匀(推荐 10-15px),按钮大小统一,字体使用微软雅黑(10-12 号),避免杂乱。
2. 核心布局属性(2 个属性搞定 90% 布局问题)
(1) Dock 属性(控件停靠到窗体 / 容器边缘)
- 可选值:
Top(上)、Bottom(下)、Left(左)、Right(右)、Fill(填充剩余空间) - 工控实操示例:
- 窗体添加
PanelTop,设置Dock=Top,高度设为 80(固定顶部操作区) - 添加
PanelLeft,设置Dock=Left,宽度设为 200(固定左侧参数区) - 添加
PanelBottom,设置Dock=Bottom,高度设为 200(固定底部数据区) - 添加
PanelDisplay,设置Dock=Fill(自动填充中间剩余空间,用于显示图像)
- 效果:窗体拉伸时,各分区自动适配大小,控件不会错位。
(2) Anchor 属性(控件锚定到窗体 / 容器边缘,保持相对位置)
- 可选值:
Top、Bottom、Left、Right(可多选) - 核心作用:当窗体 / 容器拉伸时,控件与锚定边缘的距离保持不变
- 工控实操示例:
Button(查询按钮)放在PanelTop中,设置Anchor=Top+Right(锚定顶部和右侧,窗体拉伸时按钮始终在右上角)TextBox(外径显示)放在PanelLeft中,设置Anchor=Left+Right(锚定左右两侧,拉伸时文本框宽度自动适配)- 若不设置
Anchor,默认锚定Top+Left,窗体拉伸时控件位置不变,出现空白区域。
3. 实操:搭建标准工控 WinForm 界面
- 新建 WinForm 项目,窗体大小设为 1200×600(工控机常用分辨率)
- 拖拽 4 个
Panel到窗体,按Dock属性设置分区(Top/Left/Bottom/Fill) - 顶部
Panel:添加 2 个Button(开始检测、停止检测)、2 个DateTimePicker、1 个Button(历史查询)、若干Label - 左侧
Panel:添加 4 个Label(外径尺寸、内径尺寸、缺陷数量、检测结果)、4 个TextBox(只读)、1 个Label(显示合格 / 不合格) - 中间
Panel:添加CogDisplay(VisionPro 图像显示),设置Dock=Fill - 底部
Panel:添加DataGridView,设置Dock=Fill、AutoSizeColumnsMode=AllCells - 所有控件设置
Anchor属性,窗体拉伸测试,验证无错位。
四、 WinForm 核心开发:事件处理(界面与逻辑联动)
WinForm 采用「事件驱动」模式,即「用户操作控件→触发对应事件→执行事件处理方法→实现业务逻辑」,核心掌握「事件绑定」与「跨线程更新 UI」。
1. 事件基础:绑定方式与核心事件
(1) 事件绑定的 2 种方式(工控实操优先选第一种)
-
可视化绑定(推荐):在 VS 中,选中控件(如
btnStart)→ 右侧「属性窗口」→ 点击闪电图标(事件)→ 找到对应事件(如Click)→ 双击自动生成事件处理方法 -
代码绑定(灵活):在窗体构造函数中手动绑定,适合动态创建控件的场景 csharp
运行
cs// 示例:手动绑定按钮点击事件 public MainForm() { InitializeComponent(); // 控件名.事件 += 事件处理方法名; btnStart.Click += BtnStart_Click; btnStop.Click += BtnStop_Click; }
(2) 工控高频事件(按控件分类)
| 控件类型 | 核心事件 | 触发场景 | 工控用途 |
|---|---|---|---|
Button |
Click(点击) |
用户单击按钮 | 触发开始检测、停止检测、查询等操作 |
SerialPort |
DataReceived |
串口接收到数据 | 接收 PLC / 传感器数据并处理 |
TcpClient |
(配合NetworkStream) |
网络接收到数据 | 接收 TCP 设备(如 MES 系统)数据 |
CameraHelper |
自定义ImageGrabbed |
相机采集到图像 | 接收图像并执行视觉检测 |
Form |
FormClosing |
窗体关闭时 | 释放相机、串口、数据库等资源 |
2. 实操:核心事件处理示例
(1) 按钮点击事件(最常用):开始检测
csharp
运行
cs
// 自动生成的事件处理方法(sender:触发事件的控件;e:事件参数)
private void btnStart_Click(object sender, EventArgs e)
{
try
{
// 1. 初始化辅助类
_cameraHelper = new CameraHelper(txtCameraIP.Text); // 获取文本框中的相机IP
_visionHelper = new VisionHelper();
_dbHelper = new DBHelper();
// 2. 绑定相机采集事件
_cameraHelper.ImageGrabbed += CameraHelper_ImageGrabbed;
// 3. 初始化相机
_cameraHelper.InitCamera();
// 4. 更新界面状态
lblCameraStatus.Text = "相机已连接,检测中...";
lblCameraStatus.ForeColor = Color.Green;
btnStart.Enabled = false; // 禁用开始按钮,防止重复点击
btnStop.Enabled = true; // 启用停止按钮
}
catch (Exception ex)
{
// 异常处理:提示用户并记录日志
MessageBox.Show($"启动检测失败:{ex.Message}", "错误提示", MessageBoxButtons.OK, MessageBoxIcon.Error);
rtbLog.AppendText($"【错误】{DateTime.Now}:启动检测失败-{ex.Message}\r\n");
}
}
(2) 窗体关闭事件:释放资源
csharp
运行
cs
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
{
// 释放相机资源
_cameraHelper?.Dispose();
// 释放串口资源
_serialPort?.Close();
_serialPort?.Dispose();
// 释放TCP资源
_tcpClient?.Close();
_tcpClient?.Dispose();
// 提示用户
MessageBox.Show("程序已正常退出,资源已释放", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
3. 关键难点:跨线程更新 UI(工控必避坑)
(1) 问题根源
工控中,相机采集、串口接收、TCP 通信等操作均在「后台线程」执行,而 WinForm 控件属于「UI 线程」,C# 不允许跨线程直接操作 UI 控件,否则会抛出「跨线程操作无效」异常。
(2) 解决方案:this.Invoke 方法
Invoke 方法的作用:将 UI 更新操作提交到 UI 线程执行,避免跨线程异常。
-
语法结构: csharp
运行
cs// 匿名方法形式(工控实操优先使用,简洁高效) this.Invoke(new Action(() => { // 此处编写UI更新代码(如修改Label、TextBox、DataGridView的值) })); -
工控实战示例(相机采集图像后,跨线程更新图像显示和测量值): csharp
运行
cs// 相机采集事件(后台线程执行) private void CameraHelper_ImageGrabbed(CogImage8Grey obj) { // 跨线程更新图像显示(UI操作) this.Invoke(new Action(() => { _cogDisplay.Image = obj; _cogDisplay.Fit(true); // 自适应显示图像 })); // 执行视觉检测 _visionHelper.RunDetection(obj); // 接收检测结果后,跨线程更新测量值 _visionHelper.ResultDetected += (detectResult) => { this.Invoke(new Action(() => { // 更新外径、内径、缺陷数量 txtOuterDiam.Text = detectResult.OuterDiameter.ToString("F2"); txtInnerDiam.Text = detectResult.InnerDiameter.ToString("F2"); txtDefectCount.Text = detectResult.DefectCount.ToString(); // 更新检测结果 if (detectResult.IsPass) { lblResult.Text = "合格"; lblResult.ForeColor = Color.Green; } else { lblResult.Text = "不合格"; lblResult.ForeColor = Color.Red; } // 保存数据到数据库 _dbHelper.InsertDetectResult(detectResult); })); }; }
五、 WPF 核心开发要点(补充学习)
若需开发高端工控可视化界面,掌握 WPF 核心要点即可快速入门:
-
核心语法 :XAML(用于界面布局,类似 HTML)+ C#(用于业务逻辑)
- XAML 布局示例(分区布局,对应 WinForm 的 Dock):
xml
cs<!-- Grid网格布局:替代WinForm的Panel,实现分区 --> <Grid> <Grid.RowDefinitions> <RowDefinition Height="80"/> <!-- 顶部行,对应WinForm Top --> <RowDefinition Height="*"/> <!-- 中间行,填充剩余空间 --> <RowDefinition Height="200"/> <!-- 底部行,对应WinForm Bottom --> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="200"/> <!-- 左侧列,对应WinForm Left --> <ColumnDefinition Width="*"/> <!-- 右侧列,填充剩余空间 --> </Grid.ColumnDefinitions> <!-- 顶部操作区:跨2列 --> <Button Grid.Row="0" Grid.ColumnSpan="2" Content="开始检测" Width="100" Height="30"/> <!-- 左侧参数区 --> <Label Grid.Row="1" Grid.Column="0" Content="外径尺寸"/> <!-- 中间显示区 --> <Image Grid.Row="1" Grid.Column="1" Stretch="Fill"/> <!-- 底部数据区:跨2列 --> <DataGrid Grid.Row="2" Grid.ColumnSpan="2" AutoGenerateColumns="True"/> </Grid> -
常用控件 :与 WinForm 对应,易上手
TextBlock替代Label、TextBox用法一致、DataGrid替代DataGridView、Button用法一致
-
事件处理 :与 WinForm 类似,支持 XAML 绑定和代码绑定,跨线程更新 UI 使用
Dispatcher.Invoke(替代this.Invoke)csharp
运行
cs// WPF跨线程更新UI this.Dispatcher.Invoke(new Action(() => { lblResult.Text = "检测合格"; })); -
核心优势:可通过「样式(Style)」和「模板(Template)」自定义控件外观,实现高端可视化效果。
六、 工控界面开发核心总结
- 选型优先:90% 工控项目用 WinForm,快速落地;少量高端可视化用 WPF
- 控件使用:聚焦工控高频控件,无需掌握所有控件,够用即可
- 布局关键 :掌握
Dock+Anchor(WinForm)、Grid(WPF),实现自适应布局,避免控件错位 - 事件处理 :核心绑定
Click等高频事件,跨线程更新 UI 必须用Invoke(WinForm)/Dispatcher.Invoke(WPF) - 工控规范:界面分区清晰、操作简洁、字体统一、状态直观(颜色区分合格 / 不合格),优先保证实用性而非美观度