2026-05-09
C#
0

车间里有台注塑机,PLC 每秒往上位机推一个 byte 类型的状态字。

你打开数据,看到的是 0b10110010——8个二进制位,每一位代表一个报警状态。

领导问:"哪几个报警同时触发了?"

你盯着这串数字,脑子里一片空白。

这不是 PLC 的事,也不是通信协议的事。

你缺的,是位运算。

今天这节课,把算术、逻辑、位运算三类运算符一次讲清楚,工控程序里的数据处理,你会顺多了。


📌 上节回顾

「上一节我们学了类型转换,掌握了隐式转换、显式转换和 Convert 类的使用方法。

今天在这个基础上,我们进一步学习运算符——数据有了,怎么算、怎么判断、怎么拆位,全靠它。」


💡 核心知识讲解

一、算术运算符:数字怎么算

算术运算符是最基础的一类,和数学里的加减乘除基本一样。

运算符含义示例结果
+加法3 + 58
-减法10 - 46
*乘法6 * 742
/除法10 / 33(整数除法)
%取余10 % 31

「注意:两个 int 相除,结果还是 int,小数部分直接丢掉,不是四舍五入。」

工业类比:生产线上统计每班产量时,% 取余运算非常常用。

比如每箱装 24 件,生产了 100 件,100 % 24 = 4,说明最后一箱还差 20 件才装满。


二、比较与逻辑运算符:条件怎么判

比较运算符(也叫关系运算符)用来判断两个值的大小关系,结果只有两种:truefalse

运算符含义示例
==等于temp == 85
!=不等于status != 0
> / <大于 / 小于count > 100
>= / <=大于等于 / 小于等于voltage <= 380

逻辑运算符用来组合多个条件,这在工控报警里极其常见。

运算符含义说明
&&与(AND)两个条件都成立才为 true
||或(OR)有一个成立就为 true
!非(NOT)取反

工业类比:把 && 想成 PLC 梯形图里的串联触点|| 想成并联触点! 就是常闭触点

⚠️ 注意区分 =(赋值)和 ==(比较)。if (temp = 85) 是赋值,不是判断,编译器会报错。这是新手最常犯的错之一。


三、位运算符:寄存器里的硬核操作

位运算(Bitwise Operation)是直接对二进制位进行操作的运算。

在工业开发里,PLC 状态字、Modbus 寄存器、设备 I/O 状态,全都是按位打包的数据。

不懂位运算,就读不懂这些数据。

常用位运算符一览:

运算符名称作用
&按位与提取指定位(掩码操作)
|按位或强制置位某一位
^按位异或翻转指定位
~按位取反所有位取反
<<左移相当于乘以 2 的 N 次方
>>右移相当于除以 2 的 N 次方

最常用的是 &(按位与)——用来提取某一位的状态。

工业类比:把一个 byte 想成一排 8 个开关,每个开关控制一个报警灯。

& 加上一个"掩码",就像拿一张只开了一个孔的纸盖上去,只看你想看的那一位。

状态字: 1011 0010 掩码: & 0000 0010 (只看第1位) 结果: 0000 0010 (第1位是1,说明该报警触发了)

「掩码(Mask):一个专门用来提取或屏蔽特定位的二进制数,工控程序里随处可见。」


四、赋值与复合运算符:简写更高效

C# 支持复合赋值运算符,写法更简洁,逻辑更清晰。

写法等价于
count += 1count = count + 1
total -= losstotal = total - loss
flags |= 0x04flags = flags | 0x04(置位第2位)
flags &= ~0x04flags = flags & ~0x04(清除第2位)

flags |= 0x04flags &= ~0x04 这两句是工控代码里置位清位的标准写法,记住它。

2026-05-08
C#
0

🎯 你是不是也遇到过这些抓狂的时刻?

做WPF项目的时候,产品经理拿着一张设计稿过来说:"这个按钮要做成圆角的,悬停变色,点击有波纹效果。"然后你打开代码,发现默认的Button长这样——方方正正,毫无生气。

改样式?Style只能改颜色、字体、边距,根本动不了控件的骨架。于是你开始Google,翻StackOverflow,最后发现一个词:ControlTemplate(控件模板)

这玩意儿,才是WPF外观定制的真正核武器。

我在项目中统计过,超过60%的UI定制需求,Style解决不了,必须上ControlTemplate。而很多开发者在第一次接触它时,往往因为概念模糊、结构复杂而望而却步,白白浪费了WPF最强大的特性之一。

读完这篇文章,你将掌握:

  • ControlTemplate的底层机制与工作原理
  • 3个渐进式实战方案(从简单改造到完全重绘)
  • 常见踩坑点与规避策略

