惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

量子位
云风的 BLOG
云风的 BLOG
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
The Hacker News
The Hacker News
Martin Fowler
Martin Fowler
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
U
Unit 42
F
Full Disclosure
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Recorded Future
Recorded Future
Security Archives - TechRepublic
Security Archives - TechRepublic
阮一峰的网络日志
阮一峰的网络日志
T
Threatpost
P
Privacy International News Feed
GbyAI
GbyAI
Stack Overflow Blog
Stack Overflow Blog
MongoDB | Blog
MongoDB | Blog
I
Intezer
Recent Announcements
Recent Announcements
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
P
Privacy & Cybersecurity Law Blog
A
Arctic Wolf
博客园 - 聂微东
博客园 - 叶小钗
Cisco Talos Blog
Cisco Talos Blog
H
Help Net Security
S
Schneier on Security
Y
Y Combinator Blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
T
The Exploit Database - CXSecurity.com
T
Tor Project blog
月光博客
月光博客
NISL@THU
NISL@THU
A
About on SuperTechFans
Spread Privacy
Spread Privacy
Blog — PlanetScale
Blog — PlanetScale
D
DataBreaches.Net
雷峰网
雷峰网
C
CXSECURITY Database RSS Feed - CXSecurity.com
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
博客园 - 【当耐特】
G
Google Developers Blog
W
WeLiveSecurity
P
Palo Alto Networks Blog
The Last Watchdog
The Last Watchdog
K
Kaspersky official blog
博客园 - 司徒正美
L
LINUX DO - 热门话题
小众软件
小众软件

博客园 - 我才是银古

第16章:常见问题、排错与最佳实践 第15章:扩展生态、MCAD 与外部集成 第12章:实战案例:机械结构与 3D 打印零件 第14章:构建、测试、调试与贡献流程 第13章:OpenSCAD 源码架构与核心执行流程 第11章:预览、渲染、网格精度与性能优化 第09章:列表推导、递归与算法建模 第08章:参数化零件库与复用设计 第10章:导入导出、命令行与自动化 第06章:CSG 布尔建模方法 第07章:二维图形、拉伸、旋转与投影 第05章:基础几何、坐标系与变换 第04章:参数、变量、函数、模块与作用域 OpenSCAD 教程目录 第03章:OpenSCAD 语言基础 第02章:安装、环境配置与开发工作流 第01章:OpenSCAD 项目全景与学习路线 第02章:源码获取、编译与开发环境配置 第01章:OCCT项目全景与学习路线 第18章:二次开发实战与综合案例 第18章:综合实战案例 第17章:数据交换与协同 第16章:源码架构与二次开发 第15章:插件与自定义工作台开发 第14章:Python脚本宏与自动化 第13章:FEM仿真分析 第12章:CAM数控加工 第11章:SurfaceMesh与逆向工程 第10章:Draft二维绘图与BIM建筑 第09章:工程图TechDraw 第07章:参数化表达式与Spreadsheet 第08章:装配设计Assembly 第06章:Part工作台与几何内核 第05章:PartDesign实体特征建模 第04章:草图Sketcher约束建模 第02章:安装版本与工作环境配置 第03章:界面工作台与基础操作 第01章:项目全景与学习路线 第十二章:插件开发、研究功能与最佳实践 第十章:定时任务与自动化(Cron) 第七章:技能、记忆与自学习闭环 第八章:MCP 集成与上下文文件 第六章:工具系统与终端后端 第五章:模型供应商与配置体系 Hermes Agent 教程目录 第十一章:语音、视觉、浏览器与子代理协作 第四章:CLI/TUI 与会话管理 第十二章:学习路线、实战方案与最佳实践 第十一章:源码结构、开发调试与插件开发 第十章:自动化、远程访问、日志与排障 第九章:Control UI、节点、Canvas 与语音能力 第七章:工具、技能、插件与能力扩展 第八章:安全模型、访问控制与沙箱实践 第六章:Agent 工作区、会话与多智能体路由 第五章:多通道消息接入与聊天平台配置 第四章:配置体系、模型接入与认证管理 第三章:Gateway 架构、协议与运行机制 第二章:安装、环境准备与快速上手 第一章:OpenClaw 项目概览与核心定位 oh-my-openagent 教程目录 09-命令模型回退与配置参考 10-实战案例最佳实践与故障排除 05-工作模式-Ultrawork-Prometheus-Atlas 08-Hooks与MCP系统 06-Category与Skill系统 07-核心工具链 04-智能体全景详解 03-安装与环境配置 02-整体架构与多模型编排机制 01-项目简介与核心理念 01-项目概览与学习路线 02-安装部署与工具适配 03-Skill机制与using-superpowers 05-TDD系统化调试与完成前验证 04-需求澄清方案设计与计划编写 07-并行智能体子智能体与Git-Worktree 第六章:代码审查、反馈处理与分支收尾 08-中国特色Skills与本土团队落地 09-MCP构建工作流执行与自定义Skill 第23章:FreeCAD-Python-API Clipper2 C# 源码解读教程 第19章:PolyTree 多边形树结构 第20章:实际应用与最佳实践 第18章:Minkowski 和与差 第17章:RectClip 矩形裁剪优化 第16章:ClipperOffset 偏移类详解 第15章:填充规则详解 第14章:布尔运算执行流程 第13章:ClipperD 浮点裁剪类 第11章:OutRec 与 OutPt 输出结构 第9章:Active 活动边结构 第10章:Vertex 顶点与 LocalMinima 局部极小值 第12章:Clipper64 裁剪类详解 第7章:高精度运算与128位整数 第8章:ClipperBase 基类详解 第5章:枚举类型与常量定义 第6章:InternalClipper 内部工具类 第2章:核心数据结构 - Point64、PointD 第3章:路径与多边形表示 - Path64、PathD、Paths64、PathsD 第4章:矩形边界 - Rect64、RectD
第五章:扩展数据与字典操作教程
我才是银古 · 2026-06-20 · via 博客园 - 我才是银古

