☰
CAD .NET二次开发:DBText添加单行文字完整流程与踩坑指南
2026/10/20 9:26:19 网站建设 项目流程

做 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. 添加第一行文字的标准流程:事务、块表和 AppendEntity

2.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。
  • 路线 B:SHX 字体配合大字体(BigFont),典型组合是txt.shx+hztxt.shx。

路线 A 胜在简单,中文字形完整,换到别的机器只要系统装了对应字体就能正常显示;路线 B 是很多单位早年图纸的标准做法,适合和存量图形保持一致。

下面这段代码会在当前图形里查找或创建名为CUSTOM_TXT的文字样式,并返回它的 ObjectId:

private 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)\U+00B045°
直径(Unicode)\U+2205⌀100

在 C# 的普通字符串里写\\U+00B0时要注意:反斜杠本身要转义,所以是双反斜杠;如果你用 @ 字符串,直接写@"\U+00B0"即可。千万别直接写"\U+00B0",C# 会试图把它解析成 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 高度默认是 0,AutoCAD 显示这种对象时经常表现成极小或不可见。显式设一个大于 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 这几个属性按固定顺序写完。顺序固定之后,出问题时的排查面非常小——先看样式,再看图层,再看位置参数,基本两三分钟就能定位。这个习惯帮我撑过很多次“帮同事调代码”的场合,也希望能帮你少踩几个坑。

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

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

立即咨询