🔍 问题深度剖析:Style能做什么,做不到什么?

很多同学刚开始学WPF,把Style和ControlTemplate混为一谈,这是第一个误区。

Style(样式) 的本质是属性集合——它能批量设置控件的BackgroundFontSizeMarginPadding等依赖属性,也能通过触发器(Trigger)响应状态变化。但它改不了控件的视觉结构,因为控件的视觉结构由ControlTemplate决定。

换个比喻:Style是给一栋房子刷漆、换地板、装窗帘;而ControlTemplate是重新设计这栋房子的建筑图纸,连墙的位置都能改。

WPF中每个控件(Button、TextBox、ListBox等)都有一个默认的ControlTemplate,由系统主题提供。这个模板定义了控件长什么样、由哪些元素组成。当你需要彻底改变控件的外观时,就必须替换这个模板。

常见的错误认知:

  • ❌ "用Style加个圆角就行了" → Border的CornerRadius不是Button的直接属性,Style改不到
  • ❌ "ControlTemplate太复杂,能不用就不用" → 一旦UI需求复杂,逃不掉的
  • ❌ "重写模板会丢失控件功能" → 只要正确使用TemplatePart和TemplateBinding,功能完全保留

💡 核心要点提炼:ControlTemplate的底层机制

🧱 视觉树与逻辑树的分离

WPF有两棵树:逻辑树(LogicalTree)视觉树(VisualTree)。逻辑树描述控件的层次关系,视觉树描述实际渲染的元素结构。ControlTemplate替换的正是控件的视觉树部分,而逻辑树保持不变。

这意味着:你完全重写了Button的外观,但Button的Click事件、Command绑定、IsEnabled状态依然正常工作。逻辑与视觉彻底解耦,这是WPF架构最优雅的地方之一。

🔗 TemplateBinding:模板与控件的数据桥梁

在ControlTemplate内部,子元素无法直接读取外部控件的属性。这时就需要TemplateBinding——它是一种专为模板设计的单向绑定,性能比普通Binding更高(不需要反射查找,编译时确定)。

xml
<!-- TemplateBinding示例:将控件的Background传递给模板内的Border --> <Border Background="{TemplateBinding Background}" CornerRadius="8"/>

🎯 ContentPresenter:内容的占位符

对于ContentControl(Button、Label等),模板内必须有一个ContentPresenter来告诉WPF"把控件的Content放在这里"。少了它,你设置的Button文字或图标就消失了。

🔄 VisualStateManager:状态驱动的现代方案

相比老式的Trigger,VisualStateManager(VSM) 是更现代、更推荐的状态管理方式。它将控件状态(Normal、MouseOver、Pressed、Disabled)与视觉变化解耦,支持平滑动画过渡,代码可读性更强。


2026-05-08
C#
0

你有没有遇到过这种情况:

从 PLC 读回来一个温度值,明明是 "85.6",存的是字符串。你想把它和报警阈值 90.0 比大小,结果编译器直接给你报红——"无法将 string 隐式转换为 double"。

你改了半天,改出了一个新问题:数值截断了,85.6 变成了 85,精度没了。

这种情况,不是你代码写得差,是你还没搞清楚 C# 的类型转换规则。今天这篇,把三种转换方式讲透,工厂场景全覆盖。


📌 上节回顾

「上一节我们学了 constenum,掌握了用常量锁定报警阈值、用枚举定义设备状态的方法。

今天在这个基础上,我们进一步学习如何在不同数据类型之间安全地"搬运"数值——类型转换。」


💡 核心知识讲解


为什么工厂程序特别需要类型转换?

工业现场的数据来源极其复杂。

PLC 给你的是 int,数据库存的是 string,界面控件绑定的是 double,通信协议传来的是 byte[]

这些数据要在一起"工作",就必须先统一"语言"。类型转换,就是让不同格式的数据能互相理解的翻译官。

C# 里的类型转换,主要分三种:隐式转换、显式转换(强制转换)、Convert 类转换。


第一种:隐式转换(系统自动帮你转)

隐式转换(Implicit Conversion):不需要写任何额外代码,编译器自动完成,且100%安全,不会丢失数据。

类比工厂:就像把一个 500ml 的量杯里的水倒进 1000ml 的量杯,绝对装得下,不会溢出,不用你操心。

什么情况下可以隐式转换? 简单记:小范围 → 大范围,整数 → 浮点数。

从(小)到(大)是否安全
intlong✅ 安全
intdouble✅ 安全
floatdouble✅ 安全
byteint✅ 安全