第五章:扩展数据与字典操作教程

5.1 扩展数据基础

5.1.1 什么是扩展数据

在AutoCAD二次开发中,我们经常需要在图元上存储自定义数据。AutoCAD提供了两种主要的数据存储方式:

  1. 扩展数据(XData):直接附加在图元上的数据
  2. 扩展字典(XDictionary):通过字典对象存储的数据

扩展数据(Extended Data,简称XData)是一种将自定义信息附加到图元的机制。每个图元最多可以存储16KB的扩展数据。

5.1.2 扩展数据的结构

扩展数据由一系列TypedValue对象组成,每个TypedValue包含一个DXF组码和对应的值:

扩展数据结构:
┌─────────────────────────────────────────┐
│ 应用程序名 (1001)                        │
├─────────────────────────────────────────┤
│ 数据项1 (1000-1071)                      │
│ 数据项2 (1000-1071)                      │
│ ...                                     │
└─────────────────────────────────────────┘

常用的扩展数据组码:

组码 数据类型 说明
1000 字符串 最多255个字符
1001 字符串 应用程序名(必须注册)
1002 字符串 控制字符串("{" 或 "}")
1003 图层名 图层名称
1010 Point3d 三维点
1040 double 实数
1041 double 距离(受比例影响)
1070 short 16位整数
1071 int 32位整数

5.1.3 注册应用程序

在使用扩展数据之前,必须先注册应用程序名称:

using var tr = new DBTrans();

// 注册应用程序
string appName = "MyApplication";
if (!tr.RegAppTable.Has(appName))
{
    tr.RegAppTable.Add(appName);
}

5.2 IFoxCAD的XDataList类

5.2.1 XDataList类设计

IFoxCAD提供了XDataList类来简化扩展数据的操作:

public class XDataList : List<TypedValue>
{
    // 应用程序名
    public string AppName { get; private set; }
    
    // 构造函数
    public XDataList(string appName);
    public XDataList(ResultBuffer resultBuffer);
    
    // 添加数据
    public void Add(DxfCode code, object value);
    public void Add(int code, object value);
    
