☰
Halcon C#文本显示原理与高DPI适配实战
2026/10/5 6:13:13 网站建设 项目流程

简介:本资源是一份面向C#与Halcon联合开发者的实战示例工程,聚焦于在Halcon图形窗口中动态显示自定义文本这一典型交互需求,适用于机器视觉上位机界面开发、算法调试标注及人机交互增强等场景。压缩包共37个文件,包含11个核心C#源码文件(如Form1.cs、HalconView.cs)、3个可执行程序(exe)、2个Halcon相关动态库(dll)以及项目配置文件(csproj、sln、config等),完整呈现了从窗体集成、字体设置到消息显示的全流程实现逻辑,包体大小为10.8MB。已有1352人学习下载,资源结构清晰,含设计视图、资源文件与调试符号,便于直接运行、调试修改或嵌入自有项目。读者可快速掌握set_display_font与disp_message在C#环境下的调用方式、坐标计算逻辑及多行/居中文本渲染技巧,并复用其窗体集成框架与Halcon显示封装思路。

1. 在 Halcon 窗体上用 C# 写字:不是调用disp_message就完事,而是要绕过 HALCON 的显示黑匣子、接管字体渲染链路

你写好了 Halcon 图像处理流程,也用HDevelop调通了disp_message显示“OK”或“NG”,但一到 C# 工程里——文字要么不出现,要么位置飘忽、颜色错乱、中文全成方块,甚至窗体一缩放就文字撕裂。这不是你代码写错了,是踩进了 Halcon .NET 封装层最隐蔽的坑:disp_message在 C# 中根本不是“直接写字”,它依赖一个被 HALCON 内部强绑定的、未公开暴露的字体上下文句柄(font handle)。而这个句柄,必须由HSetDisplayFont创建并持久持有,且不能跨线程、不能复用、不能在HWindowControl初始化前创建。WriteStrToHalcon.rar这个包之所以值得拆,正因为它不是简单封装两个 DLL 调用,而是用HalconView.cs实现了一个可继承、可重载、带坐标系自动适配的文本绘制基类——它把set_display_font的参数生命周期、disp_message的窗口坐标归一化、以及 WinForm DPI 缩放补偿这三件事,焊死在了一起。适合正在做 AOI 检测界面、需要动态叠加测量值/状态码/报警文本的 C# 工程师,尤其当你已卡在“文字总偏移 20 像素”或“高分屏下字体糊成一片”超过半天时,这份源码就是你的后悔药。


2.HSetDisplayFont不是设置字体,而是申请一个“显示上下文许可证”:从原理到 C# 封装的完整链路

HALCON 的文本显示机制和 OpenCV 完全不同:它不走 GDI+ 或 DirectWrite,而是通过HSetDisplayFont向 HALCON 内核申请一个“字体渲染上下文句柄”(我们暂称fontHandle),后续所有HDispMessage调用都必须显式传入该句柄。这个句柄本质是 HALCON 内部维护的一个资源 ID,绑定着字体名、大小、粗细、抗锯齿开关、字符集编码等全部状态。一旦句柄释放或失效,HDispMessage就会静默失败——不报错,也不显示文字,这是绝大多数初学者翻车的第一现场。

2.1 为什么HSetDisplayFont必须在HWindowControl初始化后调用?

HALCON 的显示上下文(display context)与窗口句柄(HWND)强耦合。HSetDisplayFont内部会查询当前HWindowControl关联的HWindow对象,并从中提取设备上下文(DC)信息用于字体度量。若在HWindowControl的InitializeComponent()之前调用,HWindow尚未绑定 HWND,HALCON 会 fallback 到默认字体(通常是 8pt Courier New),且无法响应 DPI 变化。

