简介:本资源为面向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或类似名字),再编译设计期包。顺序不能反,设计期包依赖运行时包。编译通过后右键设计期包选择 Install,IDE 会提示已注册的组件页,一般是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或ExportToMetafile,Metafile 是矢量格式,放大不糊,适合插入 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 栈,用户按一次 Ctrl+Z 只撤销一个,体验很差。正确做法是用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 下的表现整体稳定,但版本适配和路径配置这两块每次换环境都要重新过一遍。我的习惯是装完后先跑一个最小示例,确认图元创建、保存、加载、打印四个环节都通了,再往工程里集成。希望帮到你。
本文还有配套的精品资源,点击获取