    // 转换方法
    public ResultBuffer ToResultBuffer();
    public TypedValue[] ToArray();
}

5.2.2 创建扩展数据

using var tr = new DBTrans();

// 确保应用程序已注册
string appName = "MyApp";
if (!tr.RegAppTable.Has(appName))
{
    tr.RegAppTable.Add(appName);
}

// 创建扩展数据
var xdata = new XDataList(appName);
xdata.Add(DxfCode.ExtendedDataAsciiString, "项目编号");
xdata.Add(DxfCode.ExtendedDataAsciiString, "PRJ-001");
xdata.Add(DxfCode.ExtendedDataInteger32, 100);
xdata.Add(DxfCode.ExtendedDataReal, 3.14159);

// 选择图元并添加扩展数据
var result = tr.Editor?.GetEntity("\n请选择图元:");
if (result?.Status == PromptStatus.OK)
{
    var ent = tr.GetObject<Entity>(result.ObjectId, OpenMode.ForWrite);
    if (ent != null)
    {
        ent.XData = xdata.ToResultBuffer();
        tr.Editor?.WriteMessage("\n扩展数据已添加!");
    }
}

5.2.3 读取扩展数据

using var tr = new DBTrans();

// 选择图元
var result = tr.Editor?.GetEntity("\n请选择图元:");
if (result?.Status != PromptStatus.OK) return;

var ent = tr.GetObject<Entity>(result.ObjectId);
if (ent == null) return;

// 获取扩展数据
ResultBuffer? rb = ent.GetXDataForApplication("MyApp");
if (rb == null)
{
    tr.Editor?.WriteMessage("\n该图元没有扩展数据!");
    return;
}

// 读取数据
var xdata = new XDataList(rb);
tr.Editor?.WriteMessage("\n扩展数据内容:");
foreach (var tv in xdata)
{
    tr.Editor?.WriteMessage($"\n  组码 {tv.TypeCode}: {tv.Value}");
}

5.2.4 修改扩展数据

using var tr = new DBTrans();

var result = tr.Editor?.GetEntity("\n请选择图元:");
if (result?.Status != PromptStatus.OK) return;

var ent = tr.GetObject<Entity>(result.ObjectId, OpenMode.ForWrite);
if (ent == null) return;

// 获取现有扩展数据
string appName = "MyApp";
ResultBuffer? rb = ent.GetXDataForApplication(appName);

if (rb != null)
{
    var xdata = new XDataList(rb);
    
    // 修改数据(找到并替换)
    for (int i = 0; i < xdata.Count; i++)
    {
        if (xdata[i].TypeCode == (int)DxfCode.ExtendedDataAsciiString &&
            xdata[i].Value.ToString() == "PRJ-001")
        {
            xdata[i] = new TypedValue(
                (int)DxfCode.ExtendedDataAsciiString, "PRJ-002");
        }
    }
    
    // 保存修改
    ent.XData = xdata.ToResultBuffer();
    tr.Editor?.WriteMessage("\n扩展数据已修改!");
}

5.2.5 删除扩展数据

using var tr = new DBTrans();

var result = tr.Editor?.GetEntity("\n请选择图元:");
if (result?.Status != PromptStatus.OK) return;

var ent = tr.GetObject<Entity>(result.ObjectId, OpenMode.ForWrite);
if (ent == null) return;

// 删除特定应用程序的扩展数据
string appName = "MyApp";

// 创建只包含应用程序名的空扩展数据
var emptyXdata = new ResultBuffer(
    new TypedValue((int)DxfCode.ExtendedDataRegAppName, appName));

ent.XData = emptyXdata;
tr.Editor?.WriteMessage("\n扩展数据已删除!");

5.3 XRecordDataList类

5.3.1 什么是XRecord

XRecord(扩展记录)是另一种存储自定义数据的方式。与XData不同,XRecord没有16KB的大小限制,可以存储更多的数据。XRecord通常存储在字典中。

5.3.2 XRecordDataList类

IFoxCAD提供了XRecordDataList类来简化XRecord的操作:

public class XRecordDataList : List<TypedValue>
{
    // 添加数据
    public void Add(DxfCode code, object value);
    public void Add(int code, object value);
    
    // 转换方法
    public ResultBuffer ToResultBuffer();
    public static XRecordDataList FromResultBuffer(ResultBuffer rb);
}

5.3.3 创建和使用XRecord

using var tr = new DBTrans();

// 获取或创建自定义字典
DBDictionary myDict;
string dictName = "MyAppDict";

if (tr.NamedObjectsDict.Contains(dictName))
{
    myDict = tr.GetObject<DBDictionary>(
        tr.NamedObjectsDict.GetAt(dictName), OpenMode.ForWrite)!;
}
else
{
    using (tr.NamedObjectsDict.ForWrite())
    {
        myDict = new DBDictionary();
        tr.NamedObjectsDict.SetAt(dictName, myDict);
        tr.Transaction.AddNewlyCreatedDBObject(myDict, true);
    }
}

// 创建XRecord数据
var data = new XRecordDataList();
data.Add(DxfCode.Text, "配置项1");
data.Add(DxfCode.Text, "值1");
data.Add(DxfCode.Int32, 100);
data.Add(DxfCode.Real, 3.14159);

// 创建XRecord并存储
using (myDict.ForWrite())
{
    var xrec = new Xrecord();
    xrec.Data = data.ToResultBuffer();
    myDict.SetAt("Settings", xrec);
    tr.Transaction.AddNewlyCreatedDBObject(xrec, true);
}

tr.Editor?.WriteMessage("\n数据已保存!");

5.3.4 读取XRecord数据

using var tr = new DBTrans();

// 获取字典
string dictName = "MyAppDict";
if (!tr.NamedObjectsDict.Contains(dictName))
{
    tr.Editor?.WriteMessage("\n找不到字典!");
    return;
}

var myDict = tr.GetObject<DBDictionary>(
    tr.NamedObjectsDict.GetAt(dictName))!;

// 读取XRecord
if (!myDict.Contains("Settings"))
{
    tr.Editor?.WriteMessage("\n找不到设置!");
    return;
}

var xrec = tr.GetObject<Xrecord>(myDict.GetAt("Settings"))!;
var data = xrec.Data;

if (data != null)
{
    tr.Editor?.WriteMessage("\n读取到的数据:");
    foreach (TypedValue tv in data)
    {
        tr.Editor?.WriteMessage($"\n  组码 {tv.TypeCode}: {tv.Value}");
    }
}

5.4 图元扩展字典

5.4.1 扩展字典概念

每个DBObject(包括图元)都可以拥有自己的扩展字典(Extension Dictionary)。扩展字典可以存储任意数量的数据,不受XData 16KB的限制。

5.4.2 创建扩展字典

using var tr = new DBTrans();

// 选择图元
var result = tr.Editor?.GetEntity("\n请选择图元:");
if (result?.Status != PromptStatus.OK) return;

var ent = tr.GetObject<Entity>(result.ObjectId, OpenMode.ForWrite);
if (ent == null) return;

// 确保图元有扩展字典
ObjectId extDictId = ent.ExtensionDictionary;
DBDictionary extDict;

if (extDictId.IsNull)
{
    // 创建扩展字典
    ent.CreateExtensionDictionary();
    extDict = tr.GetObject<DBDictionary>(
        ent.ExtensionDictionary, OpenMode.ForWrite)!;
}
else
{
    extDict = tr.GetObject<DBDictionary>(extDictId, OpenMode.ForWrite)!;
}

// 在扩展字典中存储数据
var data = new XRecordDataList();
data.Add(DxfCode.Text, "设备名称");
data.Add(DxfCode.Text, "电动机M1");
data.Add(DxfCode.Real, 5.5);  // 功率
data.Add(DxfCode.Int32, 380); // 电压

using (extDict.ForWrite())
{
    var xrec = new Xrecord();
    xrec.Data = data.ToResultBuffer();
    extDict.SetAt("DeviceInfo", xrec);
    tr.Transaction.AddNewlyCreatedDBObject(xrec, true);
}