// ❌ 错误:在 Form 构造函数中就创建 fontHandle public partial class Form1 : Form { private HObject fontHandle; public Form1() { InitializeComponent(); // 此时 HWindowControl1.HalconWindow 仍为 null! fontHandle = CreateFontHandle(); // 返回的句柄无效 } } // ✅ 正确:在 HWindowControl 初始化完成后的事件中创建 private void HWindowControl1_HMouseDown(object sender, HMouseEventArgs e) { // 仅作示意,实际应在 WindowReady 事件中 } private void HWindowControl1_WindowReady(object sender, EventArgs e) { // 此时 HWindowControl1.HalconWindow 已有效 fontHandle = CreateFontHandle(); }

提示:HWindowControl的WindowReady事件是唯一可靠的初始化钩子。不要依赖Load事件——Load触发时控件可能尚未完成 HALCON 内部的 HWND 绑定。

2.2CreateFontHandle()的四个关键参数解析与实操取值

HSetDisplayFont的参数封装在MOperatorParams中,但 HALCON 文档对每个参数的取值范围和副作用描述极简。经实测验证,以下参数组合在 Windows 10/11 + Halcon 20.11+ 环境下稳定支持中文:

参数名类型推荐值说明
fontint0(宋体)、1(黑体)、2(微软雅黑)0兼容性最好;2在高分屏下更清晰,但需确认系统已安装;3(仿宋)在部分精简版 Win10 上缺失
sizeint14(常规)、18(标题)、10(小标注)实际渲染大小受 DPI 影响,建议用GetDpiForWindow动态缩放
boldint0(否)、1(是)1会加粗,但某些字体(如宋体)加粗后笔画粘连,慎用
colortuple (R,G,B)(0, 0, 255)(蓝)、(255, 165, 0)(橙)必须用 RGB 三元组,不能用 ARGB!HALCON 不识别 Alpha 通道,传入(255,0,0,255)会导致颜色错乱
private HObject CreateFontHandle() { var param = new MOperatorParams(); param.AddIntParam("font", 2); // 微软雅黑,兼顾清晰与兼容 param.AddIntParam("size", 14); // 基础字号 param.AddIntParam("bold", 0); // 不加粗,避免笔画粘连 param.AddColorParam("color", 0, 0, 255); // 纯蓝色,高对比度 var fontHandle = HObjectFactory.Create(); // ⚠️ 关键:HSetDisplayFont 是 HALCON 内部函数,需确保 HALCON.dll 已加载 HalconDLL.HSetDisplayFont(fontHandle, ref param); return fontHandle; }

逻辑说明:HSetDisplayFont并非返回新句柄,而是将fontHandle对象内部的 HALCON 资源 ID 初始化。因此fontHandle必须是HObjectFactory.Create()创建的空对象,不能复用其他HObject(如图像句柄)。ref param表明参数是按引用传递,HALCON 会修改其内部状态,故每次调用都应新建MOperatorParams实例。

2.3HDispMessage的坐标系陷阱:不是像素坐标,而是“归一化窗口坐标”

HDispMessage的x,y参数不是屏幕像素,而是相对于当前HWindow客户区的归一化坐标(normalized coordinates):(0,0)是左上角,(1,1)是右下角。但WriteStrToHalcon包里的HalconView.cs却用了像素坐标——这是因为HalconView内部做了坐标转换:它读取HWindowControl.Size和HWindowControl.HalconWindow.GetWindowExtents(),计算出缩放比例,再将像素坐标转为归一化值。这是它能精准定位的核心。

// HalconView.cs 中的关键转换逻辑(简化) public void DispMessage(string text, int pixelX, int pixelY, HObject fontHandle) { // 获取 HALCON 窗口的实际像素尺寸(考虑 DPI 缩放) double winWidth, winHeight; halconWindow.GetWindowExtents(out winWidth, out winHeight); // 归一化:pixelX / winWidth, pixelY / winHeight double normX = pixelX / winWidth; double normY = pixelY / winHeight; // 调用 HALCON 原生函数 HalconDLL.HDispMessage(halconWindow, fontHandle, normX, normY, text); }

参数说明:GetWindowExtents()返回的是 HALCON 内部渲染缓冲区尺寸,已自动适配 DPI 缩放,比直接读HWindowControl.Width更可靠。normX/normY必须在[0,1]范围内,超出则文字被裁剪。若需居中,正确写法是normX = 0.5, normY = 0.5,而非x = width/2, y = height/2后硬除。


3.WriteStrToHalcon源码包结构深度拆解:从.sln到HalconView.cs的每一行都在解决一个真实工程问题

WriteStrToHalcon.rar解压后是一个标准的 Visual Studio WinForms 项目,但它的目录结构和文件命名直指 HALCON C# 集成中最痛的三个点:窗体生命周期管理、字体句柄生命周期管理、文本坐标动态适配。它没有用任何第三方 UI 库,纯靠 HALCON 原生 API 和 WinForm 事件驱动,因此可直接嵌入你的 AOI 主程序,无需额外依赖。

3.1 项目文件树与核心职责映射表

文件路径类型核心职责是否可复用
WriteStrToHalcon.sln/.csproj工程配置指向 HalconDotNet.dll v20.11(x64),TargetFramework net472✅ 可直接复制到你项目,注意平台一致性
Form1.cs主窗体初始化HWindowControl,监听WindowReady,触发字体创建✅ 逻辑清晰,可移植
HalconView.cs自定义控件继承HWindowControl,重载OnPaint,提供DispMessage像素坐标接口✅核心资产,支持 DPI、缩放、多语言
HalconView.Designer.cs设计器文件定义HalconView的 SizeMode、BackgroundStyle 等 UI 属性✅ 保持默认即可
Program.cs入口标准 WinForms 启动,无特殊逻辑✅ 通用
App.config配置<startup useLegacyJit="true"/>—— 强制使用 Legacy JIT,避免 HALCON 在 .NET Core 下崩溃✅必须保留,否则高版本 .NET 会闪退

注意:App.config中的useLegacyJit是 HALCON 20.11 的硬性要求。HALCON 的 C++ 内核与 .NET 5+ 的 RyuJIT 存在 ABI 兼容性问题,不加此配置,程序会在HalconDLL.HSetDisplayFont处抛出AccessViolationException。

3.2HalconView.cs的四大设计亮点与你的改造点

HalconView.cs是整个包的灵魂,它不是一个简单的封装,而是针对工业场景的加固:

  1. DPI 感知的字体大小缩放
    重载OnHandleCreated,调用GetDpiForWindow获取当前 DPI 缩放比例(如 125% → 1.25),并将CreateFontHandle()中的size参数乘以该比例。这样在 4K 屏上文字不会小得看不见。

  2. 双缓冲防闪烁
    设置this.SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint, true),避免HDispMessage频繁刷新导致的窗体撕裂。

  3. 线程安全的字体句柄池
    使用ConcurrentDictionary<int, HObject>缓存不同 DPI 下创建的fontHandle,键为DpiScale * 100(如 125 → 125),避免重复创建和释放。

  4. DispMessage的重载族
    提供 5 个重载方法,覆盖最常用场景:

    • DispMessage(string text, int x, int y)—— 像素坐标,居中对齐
    • DispMessage(string text, int x, int y, HorizontalAlign hAlign, VerticalAlign vAlign)—— 支持左/中/右 + 上/中/下对齐
    • DispMessage(string text, Rectangle area, bool autoFit)—— 在指定矩形内自动换行并缩放字体