举个工厂例子:设备编号是 int,统计报表需要 long 类型存储,直接赋值就行,编译器不报错。


第二种:显式转换(你亲自动手,风险自负)

显式转换(Explicit Conversion),也叫强制转换(Cast),需要你用括号明确告诉编译器"我知道风险,我要转"。

类比工厂:把 1000ml 量杯的水倒进 500ml 量杯——可以倒,但超出的部分会溢出丢失。

语法格式:(目标类型)变量名

「⚠️ 警示:显式转换可能造成数据精度损失或溢出,使用前必须确认数值范围。」

比如把 double 类型的温度值 85.6 强制转成 int,结果是 85,小数点后直接截断,不是四舍五入

这在工业场景里很危险——报警阈值如果精度丢失,可能导致设备该停不停。


第三种:Convert 类(最稳、最推荐)

Convert 类是 .NET 提供的万能类型转换工具箱,专门处理各种类型之间的转换,尤其擅长处理字符串和数值之间的互转

类比工厂:这是一台专业的数据格式转换机器,带校验、带报错提示,转换失败会直接告诉你哪里出问题,不会悄悄给你一个错误结果。

常用方法速查:

方法功能工厂场景举例
Convert.ToDouble()转为 doublePLC 字符串温度值 → 浮点数
Convert.ToInt32()转为 int产量字符串 → 整数
Convert.ToString()转为 string数值 → 界面显示文本
Convert.ToBoolean()转为 bool"1"/"0" → 开关状态

「✅ 推荐:工厂程序中处理外部数据(PLC、数据库、串口)时,优先使用 Convert 类,而不是强制转换。」


三种方式怎么选?

一句话记住:能隐式就隐式,必须显式要小心,外部数据用 Convert。


💻 VS2026 操作步骤

Step 1 — 新建控制台项目

打开 VS2026,选择 文件 > 新建 > 项目,搜索"控制台应用",选择 C# 控制台应用 (.NET 10),项目名填 TypeConversionDemo,点击创建。

Step 2 — 让 Copilot 生成代码框架

Program.cs 文件中,按下 Alt + / 唤出 GitHub Copilot 内联聊天,输入以下 Prompt:

「帮我写一个工厂温度数据类型转换的演示程序,包含隐式转换、显式转换和 Convert 类三种方式,变量命名使用工业语义,加中文注释」

Copilot 会自动生成带注释的代码框架,你在此基础上调整即可,效率提升 80%。

Step 3 — 运行与调试

F5 运行,在控制台窗口观察三种转换方式的输出结果。如有异常,Copilot 会在"错误列表"面板旁提供 "用 Copilot 修复" 按钮,点击后自动给出修复建议。

Step 4 — 验证精度损失

在显式转换代码处,故意输入一个带小数的温度值(如 99.9),观察强制转换后精度丢失的现象,加深记忆。


Vibe Coding Prompt 参考写法

如果你想用 Vibe Coding 方式快速生成本节完整示例,可以在 VS2026 Copilot Chat 面板输入:

我是工厂工程师,正在学C# .NET 10。 请帮我写一个控制台程序,演示以下三种类型转换: 1. 隐式转换:设备编号 int → long 2. 显式转换:double温度值 → int,展示精度损失 3. Convert类:从字符串读取温度值并转为double,包含异常处理 变量名用工业语义命名,每段加中文注释,代码风格适合初学者阅读。

image.png

2026-05-08
Python
0

还记得第一次看到程序能"看懂"图片时那种震撼吗?

说实话,我刚接触计算机视觉那会儿,真的被惊到了。就像变魔术似的——几行Python代码,电脑就能识别出照片里的猫啊狗啊,甚至人脸。这玩意儿在我之前的认知里,那可是高深莫测的"黑科技"。

但真正上手后才发现,入门的第一步其实并没想象中那么难

今天咱们就从最基础的开始:怎么把OpenCV这个强大的工具请进你的Python程序,然后让它帮你读取、显示一张图片。别小看这个简单动作,这可是所有图像处理、人脸识别、目标检测的起点。掌握了这个,你就算正式踏进计算机视觉的大门了。

这篇文章会手把手带你走完整个流程,我会把我踩过的坑、遇到的奇葩问题都告诉你。读完你就能写出自己的第一个OpenCV程序,而且保证能跑起来!


🔍 为什么OpenCV是你的最佳选择?

在Windows下搞图像处理,库的选择其实不少。PIL、Pillow、scikit-image... 但为啥我强烈推荐OpenCV?

三个字:快、全、稳。

