简介:面向 C# 初、中级开发者的 Windows Forms 二维码生成工具完整工程,基于开源 QRCoder 类库实现。压缩包内把动态链接库引用、界面布局与二维码生成逻辑整理为可直接运行的解决方案,包含两个可运行的窗体工程,支持在界面中输入文本或链接,即时生成、显示并保存二维码图片。代码演示了创建二维码数据对象、选择错误校正级别、调用绘图接口输出位图图像等关键 API 的用法,同时可调整像素大小与纠错级别,适合学习 C# 图像处理与二维码功能的快速集成。包内共 43 个文件,以 C# 源程序、可执行程序、动态链接库、系统资源文件及项目配置文件为主,压缩包仅 405KB,目录结构清晰,便于按模块阅读与二次修改。目前已有 136 人下载学习,对希望动手实践二维码工具开发或需要现成参考项目的读者具有直接借鉴价值。
1. 为什么做二维码生成工具,我会先选 QRCoder.dll
做扫码记录、物料追溯、设备巡检这类 C# 桌面程序,二维码生成几乎是躲不开的环节。很多人第一反应是自己拿画布按规格涂格子,认真做几轮就会发现:编码规则、纠错级别、掩码计算、版本选择全是黑匣子,随便一个细节不对,扫码枪就“不认账”。QRCoder.dll 就是 C# 生态里把二维码生成这件事收口了的第三方库——引用一个 dll,几行代码就能生成合规、可扫描的二维码图片。这篇笔记从一个实际开发者视角,把“C# 引用 QRCoder.dll 开发二维码生成工具”讲透:怎么拿到 dll、怎么调参、怎么带 Logo、怎么避开我踩过的那些坑。适合要做桌面工具、报表标签、扫码追溯的从业者,照着写就能落地。
2. 拿到 QRCoder.dll 的三种方式,以及最小可运行案例
2.1 NuGet 安装、命令行引用、手动放 dll 文件,我一般怎么选
在 C# 项目里引入 QRCoder,常见的有三条路:NuGet 包管理器、dotnet命令行、手动拷贝 dll 文件。多数情况下我会优先走 NuGet,因为它会把依赖项一并处理干净,后续升级也省事。只有在内网离线环境,或者项目里明确要求“不允许引第三方包、只能放 dll 文件”时,我才会退回手动引用。
| 方式 | 适用场景 | 注意点 |
|---|---|---|
| NuGet 包管理器(Visual Studio) | 常规开发,能联网 | 自动带入依赖,升级方便 |
dotnet add package QRCoder | 命令行、CI 构建 | 需要项目文件可写 |
| 手动拷贝 dll 文件 | 内网离线、合规限制 | 要自己补依赖,版本容易对不上 |
需要说明的是,QRCoder 的底层绘图依赖 System.Drawing 或 System.Drawing.Common,手动拷贝时只拿主 dll 而漏掉这些基础库,是最常见的启动报错来源。与其从不明网站下载来路不明的 dll,不如先在有网环境装一次 NuGet 包,再从本地缓存里把真正的 dll 取出来,这一步对后文很有用。
2.2 从 NuGet 缓存中找到 QRCoder.dll 并添加到项目
如果你最终交付的形态是一个“工具”,而不是一个依赖包管理器的“工程”,那么把 dll 文件放在程序目录里直接引用,反而更符合实际。我的做法是:先建一个临时项目,执行一次 NuGet 安装,再去用户目录的~/.nuget/packages/下把对应文件拷出来。
# 1. 先建一个临时类库项目(也可以直接用现有项目) dotnet new classlib -o TempProj cd TempProj # 2. 安装 QRCoder,不指定版本时默认拿当前稳定版 dotnet add package QRCoder # 3. 从本地 NuGet 缓存找到 dll 所在目录 # 缓存的典型路径是 ~/.nuget/packages/qrcoder/<版本>/lib/<目标框架>/ ls ~/.nuget/packages/qrcoder/找到QRCoder.dll之后,在你的正式项目里右键“引用 → 添加引用 → 浏览”,选中该 dll 文件,并把引用属性的“复制到输出目录”设为 True。这样发布程序时 dll 会直接出现在exe旁边,另一台机器不需要装 NuGet 也能跑。
提示:如果你是 .NET Framework 项目,缓存路径下的目标框架文件夹里通常有net40、netstandard2.0等子目录,选择与你项目兼容的那一个。宁可多试一个框架版本,也不要拿net6.0的 dll 硬塞给net48项目,否则运行时大概率报“无法加载”或方法找不到。
2.3 最小案例:用 QRCoder.dll 生成第一张二维码 PNG
引用成功后,先写一个最小可运行案例,确认链路通顺。下面的方法接收一段文本和保存路径,生成一张 20 像素每模块的二维码图片:
using QRCoder; using System.Drawing; using System.Drawing.Imaging; static void GenerateQrPng(string content, string savePath) { // 1. 创建生成器实例,所有编码工作都由它完成 using var generator = new QRCodeGenerator(); // 2. 把文本内容编码为二维码数据,这里选 M 级纠错 QRCodeData data = generator.CreateQrCode(content, QRCodeGenerator.ECCLevel.M); // 3. 将二维码数据渲染成 Bitmap,20 表示每个模块占 20 像素 using var qrCode = new QRCode(data); using var bitmap = qrCode.GetGraphic(20); // 4. 保存为 PNG 文件 bitmap.Save(savePath, ImageFormat.Png); } // 调用示例:生成一个记录设备编号的二维码 GenerateQrPng("DEV-2024-0821", @"D:\qr_output\device.png");这段代码看起来短,但它把 QRCoder 的核心流程走完了:生成器负责编码,QRCodeData是中间产物,QRCode.GetGraphic负责把数据画成位图。using很重要——QRCodeData和QRCode内部可能持有非托管资源,不释放会导致频繁生成时内存上涨。ECCLevel.M表示中等纠错,对普通文本场景足够,后面写带 Logo 的二维码时会改成 H。
3. 把二维码的“长相”和“体格”调到项目要求:核心 API 与参数
3.1 QRCodeGenerator 与 ECC 级别:先理解纠错,再动参数
很多新手会跳过这一步,直接调像素大小,结果生成的二维码扫码率忽高忽低。先搞懂ECCLevel才能真正控制质量。QRCoder 提供四个纠错级别:L(约 7%)、M(约 15%)、Q(约 25%)、H(约 30%)。纠错能力越强,二维码能容忍的污损、遮挡越多,但同样内容生成的码会更“密”,模块数更多,点数更多。
using QRCoder; var generator = new QRCodeGenerator(); // 强制按 UTF-8 编码,处理中文内容时必须打开 generator.ForceUTF8 = true; // 用 ECCLevel.H 生成,适合后续要叠加 Logo 的二维码 QRCodeData data = generator.CreateQrCode( "SN-A100-2024-0003", QRCodeGenerator.ECCLevel.H );这里的ForceUTF8是中文场景的救星。默认情况下,部分编码器对非 ASCII 字符会按其他编码规则处理,导致扫码端出现乱码。我一般不管内容里有没有中文,一律先置为true,这个习惯在后面的避坑章节还会展开。Ecc 级别选择上,普通链接用 M,涉及打印、磨损、遮挡场景用 H;内容特别长又希望图案稀疏时,用 L 或 M 换取更低密度,但要接受容错下降。
3.2 GetGraphic 参数:像素、静区、前景色和背景色的取舍
二维码在图片上的呈现由GetGraphic控制。这个方法的参数不多,但每个都直接影响“能不能扫出来”。核心参数有pixelsPerModule(每模块像素数)、darkColor(前景色)、lightColor(背景色)、drawQuietZones(是否绘制静区)以及带图标时的icon、iconSizePercent、iconBorderWidth。
| 参数 | 作用 | 我的建议 |
|---|---|---|
pixelsPerModule | 每个码模块的像素边长 | 屏幕显示至少 5,打印输出按物理尺寸另算 |
darkColor | 码点颜色 | 优先纯黑,避免深灰/深蓝 |
lightColor | 背景颜色 | 优先纯白,确保对比度 |
drawQuietZones | 边缘静区 | 保持 true,静区宽度是 4 个模块 |
using var qrCode = new QRCode(data); // 生成 15 像素/模块、黑点白底、带静区的二维码 using var bitmap = qrCode.GetGraphic( pixelsPerModule: 15, darkColor: Color.Black, lightColor: Color.White, drawQuietZones: true );drawQuietZones这个参数很容易被忽略。它的作用是围绕二维码保留一圈空白边,这个空白边对扫码定位极其重要,打印时一旦被裁掉或印成深色,识别率会大幅下降。我的习惯是永远保持true,宁可图片稍微大一点,不要省这点空间。如果你以后要在二维码图片外加一个圆角、底色块,也请确保最终图像上这个静区仍然是干净的浅色。
3.3 输出格式不只有 PNG:Base64、SVG、ASCII 按场景选
QRCoder 家族里不止QRCode一个渲染器,不同业务要求下,同样一份QRCodeData可以输出多种格式。我最常用的有三种:PngByteQRCode输出字节数组,适合接口返回 Base64;SvgQRCode输出矢量文本,适合做打印标签和放大不失真;AsciiQRCode适合控制台程序或日志打点。
using QRCoder; var generator = new QRCodeGenerator(); generator.ForceUTF8 = true; QRCodeData data = generator.CreateQrCode("ORDER-2024-0815-001", QRCodeGenerator.ECCLevel.M); // 1. PngByteQRCode:不依赖 System.Drawing,Linux 也能跑 using var pngEncoder = new PngByteQRCode(data); byte[] pngBytes = pngEncoder.GetGraphic(20); string base64 = Convert.ToBase64String(pngBytes); // 2. SvgQRCode:输出矢量 XML 字符串,适合打印 var svgEncoder = new SvgQRCode(data); string svgContent = svgEncoder.GetGraphic(20);PngByteQRCode和SvgQRCode是比QRCode更“轻”的选择,因为它们不强制走 GDI+,这在后面的跨平台避坑里非常关键。把 Base64 字符串通过 WebApi 返回给前端时,前端可以直接用data:image/png;base64,...渲染;SVG 则直接嵌入报表 PDF 或 HTML,放大多少都不会糊。还有个冷门用法:用AsciiQRCode在日志里输出一个“可扫码”的文本二维码,排查问题时用手机拍一下就能跳到对应链接,实测很有用。
4. 带 Logo、带边框、改颜色的二维码:从默认样式到业务样式
4.1 用 GetGraphic 的 icon 参数加 Logo:容错必须提升到 H
业务需求十有八九要往二维码中心塞一个 Logo:公司图标、产品位图、产品外观图。这个操作表面上只是往图片上叠一张图,但实际是在跟纠错机制博弈。Logo 一旦盖住某个码点,该区域的真实信息就丢了,只能靠纠错算法“猜”回来。所以带图标时我几乎没有犹豫,直接把ECCLevel拉到 H,再把图标尺寸压到 15% 以内。
using var generator = new QRCodeGenerator(); QRCodeData data = generator.CreateQrCode( "https://example.com/asset/1001", QRCodeGenerator.ECCLevel.H ); using var qrCode = new QRCode(data); using var logo = new Bitmap(@"D:\assets\company_logo.png"); // iconSizePercent: 12 表示图标宽度约占整个二维码宽度的 12% // iconBorderWidth: 4 表示图标外再套一圈白边,避免和码点粘连 using var finalBmp = qrCode.GetGraphic( pixelsPerModule: 20, darkColor: Color.Black, lightColor: Color.White, icon: logo, iconSizePercent: 12, iconBorderWidth: 4 ); finalBmp.Save(@"D:\qr_output\with_logo.png", ImageFormat.Png);iconSizePercent和iconBorderWidth这两个参数是实际效果的关键。图标不是“放在正中间就行”,它周围那圈白边如果太窄,Logo 的边缘会直接压住周围码点,导致解码器读不到完整的校正图形。我的经验是:图标宽度占比超过 20%,扫码率会肉眼可见地下降;超过 30%,一部分手机扫码 App 已经无能为力。白边宽度和图标大小要一起动,图标越大,白边也需要适当加宽,但代价是可用数据区更少。
4.2 控制静区、背景色,以及颜色对比度的边界
有些项目希望二维码跟整体 UI 风格一致,比如深色底、浅色点、圆角背景。改颜色不是不行,但要守住一条底线:码点与背景的亮度差要足够大。扫码器的本质是识别明暗变化,纯黑配纯白最稳;如果你非要深蓝配浅黄,建议生成后拿扫码枪做一轮实测,不要只在屏幕上盲目检查。
using var qrCode = new QRCode(data); using var bitmap = qrCode.GetGraphic( pixelsPerModule: 18, darkColor: Color.FromArgb(255, 30, 50, 80), // 深蓝灰 lightColor: Color.FromArgb(255, 250, 245, 230), // 米白 drawQuietZones: true );Color.FromArgb的四个参数分别是透明度、R、G、B。第一个参数建议固定为255,也就是完全不透明。这里有个隐蔽坑:某些场景下把背景色设成透明(alpha=0),二维码图片在深色界面上看起来正常,但打印或贴到深色实物上时,原本该是白色的静区变成了透明,扫描时背景色混进来,对比度一塌糊涂。我现在的原则是:凡是最终要物理打印、扫码枪读取的码,颜色一律不透明。
4.3 把二维码工具接入 WinForm 程序:显示、保存、批量生成
工具落到界面上,通常就是三个动作:生成、预览、保存/打印。WinForm 里用PictureBox.Image直接显示位图,同时保留一个“保存 PNG”按钮把内存中的 Bitmap 写盘。常见的做法是写一个独立的QrService类,把生成逻辑收口,界面层只调方法。
public class QrService { public Bitmap GenerateWithLogo(string content, string logoPath, int moduleSize = 20) { var generator = new QRCodeGenerator { ForceUTF8 = true }; QRCodeData data = generator.CreateQrCode(content, QRCodeGenerator.ECCLevel.H); using var qrCode = new QRCode(data); using var logo = new Bitmap(logoPath); // 返回新的 Bitmap,由调用方决定何时 Dispose return qrCode.GetGraphic(moduleSize, Color.Black, Color.White, logo, 12, 4, true); } } // 界面里调用后显示到控件 var service = new QrService(); var qrImage = service.GenerateWithLogo(txtContent.Text, txtLogoPath.Text); pictureBox1.Image?.Dispose(); // 释放上一张图 pictureBox1.Image = qrImage;因为GetGraphic返回的是新位图,使用完务必手动Dispose,尤其在批量生成时,不释放会很快把 GDI+ 句柄耗尽,程序表现为“越来越卡”甚至报“内存不足”。我通常把pictureBox1.Image?.Dispose()写在赋值前,让它始终只持有当前一张图。
5. 避坑:QRCoder.dll 做二维码工具常见的 5 个翻车现场
5.1 中文内容扫出来乱码:编码问题最隐蔽
现象:二维码内容里含中文,用手机扫出来是乱码,比如“设备编号”变成“设å¤ç¼å·”,但有些扫码 App 又正常。原因:生成二维码时字符串被按某种非 UTF-8 编码转成了字节流,扫码端默认按 UTF-8 解码,两边对不上。解决:在QRCodeGenerator上显式设置ForceUTF8 = true,同时保证源码文件本身以 UTF-8 保存,避免编译器把字符串常量按 ANSI 处理。
5.2 带 Logo 的二维码扫码率暴跌:图标尺寸是隐性杀手
现象:同一个内容,不加 Logo 一记就扫;加了 Logo 之后,部分手机能扫、部分扫码枪扫不出,尤其 Logo 尺寸大时更明显。原因:图标覆盖了中心区域的码点和校正图形,纠错能力不足以还原。解决:把ECCLevel提到 H,图标宽度控制在二维码总宽度的 12% 到 15% 之间,并在图标外留 4 到 8 像素的白色内边距。改完之后必须做真机扫码测试,不能只看“生成出来了”。
5.3 打印出来的二维码扫不了,但屏幕显示正常:物理尺寸问题
现象:屏幕上用手机一扫就过,同一张图打印到小标签上,扫码枪死活读不出来。原因:pixelsPerModule控制的是图片上的像素数,但打印时标签尺寸小,每个模块在纸面上被缩得远小于扫码设备能识别的物理宽度。解决:别用像素思维,改用物理尺寸。打印前确认模块在纸面上的边长不小于 0.33 毫米:如果标签打印宽度是 25 毫米,而二维码有 25 个模块,那每模块只有 1 毫米,看似够;但如果pixelsPerModule=10又缩印成 12 毫米宽,模块就直接跳到极限以下了。最稳妥的做法是在打印驱动里把图片按 1:1 输出,不要做二次缩放。
5.4 设置透明背景后扫码不稳:对比度被“吃掉”了
现象:生成的二维码嵌在某个系统的深色页面上时,手机扫不出来;导出图片单独看,又感觉没问题。原因:背景透明,静区变成了“透出底层皮肤的颜色”,浅色 Code 和深色背景的反差不足,扫码器无法定位。解决:生成时指定lightColor: Color.White且 alpha 为 255,杜绝透明背景下发给前端或打印。如果 UI 非要浅色背景,那就把二维码放在一个白色圆角卡片里,而不是让二维码本身透明。
5.5 跨平台部署时 System.Drawing 抛异常:QRCoder 在 Linux 上翻车
现象:程序在 Windows 上跑得好好的,部署到 Linux 服务器后,一调用QRCode.GetGraphic就抛异常,异常信息指向System.Drawing。原因:QRCode渲染器依赖 GDI+,而 .NET 环境部署到 Linux 上时没有图形子系统的默认支持,导致位图操作失败。解决:改用PngByteQRCode或SvgQRCode,这两个渲染器不依赖 System.Drawing,输出字节数组或字符串后,再交给上层保存或传输。这条是我跨平台部署时血泪换来的结论,所有写在服务端的二维码生成,我默认都走PngByteQRCode,稳妥。
6. 验证与落地技巧:用 ZXing 反向解码做自动化回归
6.1 ZXing 自动验证码,比肉眼靠谱得多
二维码工具做到后期,最大的问题是“改了一个参数,其他码还能不能扫”。肉眼看不出来,所以我在测试项目里引入了 ZXing.Net,生成后立刻反向解码比对。这里写一个最小验证方法:
using ZXing; static bool VerifyQrCode(string imagePath, string expectedText) { // ZXing 的 BarcodeReaderGeneric 可以直接读取位图 var reader = new BarcodeReaderGeneric(); using var bitmap = new Bitmap(imagePath); var result = reader.Decode(bitmap); return result != null && result.Text == expectedText; }我的使用习惯是:每次改完代码,先生成一批包含中文、长链接、纯数字的二维码,全部跑一遍VerifyQrCode,只要有一条对不上就停住排查。注意 ZXing 解码时对图像方向敏感,如果expectedText匹配失败,先确认解码结果是不是空,而不是急着改生成参数。自动化校验通过后,再拿真实扫码枪抽测 5 张左右,这一步我从不跳过。
6.2 交付前的最终检查清单
做完一批码,我会按下面这个顺序过一遍:内容编码是否为 UTF-8;ECC 级别是否符合使用场景;模块物理尺寸在最终介质上是否足够;静区是否保留并且不透明;Logo 是否压住校正图形;有无颜色对比度不足的问题。这几项都通过,码就可以进测试环境了。
最后的习惯分享:有次给产线交付标签工具,临时把 Logo 放大了一倍以配合新品牌视觉,当天界面看起来没毛病,第二天产线扫码枪时好时坏,查到最后就是图标盖住了校正图形,而开发机上用手机扫又碰巧能过。那之后我给自己定了个规矩:任何带 Logo、改配色、调尺寸的变更,都必须同步跑一遍机器解码和真机扫码双重验证。现在每次改完参数,跑一遍 ZXing 回归,再打印一张到纸上实扫,确认无误才敢发出去。这个习惯帮我避开了好几次晚些时候才暴露的返工,希望帮到你。
本文还有配套的精品资源,点击获取