tr.Editor?.WriteMessage("\n设备信息已保存到图元扩展字典!");

5.4.3 读取扩展字典

using var tr = new DBTrans();

var result = tr.Editor?.GetEntity("\n请选择图元:");
if (result?.Status != PromptStatus.OK) return;

var ent = tr.GetObject<Entity>(result.ObjectId);
if (ent == null) return;

// 获取扩展字典
if (ent.ExtensionDictionary.IsNull)
{
    tr.Editor?.WriteMessage("\n该图元没有扩展字典!");
    return;
}

var extDict = tr.GetObject<DBDictionary>(ent.ExtensionDictionary)!;

// 读取数据
if (!extDict.Contains("DeviceInfo"))
{
    tr.Editor?.WriteMessage("\n找不到设备信息!");
    return;
}

var xrec = tr.GetObject<Xrecord>(extDict.GetAt("DeviceInfo"))!;
if (xrec.Data != null)
{
    tr.Editor?.WriteMessage("\n设备信息:");
    var tvs = xrec.Data.AsArray();
    for (int i = 0; i < tvs.Length; i += 2)
    {
        if (i + 1 < tvs.Length)
        {
            tr.Editor?.WriteMessage($"\n  {tvs[i].Value}: {tvs[i + 1].Value}");
        }
    }
}

5.5 命名对象字典操作

5.5.1 命名对象字典结构

命名对象字典(Named Objects Dictionary)是数据库的根字典,可以存储自定义的字典和数据。

命名对象字典
├── ACAD_GROUP (组字典)
├── ACAD_LAYOUT (布局字典)
├── ACAD_MATERIAL (材质字典)
├── MyAppDict (自定义字典)
│   ├── Settings
│   ├── UserData
│   └── ...
└── ...

5.5.2 创建自定义字典层次

using var tr = new DBTrans();

// 创建多层字典结构
// MyApp
// ├── Config
// │   ├── General
// │   └── Advanced
// └── Data
//     ├── Projects
//     └── Users

// 获取或创建根字典
DBDictionary GetOrCreateDict(DBDictionary parent, string name)
{
    using (parent.ForWrite())
    {
        if (parent.Contains(name))
        {
            return tr.GetObject<DBDictionary>(parent.GetAt(name))!;
        }
        else
        {
            var dict = new DBDictionary();
            parent.SetAt(name, dict);
            tr.Transaction.AddNewlyCreatedDBObject(dict, true);
            return dict;
        }
    }
}

// 创建字典结构
var rootDict = GetOrCreateDict(tr.NamedObjectsDict, "MyApp");
var configDict = GetOrCreateDict(rootDict, "Config");
var generalDict = GetOrCreateDict(configDict, "General");
var advancedDict = GetOrCreateDict(configDict, "Advanced");
var dataDict = GetOrCreateDict(rootDict, "Data");
var projectsDict = GetOrCreateDict(dataDict, "Projects");
var usersDict = GetOrCreateDict(dataDict, "Users");

tr.Editor?.WriteMessage("\n字典结构创建完成!");

5.5.3 存储复杂数据

using var tr = new DBTrans();

// 获取字典
var rootDict = tr.GetObject<DBDictionary>(
    tr.NamedObjectsDict.GetAt("MyApp"), OpenMode.ForWrite)!;
var projectsDict = tr.GetObject<DBDictionary>(
    tr.GetObject<DBDictionary>(rootDict.GetAt("Data"))!.GetAt("Projects"),
    OpenMode.ForWrite)!;

// 存储项目数据
void SaveProject(string projectId, string name, string description, 
    double area, DateTime createDate)
{
    var data = new XRecordDataList();
    data.Add(DxfCode.Text, "Name");
    data.Add(DxfCode.Text, name);
    data.Add(DxfCode.Text, "Description");
    data.Add(DxfCode.Text, description);
    data.Add(DxfCode.Text, "Area");
    data.Add(DxfCode.Real, area);
    data.Add(DxfCode.Text, "CreateDate");
    data.Add(DxfCode.Text, createDate.ToString("yyyy-MM-dd"));
    
    using (projectsDict.ForWrite())
    {
        Xrecord xrec;
        if (projectsDict.Contains(projectId))
        {
            xrec = tr.GetObject<Xrecord>(
                projectsDict.GetAt(projectId), OpenMode.ForWrite)!;
        }
        else
        {
            xrec = new Xrecord();
            projectsDict.SetAt(projectId, xrec);
            tr.Transaction.AddNewlyCreatedDBObject(xrec, true);
        }
        xrec.Data = data.ToResultBuffer();
    }
}