OpenCV(Open Source Computer Vision Library)这家伙最早是Intel搞出来的,现在已经成了计算机视觉领域的事实标准。它用C++写的底层,速度飞快;Python接口又特别友好,上手轻松。

更关键的是——它免费开源,而且社区超级活跃。遇到问题随便一搜,Stack Overflow上一大堆解决方案。这点对新手来说简直太友好了。

我见过太多初学者在这个阶段就卡壳了。不是因为技术难,而是因为环境没配好、库没装对、路径搞错了... 这些看似简单的问题,能让人抓狂一整天。

所以接下来,咱们要格外仔细。


🛠️ 第一关:把OpenCV请进你的电脑

📦 安装前的准备工作

首先,确保你的Python环境是健康的。打开命令提示符(按Win+R,输入cmd),敲下这行:

bash
python --version

如果显示类似Python 3.8.10这样的版本号,那就OK了。建议使用Python 3.7及以上版本,兼容性更好。

小插曲:我之前在一台老电脑上装,发现Python版本是2.7的古董,结果各种报错。后来才知道OpenCV 4.x基本不支持Python 2了。所以这步检查真的很重要!

⚡ 一键安装OpenCV

理论上,安装OpenCV就一行命令的事儿:

bash
pip install opencv-python

等个几分钟(具体时间看你网速),看到Successfully installed opencv-python-x.x.x就说明成功了。

但是! 在国内这个网络环境下,直接用pip安装可能会慢到你怀疑人生,甚至直接超时失败。

这时候你需要切换到国内镜像源。我个人习惯用清华的镜像,又快又稳:

bash
pip install opencv-python -i https://pypi.tuna.tsinghua.edu.cn/simple

速度瞬间起飞,基本半分钟就能搞定。

2026-05-07
C#
0

"咱们那个设备监控界面卡得要命,刷新一下CPU直接飙到80%,客户那边都投诉了!"

你有没有遇到过这种情况?明明只是画几个圆圈、几条线,为啥界面就像老年机一样卡顿?

我打开代码一看——好家伙,满屏的PictureBox控件,每个控件都在Load事件里疯狂加载图片资源。这哪儿顶得住啊!后来花了一个周末重构,改用GDI+直接绘图,CPU占用直接降到5%以内。客户那边第二天就打电话过来:"这次更新太给力了,界面丝般顺滑!"

今天咱们就聊聊,如何用C#的GDI+打造工业级的动态界面。不整虚的,全是干货。

💥 为什么你的界面又卡又丑?

三个致命误区

很多初学者(包括以前的我)在做工业控制界面时,会掉进这些坑:

误区一:疯狂堆砌控件
什么东西都用控件。画个圆?拖个PictureBox。显示数字?再拖个Label。结果呢?一个界面200多个控件,Form_Load执行了3秒还没加载完。

误区二:Timer里直接操作控件属性
为了实现动画效果,在Timer的Tick事件里不停地修改控件的Location、Size、BackColor...每次修改都会触发重绘,整个窗体闪得像蹦迪现场。

误区三:没有双缓冲概念
直接在Panel或Form上画,每次刷新都能看到明显的撕裂和闪烁。用户体验?不存在的。

我曾经接手过一个项目,前任开发为了显示一个旋转的泵,创建了36张不同角度的PNG图片,然后用Timer切换Image属性。这内存占用...简直了。

先看一下效果

image.png

🎯 GDI+才是正道——一个完整案例

咱们直接上硬菜。看看开头那个工业流程模拟系统的核心实现。

🚀 架构设计思路

整个系统分三层:

  • 绘制层:所有UI元素用Graphics对象绘制
  • 逻辑层:状态变量管理(液位、泵状态、阀门状态)
  • 动画层:Timer驱动的增量更新

关键在于:只用一个Panel作画布,所有组件都是"假的",其实是动态绘制出来的

🔧 核心状态管理

csharp
// 核心状态变量 private double tankLevel = 80.0; // 水箱液位 private bool pumpRunning = false; // 泵运行状态 private bool valveOpen = false; // 阀门开关状态 private double flowRate = 0.0; // 流量值 private double pumpAngle = 0; // 泵叶片旋转角度 private Random random = new Random(); // 模拟真实传感器噪声 private Rectangle valveArea = new Rectangle(525, 380, 50, 60); // 阀门点击区域

注意这里有个小细节——valveArea。这是为了实现画布交互。虽然阀门是画出来的,但咱们可以通过MouseClick事件判断点击位置是否在阀门区域内,从而响应用户操作。

这招在工业界面里特别实用。比如你要做一个管道流程图,几十个阀门,总不能为每个阀门创建一个控件吧?