CAD .NET二次开发:DBText添加单行文字完整流程与踩坑指南 做 CAD 二次开发这些年被问得最多的功能之一就是“往图里加文字”。圆心的编号、设备的标签、坐标的标注、图号的说明——凡是需要往图纸上写点什么的工具几乎都绕不开一个叫 DBText 的对象。说它简单是因为从创建到入库核心代码不到十行说它坑多是因为不少人在它身上翻过车中文变成问号、文字跑回原点、旋转角度直接转飞、换台电脑字体又变了。这篇文章就围绕“cad net 添加文字 DBText”这条主线把往模型空间写单行文字的完整流程、关键参数和踩坑记录整理出来给正在做类似功能的朋友一份可以照着抄的参考。1. 先搞明白一件事DBText 和 MText 到底该选谁1.1 单行文字在 AutoCAD 数据库里的真实位置很多刚接触 AutoCAD .NET 的朋友第一次写添加文字时习惯性搜索“添加文字”结果出来一堆 MText多行文字的示例照着改又发现属性对不上。这是因为他们没搞清 DBText 和 MText 在数据库里的不同定位。先说 DBText。DBText 对应 AutoCAD 交互界面里的“单行文字”命令行 TEXT它是 DatabaseServices 命名空间下的一个实体类。在 AutoCAD 的数据库模型里所有图形实体都不是随便挂在图纸上的而是挂在“块表记录”BlockTableRecord下面。模型空间、图纸空间、块编辑器里的内容本质上都是一个 BlockTableRecord。DBText 与直线、圆、多段线一样都是这个容器里的普通实体。这里我把对象层级写出来帮你建立直觉Database数据库 └── BlockTable块表 └── BlockTableRecord块表记录 └── DBText / Line / Circle实体这段层级关系后文所有代码都会用到要往模型空间加文字必须先拿到数据库 → 块表 → 模型空间块表记录 → 才能往里面 AppendEntity。1.2 什么时候闭眼用 DBText什么时候该换 MText我见过不少方案明明是用 DBText 更合适的场景非要上 MText结果代码里塞了一堆格式化专用属性改起来痛苦也有反过来一段说明文字硬拆成好几个 DBText显示效果七扭八歪。所以先把这个选择题做掉。对比项DBText单行文字MText多行文字换行不支持支持多行段落命令来源早期 AutoCAD 的 TEXT 命令后来扩展的 MTEXT 命令定位方式Position / AlignmentPointLocation格式控制依赖文字样式整体统一支持片段级格式、缩进、列表性能轻量几万条轻松处理单个对象可以很大典型场景编号标注、坐标值、图号技术要求、说明段落一句话如果你的文字就一句话没有复杂段落优先 DBText。特别是批量生成场景比如给一批图元写序号DBText 在性能和代码复杂度上都有明显优势。只有当你需要多行、不同字体混排、自动换行这些“排版”能力时再考虑 MText。选择这件事我在项目里总结的规则是先问自己“这段文字能不能一句话说完”能DBText不能MText能但字数会很多且要自动换行也选 MText。优先级很清楚。2. 添加第一行文字的标准流程事务、块表和 AppendEntity2.1 最小可运行代码逐行解读直接给代码。下面是一个可以注册成 AutoCAD 命令的最小实现作用是在当前图纸的模型空间 (100,100,0) 处写一行高 2.5 的文字using Autodesk.AutoCAD.ApplicationServices; using Autodesk.AutoCAD.DatabaseServices; using Autodesk.AutoCAD.Geometry; using Autodesk.AutoCAD.Runtime; namespace CadTextDemo { public class TextCommands { [CommandMethod(ADD_TEXT_DEMO)] public void AddTextDemo() { Document doc Application.DocumentManager.MdiActiveDocument; Database db doc.Database; using (Transaction tr db.TransactionManager.StartTransaction()) { BlockTable bt (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead); BlockTableRecord modelSpace (BlockTableRecord)tr.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForWrite); DBText text new DBText(); text.TextString Hello, CAD; text.Height 2.5; text.Position new Point3d(100, 100, 0); modelSpace.AppendEntity(text); tr.AddNewlyCreatedDBObject(text, true); tr.Commit(); } } } }一步步看Application.DocumentManager.MdiActiveDocument获取当前活动文档。命令运行时CAD 会自动把我们带入当前文档上下文这个方法基本不会失效。Database db doc.Database拿到当前文档的数据库。db.TransactionManager.StartTransaction()开启一个事务。AutoCAD .NET 的几乎所有“写操作”都建议放在事务里它保证后续一系列操作要么全部成功写入要么全部回滚不会留下半截数据。BlockTable bt (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead)打开块表。注意这里用的是 ForRead块表本身我们只读不加修改。(BlockTableRecord)tr.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForWrite)打开模型空间对应的块表记录。BlockTableRecord.ModelSpace是一个常量 ObjectId指向模型空间。因为我们接下来要往这个容器里追加实体所以必须 ForWrite 打开。new DBText()创建文字对象。此时它只是一块内存数据还没进数据库。赋值 TextString / Height / Position。高度单位与当前图形单位一致要提前想好图纸比例。modelSpace.AppendEntity(text)把实体加入块表记录。Append 之后它才拥有 ObjectId但此刻事务还没有提交磁盘上的图形文件并没有变化。tr.AddNewlyCreatedDBObject(text, true)告诉事务“有一个新生成的对象需要你来跟踪”。第二个参数true表示该对象是否采用写模式管理。这一步和 AppendEntity 必须成对出现少一个都可能出现奇怪状态。tr.Commit()提交事务所有修改才真正生效。2.2 事务为什么要包一层回滚、锁与资源释放很多刚入门的开发者不理解为什么不能像普通文件读写那样直接new DBText()然后Save完事。核心原因有两个原子性和资源锁。原子性很好理解。如果我的操作是“新建一个文字样式 → 新建一个图层 → 新建一段文字”只要中间某一步抛异常事务能保证前面的动作全部回滚。图形文件不会出现“图层建了但文字没写进去”这种半截状态。资源锁则更现实。事务内部打开的对象在事务提交或回滚后会自动释放。尤其是 ForWrite 打开的对象如果忘记处理会一直处于被写锁占用状态后面想再打开同一个对象就会报错。举个例子同一个事务里先OpenMode.ForRead打开某个块表记录后面又拿同一个 ObjectId 请求OpenMode.ForWrite系统大概率会抛eAlreadyInUse。这个错我见到的频率极高。所以代码里一定要用using (Transaction tr ...)包住事务。using 会在代码块结束时自动调用 Dispose及时清理对象锁。如果连 Commit 都忘了事务回滚后实体根本没进图下次运行自然找不到。这三个动作using 包裹、完整操作、Commit 收尾能消除一大半事务相关的诡异问题。3. 文字样式才是中文乱码的根源从 Standard 到自定义样式3.1 为什么默认样式写中文全是问号很多朋友第一次用上面的代码把TextString设为“你好”添加到图形一看变成了“”。第一反应是代码编码问题实际上绝大多数时候是**文字样式TextStyle**的问题。DBText 的文字显示依赖它绑定的 TextStyleTableRecord文字样式表记录。AutoCAD 每个图形都有一个文字样式表TextStyleTable里面默认有一条 Standard 样式。这条 Standard 样式一般绑定的是txt.shx这个矢量字体文件。这个字体设计出来是为了快速显示英文字符和简单符号其中根本不包含中文字形。用 DBText 写入中文时样式表里找不到对应字形显示出来自然全是问号。顺带说一句代码文件本身的编码也要保证是 UTF-8 或 GBK否则哪怕样式正确字符串进到 AutoCAD 就已经乱成一锅粥了。这两个问题是不同层面的不要混为一谈。3.2 创建支持中文的文字样式TrueType 与大字体两种路线要显示中文给 DBText 绑定一个支持中文的文字样式即可。常用的路线有两条路线 A直接用 TrueType 字体比如黑体、宋体、仿宋文件后缀是.ttf或.ttc。路线 BSHX 字体配合大字体BigFont典型组合是txt.shxhztxt.shx。路线 A 胜在简单中文字形完整换到别的机器只要系统装了对应字体就能正常显示路线 B 是很多单位早年图纸的标准做法适合和存量图形保持一致。下面这段代码会在当前图形里查找或创建名为CUSTOM_TXT的文字样式并返回它的 ObjectIdprivate static ObjectId EnsureChineseTextStyle(Database db, Transaction tr) { TextStyleTable tst (TextStyleTable)tr.GetObject(db.TextStyleTableId, OpenMode.ForRead); const string styleName CUSTOM_TXT; if (tst.Has(styleName)) { return tst[styleName]; } tst.UpgradeOpen(); TextStyleTableRecord record new TextStyleTableRecord(); record.Name styleName; // 方案一TrueType 字体比如黑体 record.FileName simhei.ttf; // 方案二SHX 大字体与方案一互斥二选一 // record.FileName txt.shx; // record.BigFontFileName hztxt.shx; ObjectId id tst.Add(record); tr.AddNewlyCreatedDBObject(record, true); return id; }然后在创建 DBText 时把它绑定上DBText text new DBText(); text.TextStyleId EnsureChineseTextStyle(db, tr); text.TextString 中文标注; text.Height 3.0; text.Position new Point3d(10, 10, 0);这里还有几个细节值得提文字样式名称建议固定且唯一。重复创建同名样式会导致表里出现CUSTOM_TXT1之类的派生名给后面管理带来麻烦。TextStyleTableRecord.FileName simhei.ttf和record.Font new FontDescriptor(黑体, false, false, 134, 0)可以互相替代前者直接指定字体文件后者指定字体族名称和字重。但注意TrueType 字体样式不要再设置 BigFontFileName二者不可共存。如果你创建 DBText 时不手动指定 TextStyleId它默认用图形里的 Standard 样式。这就是一票乱码的根源。3.3 特殊符号在 DBText 里的写法与转义细节CAD 文字里经常要写度数、直径、正负号。DBText 支持 AutoCAD 的传统控制码和 Unicode 转义两种方式整理一张表你要的符号在 TextString 里输入显示效果度数%%d45°直径%%c⌀100正负%%p±0.01度数Unicode\U00B045°直径Unicode\U2205⌀100在 C# 的普通字符串里写\\U00B0时要注意反斜杠本身要转义所以是双反斜杠如果你用 字符串直接写\U00B0即可。千万别直接写\U00B0C# 会试图把它解析成 Unicode 转义序列直接编译报错。另外一个反直觉的点DBText 虽然叫“文字”但它不支持换行。我以前试过往 TextString 里塞\n结果没有任何换行效果只是被当成普通空白符处理显示效果像两个词之间多了个空格。需要显示多行时要么拆成多个 DBText 自己排位置要么直接改用 MText。4. 对齐、插入点和旋转角位置运算的三角关系4.1 Position 与 AlignmentPoint 各自管哪一段往图里加文字位置是最容易出问题的地方。原因在于 DBText 有两个和位置相关的属性Position 和 AlignmentPoint。默认情况下DBText 的对齐方式是 Left左下角基线对齐此时 AutoCAD 使用 Position 作为文字基线的起点AlignmentPoint 基本不参与。可一旦你改了对齐方式比如让文字“居中”或“右对齐”AutoCAD 就会改读 AlignmentPoint而 Position 会被当作基线左侧端点或干脆被忽略。很多代码翻车的场景是DBText text new DBText(); text.TextString 居中文字; text.Height 5.0; text.Alignment TextAlignment.Center; // 改成居中对齐 text.Position new Point3d(50, 50, 0); // 只设置了 Position结果文字没有出现在 (50,50,0)反而跑到原点附近或位置错乱。原因就是 AlignmentPoint 还是默认值 (0,0,0)。正确写法是DBText text new DBText(); text.TextString 居中文字; text.Height 5.0; text.Alignment TextAlignment.MiddleCenter; text.AlignmentPoint new Point3d(50, 50, 0); // 对齐锚点才是关键这里的MiddleCenter表示水平居中加垂直居中适合做图框中心标题、圆心编号这类“正中定位”的需求。如果只是水平居中用TextAlignment.Center如果要顶部居中、底部左对齐对应枚举里的 TopCenter、BottomLeft方向很直白。4.2 弧度与角度的转换躲掉旋转 90 度却转了半个圆的坑DBText 的Rotation属性是弧度制不是角度制。很多人第一次设置text.Rotation 90期待文字竖排结果文字要么旋转到基本看不见要么和预想差了十万八千里。弧度和角度的换算是double degrees 90; double radians degrees * Math.PI / 180.0; text.Rotation radians;顺便提醒DBText 还有一个从文字样式继承来的倾斜角Oblique同样是弧度制。两处都要转换别只转了 Rotation 就把 Oblique 忘了。旋转时注意 AutoCAD 的坐标系规则0 弧度表示水平向右正方向是逆时针。如果你需要文字沿一条已经绘制好的线段方向排列可以用线段的端点向量来算弧度Math.Atan2(y, x)就能拿到。4.3 把文字放到九宫格任意位置的通用写法项目中经常遇到“我要把这个文字放在块的中心点正上方 2 个单位”这类需求。与其每次手工演算我习惯封装一个辅助方法private static DBText CreateAlignedText( string content, Point3d anchor, double height, TextAlignment alignment, double rotationDegrees 0) { DBText text new DBText(); text.TextString content; text.Height height; text.Rotation rotationDegrees * Math.PI / 180.0; text.Alignment alignment; text.AlignmentPoint anchor; return text; }调用时传入对准点和对齐方式即可。这个辅助方法我几乎每个项目都会带上它把最容易错的“对齐点到底设在哪个坐标”收敛到了一个地方后续维护成本很低。5. 工程化补充批量性能、图层设置与坐标系转换5.1 单事务写十万条文字的性能实测如果你要批量生成大量 DBText性能问题必须提前在意。我做过一个模拟项目需要给几万个测量点写编号。最初图省事每个点一个独立事务最后测试下来稍微复杂一点的图形要跑一分多钟。后来改成在同一个事务里循环追加同样是几万条基本一秒内完成。原因不复杂事务的提交要经过锁定、序列化、写盘等多个环节这个固定开销不小100 个事务和 1 个大事务固定开销差了 100 倍。写大批量实体的通用建议是尽量在同一个事务里完成一轮批量化操作不要频繁开事务、提交事务。如果数量实在太大几十万、百万级别再考虑每 1 万条左右分批提交避免单个事务占用内存过高。循环里不要反复tr.GetObject(...)同一个块表记录。事务开始后打开一次复用那个对象即可。5.2 新建图层并给文字指定图层的标准代码工程上文字一般不会直接扔在 0 层而是放在“标注层”“文字层”方便后续按图层开关、冻结、打印控制。DBText 的Layer属性接收图层名前提是这个图层已经存在。创建图层和创建文字样式非常像都需要操作对应的符号表private static void EnsureLayer(Database db, Transaction tr, string layerName) { LayerTable lt (LayerTable)tr.GetObject(db.LayerTableId, OpenMode.ForRead); if (lt.Has(layerName)) return; lt.UpgradeOpen(); LayerTableRecord ltr new LayerTableRecord(); ltr.Name layerName; lt.Add(ltr); tr.AddNewlyCreatedDBObject(ltr, true); }然后设置EnsureLayer(db, tr, 标注层); text.Layer 标注层;如果预设的图层没有这段代码会自动建一个。工程里我一般还会顺手给图层设置颜色和线型核心套路一样拿 LayerTable → 看有没有同名图层 → 没有就 UpgradeOpen → 新建 LayerTableRecord → Add AddNewlyCreatedDBObject。5.3 用户坐标系下的坐标转换否则文字跑到奇怪位置DBText 的 Position、AlignmentPoint 都是按世界坐标系WCS解释的。如果你的工具是在默认坐标系下工作完全不用管但用户一旦把 UCS 旋转或平移之后直接把“当前坐标系下看似正确”的坐标赋给文字文字就会跑到一个看起来很奇怪的位置。手上有 UCS 坐标时转一次即可Editor ed doc.Editor; Matrix3d ucsToWcs ed.CurrentUserCoordinateSystem; Point3d ucsPoint new Point3d(100, 100, 0); Point3d wcsPoint ucsPoint.TransformBy(ucsToWcs); DBText text new DBText(); text.TextString 转换后的坐标; text.Height 3.0; text.Position wcsPoint;补充一点通过Editor.GetPoint让用户点选获取插入点时返回值在文档坐标系里已经处理过了直接给 Position 用没有问题。真正需要手动转换的场景是你代码里拿着一个“已知是 UCS 坐标”的数值比如从外部文件读进来的点或者从另一个插件接口里拿到的点。6. 踩坑实录写进去了不显示、样式丢失、版本兼容6.1 画面上找不到文字的四个排查方向“代码没报错Commit 也执行了但屏幕上就是看不到文字”是我最常收到的求助。这个问题一般按下面顺序排查现象可能原因处理办法找不到文字所在图层被冻结/关闭/锁定检查图层的 IsOff / IsFrozen / IsLocked放大后是黑点Height 太小或 Height 为 0显式给一个合理高度视图拉太远找不到插入点坐标在视图之外用 ZoomExtents 或直接按坐标找被其他实体盖住绘制顺序靠后调整 DrawOrderTable或把文字换到更上层其中 Height 为 0 特别值得说。有些代码里为了“省事”不设置 Height认为样式里有默认高度但在 .NET API 里新建的 DBText 高度默认是 0AutoCAD 显示这种对象时经常表现成极小或不可见。显式设一个大于 0 的值是零成本规避问题的方法。如果以上都查过还是看不见在命令末尾加一句doc.Editor.Regen()强制重生成视图。我在部分版本上遇到过视图缓存没刷新导致的“假性不存在”这句能解决大部分焦虑。6.2 换机器后字体被替换的真相DBText 的显示依赖文字样式而文字样式又依赖字体文件。如果图纸被拿到另一台没有对应字体的机器上打开AutoCAD 会按替代字体显示。轻则字形变化重则中文直接显示错误。这不是代码 bug但确实是开发 DBText 功能时要提前考虑的环境问题。规避手段有两类一是尽量用常见字体比如黑体、宋体这类系统自带的 TrueType绝大多数环境都支持二是如果必须用 SHX 大字体最好把字体文件一并部署到 CAD 的 Support 搜索路径里。开发库和部署包分离是我在这个问题上吃过亏后养成的习惯。6.3 事务里常见的 eAlreadyInUse 等异常源头最后一个坑区专门说事务相关的异常。常见的有下面几类同一个对象重复打开。比如先用OpenMode.ForRead打开一个块表记录后面又要修改它再拿同一个 ObjectId 请求OpenMode.ForWrite系统会抛eAlreadyInUse。解决方式从一开始就按 ForWrite 打开或者先关闭旧的句柄再重新打开。循环里反复 GetObject 同一个对象打开的 DBObject 没有及时释放。虽然事务结束时会统一回收但循环量大了之后对象锁的争用会显著拖慢性能极端情况还会触发锁冲突。忘了 Commit。代码跑完不报错但第二次执行时发现上一次的内容不在图里。把事务包在 using 里把 Commit 写在最后这两个动作几乎可以消除 90% 的事务困惑。版本兼容方面AutoCAD .NET API 在近几代版本里接口变化不大我写的这些代码在 2014 到 2024 的范围内基本可以直接编译使用。真正要注意的是引用哪个版本的 ObjectARX DLL以及目标机器上的 AutoCAD 版本和你引用的一致或兼容。这一点在交付给客户前最好用对方具体版本跑一遍省得到现场再出幺蛾子。最后分享一个我个人的习惯所有新建的 DBText我都会在 Append 之前把 TextStyleId、Layer、Height、AlignmentPoint 这几个属性按固定顺序写完。顺序固定之后出问题时的排查面非常小——先看样式再看图层再看位置参数基本两三分钟就能定位。这个习惯帮我撑过很多次“帮同事调代码”的场合也希望能帮你少踩几个坑。