// 保存几个项目
SaveProject("PRJ001", "办公楼项目", "市中心办公楼设计", 5000.0, DateTime.Now);
SaveProject("PRJ002", "住宅项目", "高层住宅小区", 12000.0, DateTime.Now);
SaveProject("PRJ003", "商业中心", "购物中心设计", 8000.0, DateTime.Now);

tr.Editor?.WriteMessage("\n项目数据已保存!");

5.5.4 读取和遍历字典

using var tr = new DBTrans();

// 遍历所有项目
if (!tr.NamedObjectsDict.Contains("MyApp"))
{
    tr.Editor?.WriteMessage("\n找不到应用数据!");
    return;
}

var rootDict = tr.GetObject<DBDictionary>(
    tr.NamedObjectsDict.GetAt("MyApp"))!;

if (!rootDict.Contains("Data")) return;
var dataDict = tr.GetObject<DBDictionary>(rootDict.GetAt("Data"))!;

if (!dataDict.Contains("Projects")) return;
var projectsDict = tr.GetObject<DBDictionary>(dataDict.GetAt("Projects"))!;

tr.Editor?.WriteMessage("\n项目列表:");
tr.Editor?.WriteMessage("\n" + new string('-', 60));

foreach (DBDictionaryEntry entry in projectsDict)
{
    string projectId = entry.Key;
    var xrec = tr.GetObject<Xrecord>(entry.Value)!;
    
    if (xrec.Data != null)
    {
        var data = xrec.Data.AsArray();
        
        // 解析数据
        Dictionary<string, string> project = new();
        for (int i = 0; i < data.Length; i += 2)
        {
            if (i + 1 < data.Length)
            {
                project[data[i].Value.ToString()!] = data[i + 1].Value.ToString()!;
            }
        }
        
        tr.Editor?.WriteMessage($"\n项目ID: {projectId}");
        tr.Editor?.WriteMessage($"\n  名称: {project.GetValueOrDefault("Name", "")}");
        tr.Editor?.WriteMessage($"\n  描述: {project.GetValueOrDefault("Description", "")}");
        tr.Editor?.WriteMessage($"\n  面积: {project.GetValueOrDefault("Area", "")}");
        tr.Editor?.WriteMessage($"\n  创建日期: {project.GetValueOrDefault("CreateDate", "")}");
        tr.Editor?.WriteMessage("\n");
    }
}

5.6 实体与数据关联

5.6.1 建立图元数据关联

一种常见的需求是将图形中的图元与外部数据关联。可以使用XData存储标识符:

using var tr = new DBTrans();

// 确保应用程序已注册
string appName = "MyDataLink";
if (!tr.RegAppTable.Has(appName))
{
    tr.RegAppTable.Add(appName);
}

// 为图元添加数据标识
void LinkEntityToData(Entity ent, string dataId)
{
    using (ent.ForWrite())
    {
        var xdata = new XDataList(appName);
        xdata.Add(DxfCode.ExtendedDataAsciiString, "DataLink");
        xdata.Add(DxfCode.ExtendedDataAsciiString, dataId);
        ent.XData = xdata.ToResultBuffer();
    }
}

// 获取图元关联的数据ID
string? GetLinkedDataId(Entity ent)
{
    var rb = ent.GetXDataForApplication(appName);
    if (rb == null) return null;
    
    var data = rb.AsArray();
    for (int i = 0; i < data.Length - 1; i++)
    {
        if (data[i].Value.ToString() == "DataLink")
        {
            return data[i + 1].Value.ToString();
        }
    }
    return null;
}

