讲解目标:针对工控上位机开发场景,剥离无用复杂语法,聚焦「变量、数据类型、流程控制、面向对象」的核心实用知识点,配合工控实战案例,让你快速掌握并落地使用。
一、 变量与数据类型(工控数据存储核心)
1. 变量基础(工控场景必备操作)
变量是用于存储工控场景中动态数据的容器(如测量值、设备状态、通信参数等),核心遵循「先定义 / 声明,后使用」的原则。
(1) 变量声明语法
cs
// 语法:数据类型 变量名;
int defectCount; // 声明:存储缺陷数量的变量
double outerDiameter; // 声明:存储轴承外径的变量
// 语法:数据类型 变量名 = 初始值;(推荐:工控场景优先初始化,避免空值异常)
int port = 502; // 声明并初始化:Modbus通信端口
string cameraIP = "192.168.1.100"; // 声明并初始化:相机IP地址
bool isCameraConnected = false; // 声明并初始化:相机连接状态
(2) 变量命名规范(工控项目可维护性关键)
- 采用「驼峰命名法」:首字母小写,后续单词首字母大写(如
defectCount、innerDiameter)。 - 见名知意:避免
a、b、temp等无意义命名,直接体现变量用途(如detectTime= 检测时间,ocvResult= 字符识别结果)。 - 避免关键字:不能使用
int、string、if等 C# 关键字作为变量名。
2. 核心数据类型(工控场景高频使用)
按用途分为「值类型」和「引用类型」,以下仅罗列工控上位机开发中必须掌握的类型,无实用价值的类型不做赘述。
(1) 值类型(直接存储数据,常用工控场景)
| 数据类型 | 大小 | 核心用途 | 工控实战示例 |
|---|---|---|---|
int |
4 字节 | 存储整数型参数(计数、端口号、数量、编号等) | 缺陷数量:int defectCount = 0;串口编号:int comPort = 3;零件编号:int partNo = 1001; |
double |
8 字节 | 存储浮点型参数(尺寸、坐标、精度值、温度等) | 轴承内径:double innerDiameter = 19.95;定位 X 坐标:double xPos = 256.32;温度值:double temp = 25.8; |
bool |
1 字节 | 存储状态信息(是 / 否、合格 / 不合格、连接 / 断开等) | 检测结果:bool isPass = true;TCP 连接状态:bool isTcpConnected = false;超差状态:bool isOverTolerance = false; |
DateTime |
8 字节 | 存储时间信息(检测时间、日志时间、设备启动时间等) | 当前检测时间:DateTime detectTime = DateTime.Now;起始查询时间:DateTime startTime = new DateTime(2026,1,1); |
(2) 引用类型(存储数据地址,常用工控场景)
| 数据类型 | 核心用途 | 工控实战示例 |
|---|---|---|
string |
存储文本信息(IP 地址、识别结果、日志内容、设备型号等) | 相机 IP:string cameraIP = "192.168.1.100";OCV 识别结果:string ocvResult = "A2B3C4";设备型号:string deviceModel = "Basler acA2040"; |
(3) 类型转换(工控场景高频操作)
工控中常需在不同类型间转换(如文本框输入的string转int/double、测量值double转string显示),核心掌握 2 种转换方式:
(2) while 循环(适用于未知循环次数,满足条件即执行)
3. 异常处理语句(try-catch-finally,工控程序稳定性保障)
工控场景中存在大量不可控异常(如相机断开、数据库连接失败、串口未打开),异常处理可避免程序崩溃,是现场部署的必备技能。
-
显式转换(强制转换 / 专用方法,推荐工控场景使用)
cs// 场景1:TextBox输入的端口号(string)转int string portStr = txtPort.Text; int port = int.Parse(portStr); // 字符串转int // 场景2:测量值double转string显示在TextBox double outerDiam = 49.98; txtOuterDiam.Text = outerDiam.ToString("F2"); // 保留2位小数转换 // 场景3:字符串转double(尺寸参数输入) string diamStr = txtInnerDiam.Text; double innerDiam = double.Parse(diamStr);- 安全转换(避免转换失败导致程序崩溃,推荐异常场景使用)
csstring portStr = txtPort.Text; int port; // TryParse:转换成功返回true,失败返回false,不抛异常 if (int.TryParse(portStr, out port)) { // 转换成功,执行后续逻辑 InitSerialPort(port); } else { // 转换失败,提示用户 MessageBox.Show("请输入有效的端口号", "错误提示"); }二、 流程控制语句(工控业务逻辑编排核心)
流程控制用于实现工控业务逻辑(如超差判断、批量检测、异常分支处理),核心掌握 3 类语句,无需深入复杂语法。
1. 条件判断语句(根据状态 / 数据执行不同逻辑)
(1)
if-else语句(工控首选,适用于大部分条件场景) -
语法结构:
cs// 单条件 if (条件表达式) { // 条件为true时执行 } // 双条件 if (条件表达式) { // 条件为true时执行 } else { // 条件为false时执行 } // 多条件 if (条件1) { // 条件1为true时执行 } else if (条件2) { // 条件1为false、条件2为true时执行 } else { // 所有条件都为false时执行 }工控实战示例(轴承尺寸超差判断,核心业务逻辑):
cs// 已知:轴承外径公差49.8mm ~ 50.2mm,内径公差19.8mm ~ 20.2mm double outerDiam = 50.3; // 测量得到的外径 double innerDiam = 19.7; // 测量得到的内径 bool isPass = true; // 初始化合格状态 string errorMsg = ""; // 异常信息 // 外径判断 if (outerDiam < 49.8 || outerDiam > 50.2) { isPass = false; errorMsg += "外径超差;"; } // 内径判断 if (innerDiam < 19.8 || innerDiam > 20.2) { isPass = false; errorMsg += "内径超差;"; } // 最终结果判断 if (isPass) { lblResult.Text = "检测合格"; lblResult.ForeColor = Color.Green; } else { lblResult.Text = "检测不合格:" + errorMsg; lblResult.ForeColor = Color.Red; System.Media.SystemSounds.Exclamation.Play(); // 超差报警 }(2)
switch语句(适用于多分支固定值判断) -
语法结构:
csswitch (判断变量) { case 值1: // 变量等于值1时执行 break; // 跳出switch,避免穿透 case 值2: // 变量等于值2时执行 break; default: // 变量不等于所有case值时执行 break; }工控实战示例(设备状态判断):
cs// 设备状态码:1=待机,2=检测中,3=故障,4=停机 int deviceStatus = 3; string statusDesc = ""; switch (deviceStatus) { case 1: statusDesc = "设备待机"; lblStatus.ForeColor = Color.Blue; break; case 2: statusDesc = "检测中"; lblStatus.ForeColor = Color.Green; break; case 3: statusDesc = "设备故障"; lblStatus.ForeColor = Color.Red; System.Media.SystemSounds.Beep.Play(); break; case 4: statusDesc = "设备停机"; lblStatus.ForeColor = Color.Gray; break; default: statusDesc = "未知状态"; break; } lblStatus.Text = statusDesc;2. 循环语句(批量处理 / 重复执行逻辑)
(1)
for循环(适用于已知循环次数的场景) -
语法结构:
cs// 初始化循环变量 → 循环条件 → 循环变量自增/自减 for (int i = 0; i < 循环次数; i++) { // 循环执行的逻辑 } -
工控实战示例(批量读取 10 个零件测量数据):
cs// 循环读取10个零件的外径数据并打印 for (int i = 0; i < 10; i++) { // 模拟获取第i+1个零件的测量值 double measureDiam = GetOuterDiameter(i + 1); // 打印日志 rtbLog.AppendText($"第{i+1}个零件外径:{measureDiam:F2}mm\r\n"); // 批量存储到数据库 InsertMeasureData(i + 1, measureDiam, DateTime.Now); } // 自定义方法:模拟获取测量值 private double GetOuterDiameter(int partNo) { // 实际场景中替换为真实视觉测量逻辑 Random random = new Random(); return 49.8 + random.NextDouble() * 0.4; // 生成49.8~50.2之间的随机数 } -
语法结构:
cs// 先判断条件,条件为true则执行循环体 while (条件表达式) { // 循环执行的逻辑 // 必须有修改条件的语句,避免死循环 } -
工控实战示例(等待相机连接成功):
csbool isCameraConnected = false; int retryCount = 0; // 重试次数 const int maxRetry = 5; // 最大重试次数 // 循环等待相机连接,最多重试5次 while (!isCameraConnected && retryCount < maxRetry) { try { CameraHelper camera = new CameraHelper("192.168.1.100"); camera.InitCamera(); isCameraConnected = true; // 连接成功,修改条件 rtbLog.AppendText("相机连接成功\r\n"); } catch (Exception ex) { retryCount++; // 重试次数自增 rtbLog.AppendText($"第{retryCount}次连接失败:{ex.Message}\r\n"); System.Threading.Thread.Sleep(1000); // 等待1秒后重试 } } if (!isCameraConnected) { MessageBox.Show($"相机连接失败,已重试{maxRetry}次", "错误提示"); } -
语法结构:
cstry { // 可能抛出异常的代码(相机操作、通信、数据库操作等) } catch (Exception ex) { // 捕获异常并处理(提示用户、记录日志等) } finally { // 无论是否发生异常,都会执行的代码(释放资源、关闭连接等) } -
工控实战示例(串口操作异常处理):
csprivate SerialPort _serialPort; private void OpenSerialPort() { try { _serialPort = new SerialPort(); _serialPort.PortName = "COM3"; _serialPort.BaudRate = 9600; _serialPort.Open(); // 可能抛出异常:串口被占用、不存在等 lblSerialStatus.Text = "串口已打开"; lblSerialStatus.ForeColor = Color.Green; } catch (Exception ex) { // 捕获异常并提示用户 MessageBox.Show($"串口打开失败:{ex.Message}", "错误提示", MessageBoxButtons.OK, MessageBoxIcon.Error); // 记录异常日志 rtbLog.AppendText($"【错误】{DateTime.Now}:串口打开失败-{ex.Message}\r\n"); } finally { // 无需额外操作,若串口打开失败,_serialPort为null,无需释放 // 若为相机/数据库连接,此处可执行释放操作 } }
三、 面向对象编程(工控代码封装与复用核心)
工控上位机开发强调代码的可维护性和复用性(如多个项目共用相机操作、数据库操作逻辑),面向对象编程(OOP)是实现该目标的核心手段,重点掌握「类」和「方法」的封装,无需深入继承、多态等复杂特性。
1. 核心概念:类(Class)
类是「相关数据和功能的封装容器」,工控场景中可按「功能模块」划分类(如相机操作类、数据库操作类、视觉检测类),实现代码隔离和复用。
-
类的定义语法:
cs// 访问修饰符:public(公开,其他类可访问)/ private(私有,仅本类内部访问) public class 类名 { // 1. 私有字段(存储类的内部数据,外部不可直接访问) private 数据类型 字段名; // 2. 构造函数(初始化类的实例,创建对象时自动执行) public 类名(参数列表) { // 初始化字段 } // 3. 公共方法(提供外部访问的功能,实现核心逻辑) public 返回值类型 方法名(参数列表) { // 方法逻辑 } // 4. 私有方法(本类内部使用,辅助实现公共方法逻辑) private 返回值类型 方法名(参数列表) { // 方法逻辑 } }
2. 核心概念:方法(Method)
方法是「封装单一功能的代码块」,一个方法对应一个核心功能(如「初始化相机」、「插入检测数据」、「发送 Modbus 指令」),便于调试和修改。
-
方法的定义语法:
cs// 访问修饰符 返回值类型 方法名(参数列表) public double GetOuterDiameter(int partNo) { // 方法逻辑 Random random = new Random(); double diam = 49.8 + random.NextDouble() * 0.4; return diam; // 返回测量值(与返回值类型一致) } // 无返回值方法:返回值类型为void public void InsertMeasureData(int partNo, double diam, DateTime detectTime) { // 方法逻辑(存储数据,无需返回值) using (SqlConnection conn = new SqlConnection(_connStr)) { string sql = "INSERT INTO T_Measure(PartNo, OuterDiam, DetectTime) VALUES(@PartNo, @OuterDiam, @DetectTime)"; SqlCommand cmd = new SqlCommand(sql, conn); cmd.Parameters.AddWithValue("@PartNo", partNo); cmd.Parameters.AddWithValue("@OuterDiam", diam); cmd.Parameters.AddWithValue("@DetectTime", detectTime); conn.Open(); cmd.ExecuteNonQuery(); } }
3. 工控实战:类与方法的封装(相机操作类)
这是工控项目中最典型的封装示例,可直接复用在所有视觉检测项目中。
cs
using System;
using HalconDotNet;
using Cognex.VisionPro;
// 相机操作类:封装相机初始化、图像采集、资源释放功能
public class CameraHelper : IDisposable
{
// 私有字段:仅本类内部访问,存储相机实例和IP
private HCamera _hCamera; // 相机实例
private string _cameraIP; // 相机IP地址
private bool _isDisposed = false; // 释放状态标记
// 事件:图像采集完成后触发,用于传递采集到的图像
public event Action<CogImage8Grey> ImageGrabbed;
// 构造函数1:无参构造(默认IP)
public CameraHelper()
{
_cameraIP = "192.168.1.100"; // 默认相机IP
}
// 构造函数2:带参构造(自定义IP,更灵活)
public CameraHelper(string cameraIP)
{
_cameraIP = cameraIP; // 初始化相机IP
}
// 公共方法:初始化相机(外部可调用)
public void InitCamera()
{
try
{
_hCamera = new HCamera();
_hCamera.Open(_cameraIP); // 打开相机
_hCamera.SetContinuousGrab(true); // 设置连续采集
_hCamera.GrabStarted += HC_GrabStarted; // 绑定采集事件
Console.WriteLine("相机初始化成功");
}
catch (Exception ex)
{
throw new Exception($"相机初始化失败:{ex.Message}"); // 抛出异常,由调用方处理
}
}
// 私有方法:相机采集回调(仅本类内部使用,外部不可访问)
private void HC_GrabStarted(object sender, EventArgs e)
{
HImage hImage = _hCamera.GrabImage(); // 采集Halcon图像
CogImage8Grey cogImage = ConvertHImageToCogImage(hImage); // 转换为VisionPro图像
ImageGrabbed?.Invoke(cogImage); // 触发事件,传递图像
}
// 私有方法:图像格式转换(辅助功能,内部封装)
private CogImage8Grey ConvertHImageToCogImage(HImage hImage)
{
hImage.GetImagePointer1(out IntPtr ptr, out int type, out int width, out int height);
return new CogImage8Grey(width, height, ptr);
}
// 公共方法:释放相机资源(外部可调用)
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
// 受保护方法:实际释放资源逻辑
protected virtual void Dispose(bool disposing)
{
if (!_isDisposed)
{
if (disposing)
{
// 释放托管资源
_hCamera?.Close(); // 关闭相机
_hCamera?.Dispose(); // 释放相机实例
}
_isDisposed = true;
}
}
}
4. 类的使用(实例化对象)
封装好的类需要「实例化对象」才能使用其功能,这是工控代码调用的核心步骤。
cs
// 1. 实例化相机对象(使用带参构造,传入自定义IP)
CameraHelper cameraHelper = new CameraHelper("192.168.1.101");
try
{
// 2. 调用对象的公共方法:初始化相机
cameraHelper.InitCamera();
// 3. 绑定对象的事件:接收采集到的图像
cameraHelper.ImageGrabbed += CameraHelper_ImageGrabbed;
lblCameraStatus.Text = "相机已连接";
lblCameraStatus.ForeColor = Color.Green;
}
catch (Exception ex)
{
MessageBox.Show($"相机操作失败:{ex.Message}", "错误提示");
}
// 4. 图像采集事件处理方法
private void CameraHelper_ImageGrabbed(CogImage8Grey obj)
{
// 跨线程更新UI,显示采集到的图像
this.Invoke(new Action(() =>
{
_cogDisplay.Image = obj;
_cogDisplay.Fit(true);
}));
// 调用视觉检测方法,处理图像
_visionHelper.RunDetection(obj);
}
// 5. 窗体关闭时,释放相机资源
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
{
cameraHelper?.Dispose();
}
四、 核心总结(工控场景重点提炼)
- 变量与数据类型 :优先掌握
int/double/bool/string/DateTime,重点练习类型转换(Parse/TryParse),避免空值和类型不匹配异常。 - 流程控制 :
if-else是工控首选,for循环用于批量处理,while循环用于等待 / 重试,try-catch必须包裹所有不可控操作(相机、通信、数据库)。 - 面向对象:按功能模块封装类(相机 / DB / 视觉),一个方法对应一个单一功能,通过「实例化对象」调用类的方法,实现代码复用和可维护性。
- 工控避坑 :变量初始化、避免中文命名、异常处理、资源释放(
Dispose/using),这四点是保证程序稳定运行的关键。