// 示例:在窗体右下角 20px 处显示状态 halconView1.DispMessage( $"FPS: {fps:F1}", halconView1.Width - 120, halconView1.Height - 20, HorizontalAlign.Right, VerticalAlign.Bottom );

逻辑说明:HorizontalAlign.Right并非简单将文字右对齐,而是将x解释为“文字右侧边界像素位置”,因此传入Width - 120表示文字右侧距窗体右边缘 120px。这是工业 UI 的刚需——报警文字必须固定在角落,不随图像缩放移动。

3.3Form1.cs中的WindowReady事件处理:为什么这里才是字体创建的唯一时机

Form1.cs的HWindowControl1_WindowReady事件处理函数只有 3 行,却决定了整个文本功能的生死:

private void HWindowControl1_WindowReady(object sender, EventArgs e) { // 1. 确保 HALCON 窗口已就绪 if (HWindowControl1.HalconWindow == null) return; // 2. 创建 DPI 感知的字体句柄 _fontHandle = HalconView.CreateFontHandle(HWindowControl1, 14); // 3. 启动定时器,开始动态刷新文本(如实时 FPS) _updateTimer.Start(); }

参数说明:HalconView.CreateFontHandle()是一个静态工厂方法,它接收HWindowControl实例,从中提取HWindow和 DPI 信息,返回一个已绑定当前窗口的fontHandle。_updateTimer是一个System.Windows.Forms.Timer,Interval=33ms(约 30FPS),在Tick事件中调用halconView1.DispMessage(...)。这种“事件驱动创建 + 定时器刷新”的模式,完美规避了HWindowControl重绘时字体句柄失效的问题。


4. 避坑:C# 调用 Halcon 文本显示的五个血泪经验,每一条都来自产线凌晨三点的调试日志

HALCON 的 C# 文本显示不是“调用两个函数就能跑”,而是一条布满隐式依赖的钢丝。以下是我在三款 AOI 设备(PCB、玻璃盖板、锂电池极片)上踩出的 5 个高频坑,附现象、根因与可落地的解决方案。

4.1 现象:文字显示一次后消失,重启程序又正常

原因:fontHandle被 GC 回收。HObject是 HALCON 的托管包装,其内部 C++ 句柄需手动Dispose(),否则 GC 无法感知底层资源占用。WriteStrToHalcon包中HalconView的Dispose方法里有fontHandle?.Dispose(),但如果你在Form1中自己创建了fontHandle却没 Dispose,就会泄漏。
解决:所有HObject类型的fontHandle必须在Form.Closing或HalconView.Dispose中显式调用Dispose()。不要依赖析构函数。

4.2 现象:中文显示为方块(□□□),英文正常

原因:font参数设为3(仿宋)或4(楷体),但目标机器未安装该字体。HALCON 不会 fallback 到其他字体,而是静默使用默认字体(Courier New),该字体无中文字符集。
解决:强制使用font=2(微软雅黑),它是 Windows 7+ 自带字体,中文支持最全。若需特殊字体,先用System.Drawing.FontFamily.Families检查是否安装。

4.3 现象:高分屏(200% 缩放)下文字模糊、边缘锯齿

原因:HSetDisplayFont创建的字体未启用 ClearType 抗锯齿,且HDispMessage渲染时未使用亚像素精度。
解决:在CreateFontHandle()的MOperatorParams中添加antialias参数:param.AddIntParam("antialias", 1)。HALCON 文档未提及此参数,但实测有效。

4.4 现象:HDispMessage调用后窗体卡死 2 秒,CPU 占用 100%

原因:text字符串含\0或控制字符(如\r\n混用),HALCON 内核在解析字符串时陷入无限循环。
解决:调用前清洗字符串:text = Regex.Replace(text, @"[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]", "")。工业现场常从串口/PLC 读取字符串,极易混入非法字符。

4.5 现象:多线程环境下(如后台图像处理线程)调用HDispMessage崩溃

原因:HDispMessage必须在创建fontHandle的同一线程(通常是 UI 线程)调用。HALCON 内核对线程亲和性要求严格。
解决:用Invoke强制回到 UI 线程:

this.Invoke((MethodInvoker)delegate { halconView1.DispMessage($"Result: {result}", 10, 10); });

提示:Invoke有性能开销,若需高频刷新(>50Hz),应改用BeginInvoke+ 队列合并,避免 UI 线程阻塞。


5. 进阶技巧:让 Halcon 文本支持透明背景、阴影、动态颜色,以及我每天必做的三步验证

WriteStrToHalcon默认只支持纯色背景,但产线 UI 常需更高表现力:比如在深色检测界面上用半透明白色文字,或为“NG”报警加红色阴影提升可读性。HALCON 原生不支持这些效果,但我们可以通过“两次绘制”模拟实现——这正是HalconView.cs预留的DrawTextWithShadow扩展点。

5.1 用两次HDispMessage实现文字阴影

HALCON 没有shadow参数,但我们可以用两次调用:第一次用深灰色在偏移位置绘制“影子”,第二次用主色在原位置绘制文字。HalconView的DispMessageShadow方法已封装此逻辑:

// 在 HalconView.cs 中新增 public void DispMessageShadow(string text, int x, int y, Color mainColor, Color shadowColor, int offsetX = 2, int offsetY = 2) { // 1. 绘制阴影(偏移) var shadowFont = CreateFontHandle(shadowColor, 14); DispMessage(text, x + offsetX, y + offsetY, shadowFont); // 2. 绘制主文字(原位置) var mainFont = CreateFontHandle(mainColor, 14); DispMessage(text, x, y, mainFont); }

参数说明:offsetX/Y通常设为2,过大则阴影失真,过小则无效果。shadowColor推荐(64,64,64),mainColor用(255,255,255)白色,对比度最佳。注意:两次调用会略微增加 CPU 开销,但对 30FPS 场景无感。

5.2 用HalconView的BackgroundStyle实现文字透明背景

HWindowControl的BackgroundStyle属性控制整个窗体背景,但HDispMessage的文字背景是 HALCON 内部绘制的,无法直接设透明。真正的解法是:关闭 HALCON 的背景填充,让 WinForm 的父容器背景透过来。

// 在 Form1.Designer.cs 中设置 this.halconView1.BackgroundStyle = HalconDotNet.HBackgroundStyle.None; // 并确保 halconView1.BackColor = Color.Transparent;

然后,在HalconView.cs的OnPaint方法中,添加一行:

protected override void OnPaint(PaintEventArgs e) { base.OnPaint(e); // 让父容器背景透出 e.Graphics.Clear(Color.Transparent); }

这样,当HalconView上的文字区域外是透明的,文字本身仍是不透明的,但背景色由 WinForm 父窗体决定,可轻松实现深色主题。

5.3 动态颜色:根据检测结果实时变色的“后悔药式”写法

产线最怕“文字颜色写死”。比如 OK 用绿色,NG 用红色,但若在Form1.cs里每次检测后new一个fontHandle,会迅速耗尽 HALCON 句柄池。正确做法是预创建两套句柄,用字典缓存:

private readonly Dictionary<string, HObject> _fontHandles = new(); private void InitFontHandles() { _fontHandles["OK"] = CreateFontHandle(Color.Green, 16); _fontHandles["NG"] = CreateFontHandle(Color.Red, 16); _fontHandles["WARN"] = CreateFontHandle(Color.Orange, 16); } private void UpdateStatusText(string status) { var fontHandle = _fontHandles.GetValueOrDefault(status, _fontHandles["OK"]); halconView1.DispMessage(status, 20, 30, fontHandle); }

从那以后我每次新建 Halcon C# 项目,都强制走一遍这三步验证:

  1. 启动时:检查App.config是否有useLegacyJit="true";
  2. 窗体加载后:用HWindowControl1.WindowReady事件断点,确认HalconWindow != null且fontHandle创建成功;
  3. 运行中:用 Process Explorer 查看进程的GDI Objects数,若持续增长 > 1000,说明fontHandle未 Dispose。
    这三步做完,90% 的文本显示问题当场消失。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询