// 示例:为选择的图元添加关联
var result = tr.Editor?.GetSelection();
if (result?.Status == PromptStatus.OK)
{
    int index = 1;
    foreach (SelectedObject obj in result.Value)
    {
        if (obj == null) continue;
        
        var ent = tr.GetObject<Entity>(obj.ObjectId, OpenMode.ForWrite);
        if (ent != null)
        {
            string dataId = $"DATA_{index:D5}";
            LinkEntityToData(ent, dataId);
            index++;
        }
    }
    
    tr.Editor?.WriteMessage($"\n已为 {result.Value.Count} 个图元添加数据关联!");
}

5.6.2 查找关联图元

using var tr = new DBTrans();

// 查找所有有数据关联的图元
string appName = "MyDataLink";

var filter = OpFilter.Build(e => e.Dxf(-3) == appName);
var result = tr.Editor?.SelectAll(filter);

if (result?.Status == PromptStatus.OK)
{
    tr.Editor?.WriteMessage("\n关联的图元:");
    
    foreach (SelectedObject obj in result.Value)
    {
        if (obj == null) continue;
        
        var ent = tr.GetObject<Entity>(obj.ObjectId);
        if (ent == null) continue;
        
        var rb = ent.GetXDataForApplication(appName);
        if (rb != null)
        {
            var data = rb.AsArray();
            for (int i = 0; i < data.Length - 1; i++)
            {
                if (data[i].Value.ToString() == "DataLink")
                {
                    tr.Editor?.WriteMessage(
                        $"\n  {ent.GetType().Name} -> {data[i + 1].Value}");
                    break;
                }
            }
        }
    }
}

5.7 数据持久化方案

5.7.1 文档级数据存储

对于需要在文档中持久化的应用配置,可以使用命名对象字典:

public class DocumentDataManager
{
    private const string RootDictName = "MyAppSettings";
    
    public static void SaveSetting(string key, object value)
    {
        using var tr = new DBTrans();
        
        // 获取或创建设置字典
        DBDictionary settingsDict;
        if (tr.NamedObjectsDict.Contains(RootDictName))
        {
            settingsDict = tr.GetObject<DBDictionary>(
                tr.NamedObjectsDict.GetAt(RootDictName), OpenMode.ForWrite)!;
        }
        else
        {
            using (tr.NamedObjectsDict.ForWrite())
            {
                settingsDict = new DBDictionary();
                tr.NamedObjectsDict.SetAt(RootDictName, settingsDict);
                tr.Transaction.AddNewlyCreatedDBObject(settingsDict, true);
            }
        }
        
        // 保存设置
        var data = new ResultBuffer(
            new TypedValue((int)DxfCode.Text, value.ToString()));
        
        using (settingsDict.ForWrite())
        {
            Xrecord xrec;
            if (settingsDict.Contains(key))
            {
                xrec = tr.GetObject<Xrecord>(
                    settingsDict.GetAt(key), OpenMode.ForWrite)!;
            }
            else
            {
                xrec = new Xrecord();
                settingsDict.SetAt(key, xrec);
                tr.Transaction.AddNewlyCreatedDBObject(xrec, true);
            }
            xrec.Data = data;
        }
    }
    
    public static string? GetSetting(string key)
    {
        using var tr = new DBTrans();
        
        if (!tr.NamedObjectsDict.Contains(RootDictName))
            return null;
        
        var settingsDict = tr.GetObject<DBDictionary>(
            tr.NamedObjectsDict.GetAt(RootDictName))!;
        
        if (!settingsDict.Contains(key))
            return null;
        
        var xrec = tr.GetObject<Xrecord>(settingsDict.GetAt(key))!;
        if (xrec.Data == null) return null;
        
        var tvs = xrec.Data.AsArray();
        return tvs.Length > 0 ? tvs[0].Value.ToString() : null;
    }
}

5.7.2 使用示例

