Delphi 13 集成 TMS VCL Diagram Studio v4.32 实战:安装、序列化与避坑指南 简介本资源为面向Delphi 13开发者的TMS VCL Diagram Studio v4.32.0.0组件包适合需要在VCL应用中实现流程图、组织结构图、网络拓扑图等专业图表的中高级开发者。包内共272个文件以60个res资源文件、56个pas源码单元、40个dpk包定义、38个dproj工程文件及23个dfm窗体文件为主另含dgr图表模板、dpr示例工程与chm帮助文档压缩包约1.82MB结构完整便于直接集成。组件支持节点添加、移动、连接与属性编辑并提供颜色、边框、字体等界面定制及图表导入导出能力可显著减少绘图编码量。目前已有120人学习下载适合希望快速为Delphi 13项目补充可视化图表能力的读者参考使用。1. Delphi 13 里把 TMS VCL Diagram Studio 跑起来它到底解决什么问题如果你正在用 Delphi 13 做工业组态、流程编排、拓扑图或者工单流转界面大概率绕不开一个需求让用户在界面上拖拽节点、连线、改属性还要能存成文件下次打开继续编辑。自己用 TCanvas 画线画框做到第三周就会发现命中检测、撤销重做、缩放平移、序列化全是坑。TMS VCL Diagram Studio 就是干这个的——它是一套 VCL 图元编辑控件把画布、图元、连接线、选择框、图库面板、打印导出这些基础设施都封好了你只需要关心业务数据怎么映射到图元上。标题里的 v4.32.0.0 是这套控件的版本号7z 是压缩包格式Delphi 13 是目标 IDE 版本。这篇文章不聊虚的就讲三件事这套控件在 Delphi 13 下怎么装、最小可运行的程序怎么写、以及从图元序列化到打印导出这条链路上最容易翻车的地方在哪。适合已经会用 Delphi 写 VCL 程序、但没接触过 Diagram Studio 的开发者也适合之前用过老版本、想确认 Delphi 13 下有没有行为变化的熟手。2. 安装与工程配置从 7z 解压到第一个图元出现在窗体上2.1 解压后的目录结构与包文件识别拿到 7z 压缩包后先解压到一个路径不含中文和空格的目录比如D:\Components\TMSDiagram。这一点不是洁癖Delphi 的包编译对路径里的空格和中文偶尔会出玄学问题尤其是涉及 IDE 注册表写入的时候。解压后典型目录结构是这样的目录/文件用途Source\控件源码单元含图元、连接线、图库等核心实现Packages\各 Delphi 版本的 .dpk 包工程Demos\示例工程通常按功能分文件夹Help\帮助文档chm 或 hlp 格式Lib\预编译的 dcus按平台和版本分关键动作是找到Packages下对应 Delphi 13 的包工程。TMS 的命名习惯一般是DiagramStudioDXE14.dpk这种DXE 后面的数字对应 IDE 版本。Delphi 13 的编译器版本号是 37如果你在包工程里看到{$IFDEF VER370}之类的条件编译说明这个包已经适配了。如果只有到 VER360 的条件分支也不一定跑不起来但需要留意后面避坑章节里提到的编译错误。2.2 在 Delphi 13 IDE 中编译并安装设计期包打开对应的 dpk 工程先编译运行时包通常叫DiagramStudioR.dpk或类似名字再编译设计期包。顺序不能反设计期包依赖运行时包。编译通过后右键设计期包选择 InstallIDE 会提示已注册的组件页一般是TMS Diagram或TMS。安装成功后在 Tool Palette 里应该能看到TDiagram、TDiagramToolBar、TDiagramGallery这几个核心组件。如果没看到先检查 Library Path 里有没有把Source目录加进去。Delphi 13 的 Library Path 在Tools Options Language Delphi Library下注意要选对目标平台Win32 还是 Win64两个平台的路径是分开配的。// 在窗体单元的 uses 里加入核心单元 uses Diagram, DiagramTools, DiagramGallery, DiagramPrint; // Diagram : TDiagram 主画布控件 // DiagramTools : 工具栏、选择工具、连线工具 // DiagramGallery : 图库面板提供预置图元模板 // DiagramPrint : 打印与导出相关这段 uses 是后面所有代码的基础。Diagram单元里定义了TDiagram和TDiagramObject基类DiagramTools里是交互工具类DiagramGallery负责图库的加载和拖放。如果你只做只读展示可以不加DiagramTools能省一点编译体积。2.3 最小可运行示例一个图元加一条连线不要一上来就搞复杂拓扑先用最少代码确认控件能正常工作。在窗体上放一个TDiagram然后在 FormCreate 里写procedure TForm1.FormCreate(Sender: TObject); var R1, R2: TDiagramRect; Conn: TDiagramConnection; begin Diagram1.BeginUpdate; // 批量操作前挂起重绘避免闪烁 try // 创建第一个矩形图元 R1 : TDiagramRect.Create(Diagram1); R1.Left : 50; R1.Top : 50; R1.Width : 120; R1.Height : 60; R1.Text : 开始; R1.Pen.Color : clNavy; R1.Brush.Color : clSkyBlue; // 创建第二个矩形图元 R2 : TDiagramRect.Create(Diagram1); R2.Left : 300; R2.Top : 50; R2.Width : 120; R2.Height : 60; R2.Text : 处理; R2.Pen.Color : clNavy; R2.Brush.Color : clSkyBlue; // 创建连接线从 R1 连到 R2 Conn : TDiagramConnection.Create(Diagram1); Conn.Source : R1; Conn.Target : R2; Conn.SourceAnchor : 3; // 右侧锚点 Conn.TargetAnchor : 1; // 左侧锚点 Conn.Text : 流转; finally Diagram1.EndUpdate; // 恢复重绘 end; end;BeginUpdate/EndUpdate这对调用是血泪经验图元数量超过二十个以后不挂起重绘每次属性赋值都会触发一次重绘界面会闪得没法看。SourceAnchor和TargetAnchor的取值是 0 到 7对应图元边框上的八个锚点位置顺时针排列0 是左上角。这个参数设错了连线会从奇怪的角度绕出来但不会报错属于典型的静默翻车。3. 图元数据绑定与序列化让画布上的图形和业务数据对上号3.1 用 Tag 还是自定义属性两种数据绑定路径的取舍图元画出来了下一步就是让它承载业务数据。Diagram Studio 的TDiagramObject有一个Tag属性是NativeInt类型可以塞一个主键 ID 进去。但如果你需要存多个字段比如节点类型、状态、负责人光靠 Tag 就不够了。常见做法有两种第一种是继承TDiagramRect或TDiagramShape加自己的 published 属性。这种方式的优点是属性可以出现在 Object Inspector 里设计期就能改缺点是每加一种图元类型就要多一个类类多了以后图库管理会变重。第二种是用TDiagramObject.Data属性它是个TStringList可以存任意键值对。这种方式灵活不用改类结构但设计期看不到全靠代码维护。我一般会在图元创建后立刻把业务主键写进 Data// 创建图元后绑定业务数据 R1.Data.Values[BizId] : 1001; R1.Data.Values[NodeType] : Start; R1.Data.Values[Owner] : 张三; // 读取时做防御性判断 function GetBizId(AObj: TDiagramObject): string; begin if Assigned(AObj.Data) then Result : AObj.Data.Values[BizId] else Result : ; end;Data属性在对象创建时可能为 nil直接访问会报访问违例所以读取前必须判断。这个坑我在第一次用的时候踩过调试了半天才发现是 Data 没初始化。3.2 保存与加载Diagram 的序列化格式与版本兼容TDiagram提供了SaveToFile和LoadFromFile默认格式是二进制。如果你需要人眼可读或者做版本 diff可以用SaveToStream配合自定义的 XML 序列化。但大多数场景下二进制够用关键是加载时的版本兼容。// 保存 Diagram1.SaveToFile(D:\temp\flow.dgm); // 加载 if FileExists(D:\temp\flow.dgm) then begin Diagram1.BeginUpdate; try Diagram1.LoadFromFile(D:\temp\flow.dgm); finally Diagram1.EndUpdate; end; end;加载旧版本文件到新版本控件时偶尔会遇到图元属性丢失的情况。原因是新版本可能改了某个属性的默认值或者存储格式。稳妥的做法是在加载后遍历所有图元对关键属性做一次校验和补全procedure TForm1.ValidateAfterLoad; var I: Integer; Obj: TDiagramObject; begin for I : 0 to Diagram1.ObjectCount - 1 do begin Obj : Diagram1.Objects[I]; if Obj.Data.Values[NodeType] then Obj.Data.Values[NodeType] : Unknown; if Obj.Width 20 then Obj.Width : 80; // 修复异常尺寸 end; end;ObjectCount和Objects是遍历图元的标准入口。注意Objects返回的顺序不保证和创建顺序一致如果你的业务逻辑依赖顺序需要在 Data 里额外存一个序号字段。3.3 连接线的业务语义不只是画一条线连接线在业务上往往代表流转关系需要存条件表达式、优先级、超时时间这些信息。TDiagramConnection同样有 Data 属性用法和图元一致。但连接线的 Source 和 Target 在保存加载后可能变成 nil尤其是当目标图元被删除但连接线没清理的时候。// 清理悬空连接线 procedure TForm1.CleanDanglingConnections; var I: Integer; Conn: TDiagramConnection; begin for I : Diagram1.ObjectCount - 1 downto 0 do begin if Diagram1.Objects[I] is TDiagramConnection then begin Conn : TDiagramConnection(Diagram1.Objects[I]); if (Conn.Source nil) or (Conn.Target nil) then Conn.Free; end; end; end;倒序遍历是因为 Free 会改变集合内容正序会越界。这个写法在删除图元时也要用先删连接线再删图元否则连接线会变成悬空状态界面上看不出来但保存后再加载就可能报错。4. 打印、导出与交互细节交付前必须过的三道关4.1 打印与导出图片的参数配置Diagram Studio 的打印走的是TDiagramPrint单元核心是PrintDiagram方法。但直接打印往往出来的效果和屏幕上不一样问题出在缩放和页边距上。procedure TForm1.PrintDiagram1; var Print: TDiagramPrint; begin Print : TDiagramPrint.Create; try Print.Diagram : Diagram1; Print.FitToPages : True; // 自动缩放到页数 Print.PagesCount : 1; // 强制一页适合小图 Print.MarginLeft : 10; // 单位是毫米 Print.MarginTop : 10; Print.MarginRight : 10; Print.MarginBottom : 10; Print.Print; finally Print.Free; end; end;FitToPages设为 True 时PagesCount才生效。如果图很大又强制一页字会小到看不清这时候应该设PagesCount为 0 让控件自己算或者按纸张大小分页。导出图片用ExportToBitmap或ExportToMetafileMetafile 是矢量格式放大不糊适合插入 Word 文档。4.2 缩放平移与命中检测的交互调优TDiagram自带缩放和平移但默认的鼠标滚轮行为可能不符合预期。常见做法是在OnMouseWheel里自己控制procedure TForm1.Diagram1MouseWheel(Sender: TObject; Shift: TShiftState; WheelDelta: Integer; MousePos: TPoint; var Handled: Boolean); begin if ssCtrl in Shift then begin if WheelDelta 0 then Diagram1.Zoom : Diagram1.Zoom * 1.1 else Diagram1.Zoom : Diagram1.Zoom / 1.1; Handled : True; end; end;Zoom属性是浮点数1.0 表示原始大小。注意缩放后鼠标坐标到画布坐标的转换要用ScreenToClient配合Zoom做换算否则拖拽图元时位置会偏。命中检测用Diagram1.GetObjectAt(X, Y)返回 nil 表示没点中图元。这个方法在缩放状态下内部会做换算不用自己处理。4.3 撤销重做栈的边界与内存控制Diagram Studio 内置了 Undo/Redo但默认栈深度有限。图元操作频繁时栈会很快满旧的操作被丢弃。可以通过Diagram1.UndoLimit调整但设太大内存会涨。Diagram1.UndoLimit : 50; // 默认通常是 20 左右另一个坑是批量操作。如果你在一个循环里创建了 100 个图元每个创建都会进 Undo 栈用户按一次 CtrlZ 只撤销一个体验很差。正确做法是用BeginUpdate/EndUpdate包起来这样整批操作算一步。但注意BeginUpdate期间 Undo 是挂起的EndUpdate后才会记录。5. 避坑与排查Delphi 13 下最容易翻车的五个场景5.1 编译报错「Unit Diagram not found」现象包安装后新建工程uses 里加Diagram编译报找不到单元。原因Library Path 没配全或者只配了 Win32 没配 Win64。Delphi 13 的 Library Path 是按平台分开的切换目标平台后路径不会自动跟着变。解决在Tools Options Language Delphi Library里先选 Win32 平台把Source目录加进去再切到 Win64 加一遍。如果用了预编译 dcus还要把Lib\Win32\Release和Lib\Win64\Release分别加进去。5.2 设计期能看到组件但运行时报「Class TDiagramRect not found」现象编译通过运行到创建图元的代码时抛异常提示类未注册。原因运行时包没有正确加载或者工程里同时引用了两个版本的 Diagram 单元。常见于之前装过旧版本没清理干净。解决先检查工程搜索路径里有没有残留的旧版 Source 目录。然后在 IDE 的Component Install Packages里确认 TMS 的运行时包和设计期包都是勾选状态。如果还不行把工程目录下的 dcu 全部删掉重新编译Delphi 有时候会用缓存的旧 dcu。5.3 图元文字显示为乱码或方框现象图元上设置的 Text 在界面上显示为问号或方框。原因字体不支持中文或者Font.Charset没设对。Diagram Studio 默认字体可能是Arial在某些系统上中文渲染有问题。解决在创建图元后显式设置字体R1.Font.Name : Microsoft YaHei; R1.Font.Charset : GB2312_CHARSET; R1.Font.Size : 9;如果图元数量多建议在TDiagram级别设默认字体而不是每个图元单独设。Diagram1.DefaultFont属性可以统一控制。5.4 保存后再加载连接线位置偏移现象保存前连线正常加载后连线锚点跑到图元外面去了。原因图元尺寸在加载后被改变但连接线的锚点位置没有重新计算。常见于加载后做了 ValidateAfterLoad 之类的尺寸修正。解决在修改图元尺寸后调用Conn.UpdateAnchors或者直接Diagram1.Invalidate强制重算。更稳妥的做法是把尺寸修正放在加载前或者修正后遍历所有连接线重新赋值 Source 和 Target。5.5 打印时图元被截断或分页错乱现象屏幕上完整的图打印出来右边或下边被切掉。原因打印机的可打印区域比纸张尺寸小而FitToPages计算时用的是纸张尺寸。解决把页边距设大一点或者用Print.Scale手动调缩放比例。如果图本身不大直接设PagesCount : 1并适当减小Scale比自动分页可控。6. 进阶技巧用图库模板和自定义图元类把复用做到位6.1 图库面板的模板定义与拖放TDiagramGallery组件可以加载预置的图元模板用户从图库拖到画布上就能创建对应图元。模板定义可以写在代码里也可以存成文件。我一般会在 FormCreate 里注册业务常用的几种图元procedure TForm1.RegisterGalleryTemplates; var Item: TDiagramGalleryItem; begin DiagramGallery1.Clear; Item : DiagramGallery1.Items.Add; Item.Caption : 开始节点; Item.ShapeType : stRectangle; Item.DefaultWidth : 100; Item.DefaultHeight : 50; Item.Data.Values[NodeType] : Start; Item : DiagramGallery1.Items.Add; Item.Caption : 处理节点; Item.ShapeType : stRectangle; Item.DefaultWidth : 120; Item.DefaultHeight : 60; Item.Data.Values[NodeType] : Process; Item : DiagramGallery1.Items.Add; Item.Caption : 判断节点; Item.ShapeType : stDiamond; Item.DefaultWidth : 100; Item.DefaultHeight : 80; Item.Data.Values[NodeType] : Decision; end;ShapeType决定了拖出来的图元形状Data里的键值对会复制到新创建的图元上。这样用户在画布上拖一个「判断节点」代码里通过Data.Values[NodeType]就能知道这是判断分支不用去判断形状。6.2 自定义图元类的注册与使用如果预置形状不够用可以继承TDiagramShape写自己的图元类。关键是要在initialization里注册type TMyCustomShape class(TDiagramShape) private FStatus: string; published property Status: string read FStatus write FStatus; end; initialization RegisterClass(TMyCustomShape);注册后序列化时才能正确识别这个类。不注册的话保存再加载会变成基类自定义属性全丢。这个坑很隐蔽因为保存时不报错加载后才发现属性没了。6.3 一个验证序列化完整性的小工具交付前我习惯写一个自检方法把画布上的图元数量、连接线数量、关键属性做一次快照保存加载后再比对function TForm1.SnapshotDiagram: string; var I: Integer; Obj: TDiagramObject; begin Result : ; for I : 0 to Diagram1.ObjectCount - 1 do begin Obj : Diagram1.Objects[I]; Result : Result Obj.ClassName | Obj.Data.Values[BizId] | IntToStr(Obj.Left) , IntToStr(Obj.Top) #13#10; end; end;保存前调一次加载后再调一次两个字符串比对。不一致就说明序列化链路有问题。这个方法帮我抓过好几次属性丢失的 bug比肉眼检查靠谱得多。这套控件在 Delphi 13 下的表现整体稳定但版本适配和路径配置这两块每次换环境都要重新过一遍。我的习惯是装完后先跑一个最小示例确认图元创建、保存、加载、打印四个环节都通了再往工程里集成。希望帮到你。本文还有配套的精品资源点击获取