[CommandMethod("SaveConfig")]
public void SaveConfiguration()
{
    using var tr = new DBTrans();
    
    // 保存各种设置
    DocumentDataManager.SaveSetting("ProjectName", "示例项目");
    DocumentDataManager.SaveSetting("Author", "张三");
    DocumentDataManager.SaveSetting("Version", "1.0.0");
    DocumentDataManager.SaveSetting("LastModified", DateTime.Now.ToString());
    
    tr.Editor?.WriteMessage("\n配置已保存!");
}

[CommandMethod("LoadConfig")]
public void LoadConfiguration()
{
    using var tr = new DBTrans();
    
    tr.Editor?.WriteMessage("\n项目配置:");
    tr.Editor?.WriteMessage($"\n  项目名称: {DocumentDataManager.GetSetting("ProjectName")}");
    tr.Editor?.WriteMessage($"\n  作者: {DocumentDataManager.GetSetting("Author")}");
    tr.Editor?.WriteMessage($"\n  版本: {DocumentDataManager.GetSetting("Version")}");
    tr.Editor?.WriteMessage($"\n  最后修改: {DocumentDataManager.GetSetting("LastModified")}");
}

5.8 最佳实践

5.8.1 选择合适的存储方式

存储方式 适用场景 优点 缺点
XData 小量图元数据 直接关联图元 16KB限制
扩展字典 大量图元数据 无大小限制 需要额外管理
命名对象字典 全局配置数据 易于访问 与图元无直接关联

5.8.2 数据版本管理

public class DataVersionManager
{
    private const string VersionKey = "_DataVersion";
    private const string CurrentVersion = "2.0";
    
    public static bool NeedsMigration()
    {
        string? version = DocumentDataManager.GetSetting(VersionKey);
        return version != CurrentVersion;
    }
    
    public static void MigrateData()
    {
        string? version = DocumentDataManager.GetSetting(VersionKey);
        
        if (string.IsNullOrEmpty(version))
        {
            // 从无版本迁移
            MigrateFromV0();
        }
        else if (version == "1.0")
        {
            // 从1.0迁移到2.0
            MigrateFromV1();
        }
        
        // 更新版本号
        DocumentDataManager.SaveSetting(VersionKey, CurrentVersion);
    }
    
    private static void MigrateFromV0()
    {
        // 迁移旧版本数据的逻辑
    }
    
    private static void MigrateFromV1()
    {
        // 从1.0迁移到2.0的逻辑
    }
}

5.8.3 错误处理

public static class SafeDataAccess
{
    public static T? GetData<T>(Func<T> accessor, T? defaultValue = default)
    {
        try
        {
            return accessor();
        }
        catch (Exception ex)
        {
            System.Diagnostics.Debug.WriteLine($"数据访问错误: {ex.Message}");
            return defaultValue;
        }
    }
    
    public static bool TrySetData(Action setter, out string? error)
    {
        try
        {
            setter();
            error = null;
            return true;
        }
        catch (Exception ex)
        {
            error = ex.Message;
            return false;
        }
    }
}

// 使用示例
var projectName = SafeDataAccess.GetData(
    () => DocumentDataManager.GetSetting("ProjectName"),
    "未命名项目");

if (!SafeDataAccess.TrySetData(
    () => DocumentDataManager.SaveSetting("Key", "Value"),
    out var error))
{
    // 处理错误
}

5.9 本章小结

本章我们深入学习了IFoxCAD的扩展数据和字典操作:

  1. 扩展数据基础:理解了XData的概念、结构和组码
  2. XDataList类:学习了创建、读取、修改和删除扩展数据
  3. XRecordDataList类:掌握了XRecord的创建和使用
  4. 图元扩展字典:学习了如何为图元创建和使用扩展字典
  5. 命名对象字典:掌握了创建字典层次和存储复杂数据
  6. 实体数据关联:学习了如何建立图元与数据的关联
  7. 数据持久化:了解了文档级数据存储的方案
  8. 最佳实践:学习了存储方式选择、版本管理和错误处理

扩展数据和字典是CAD二次开发中数据存储的核心机制,掌握这些技术对于开发功能完整的CAD插件至关重要。下一章我们将学习IFoxCAD的扩展方法体系。