简介:本资源为TMS FNC UI Pack v7.1.1.0完整源码包,面向使用Delphi与C++ Builder进行跨平台界面开发的程序员,覆盖XE7至13 Florence版本。控件包提供现代化、触摸友好的UI组件,支持Windows、Mac、Linux、iOS与Android多平台,适合希望快速构建响应式应用界面的中高级开发者。压缩包共约2000个文件,体积31.09MB,以591个pas源码单元、158个dpr工程文件、88个dfm窗体、67个fmx跨平台窗体及82个dcr组件资源为主,另含dproj、res、ico、svg、png等工程与素材文件,并附带html文档、csv数据与示例图片,便于查阅与二次开发。目前已有56人学习下载。完整源代码开放意味着开发者可自由修改、扩展组件样式与动画效果,结合官方文档能显著提升界面开发效率,降低调试成本,长期项目亦可获得持续更新支持。
1. TMS FNC UI Pack 在 Delphi 13.1 里到底解决了什么问题
如果你正在用 Delphi 13 Florence 做跨平台项目,大概率遇到过这种局面:VCL 一套控件、FMX 一套控件,同一份业务逻辑要在两套 UI 里各写一遍,界面风格还统一不了。TMS FNC UI Pack 就是冲着这个痛点来的——它是一套基于 FNC(Framework Neutral Components)架构的 UI 控件库,同一份控件代码可以同时跑在 VCL、FMX 甚至 Web 端,Delphi 13.1 和 C++Builder XE7 到 13 Florence 都在支持范围内。标题里的 Full Source 意味着你拿到的是完整源码版,不是只有 DCU 的编译版,这对需要改控件内部行为、做深度定制的团队来说差别很大。
这套包覆盖的控件类型很广:网格、树、列表、工具栏、日程、图表、编辑器等,日常业务系统里能想到的 UI 组件基本都有对应实现。它适合两类人:一是手上有多端发布需求、不想维护两套 UI 代码的团队;二是需要读源码、改源码来解决特定渲染或交互问题的资深开发者。如果你只是做单平台小工具,用原生控件可能更省事,但一旦涉及多端和深度定制,FNC 这套架构的价值就出来了。
2. FNC 架构的跨框架原理与工程结构拆解
2.1 FNC 为什么能做到一份代码多端渲染
FNC 的核心思路是把控件的逻辑层和渲染层彻底分开。逻辑层处理数据、状态、事件,渲染层负责把控件画到具体平台上。VCL 下它调用 Windows GDI/GDI+,FMX 下走 FireMonkey 的 Canvas 抽象,Web 端则输出 HTML/CSS/JS。你在代码里操作的TTMSFNCGrid或TTMSFNCTreeView,在不同框架下是同一个类名、同一套属性方法,编译器根据当前框架选择对应的后端单元。
这种设计带来的直接好处是:业务代码里不需要写{$IFDEF VCL}和{$IFDEF FMX}的分支,控件属性设置、事件绑定、数据填充的写法完全一致。代价是 FNC 控件不会像原生控件那样在每个平台上都做到像素级原生外观,它走的是自绘路线,风格统一但和系统原生控件有视觉差异。这一点在选型时要提前想清楚:你要的是开发效率还是原生观感。
从工程结构看,安装后源码通常分布在几个关键目录:FMX目录放 FireMonkey 后端,VCL目录放 VCL 后端,Common或Core目录放跨框架共享的逻辑单元,Packages目录放各版本的包文件。理解这个结构很重要,因为后面你要改源码或排查编译错误时,得知道该去哪个目录找对应单元。
2.2 在 Delphi 13.1 里安装 Full Source 版的完整步骤
Full Source 版和编译版的安装流程不一样,编译版直接装包就行,源码版需要先把源码路径加入搜索路径,再编译包。下面是我在 Delphi 13 Florence 上的实际操作步骤。
第一步,解压后先确认目录结构。通常根目录下会有Source、Packages、Demos几个文件夹。不要急着打开 IDE,先在文件管理器里确认Source下确实有.pas源文件,而不是只有.dcu。
第二步,在 Delphi 里打开包文件。Delphi 13 对应的包文件一般在Packages下按版本分目录,找到Delphi 13或Delphi13目录,里面会有TMSFNCUIpackDXE13.dpk之类的设计期包和对应的运行期包。先打开运行期包(Runtime Package),编译,再安装设计期包(Design Package)。
// 安装后在代码里引用 FNC 控件的典型 uses 写法 uses FMX.TMSFNCGrid, // FMX 下的 FNC 网格 VCL.TMSFNCGrid, // VCL 下的 FNC 网格(按框架二选一) TMSFNCGridData, // 跨框架共享的数据单元 TMSFNCUtils; // 工具函数单元这里要注意单元命名规律:框架相关的单元带FMX.或VCL.前缀,跨框架共享的单元没有前缀。写代码时尽量只引用共享单元和当前框架的单元,不要同时引用两个框架的版本,否则会出现重复定义。
第三步,把Source目录加入 IDE 的库搜索路径。菜单Tools→Options→Language→Delphi→Library,在Library Path里加上源码根目录和必要的子目录。这一步不做的话,编译时找不到.pas文件,只能找到预编译的.dcu,那就失去 Full Source 的意义了。
第四步,编译一个 Demo 验证。Demos目录下通常有按框架分类的示例项目,打开一个 VCL 的、一个 FMX 的,分别编译运行。如果两个都能跑起来,说明包安装和路径配置都对了。
提示:安装前先备份 IDE 的库路径配置,源码版安装涉及路径修改,出问题回滚会方便很多。
2.3 包文件与搜索路径的对应关系
很多人装源码版翻车,不是包编译不过,而是搜索路径没配对,导致 IDE 里能看到控件但一编译就报找不到单元。下面这张表是我整理的关键目录和用途对应关系,按这个核对基本不会漏。
| 目录 | 内容 | 是否加入搜索路径 |
|---|---|---|
| Source\Common | 跨框架共享逻辑单元 | 是 |
| Source\VCL | VCL 后端单元 | 是(VCL 项目) |
| Source\FMX | FMX 后端单元 | 是(FMX 项目) |
| Packages\Delphi13 | 各版本 dpk 包文件 | 否(通过 IDE 打开安装) |
| Demos | 示例项目 | 否 |
路径加多了也会出问题,比如同时把 VCL 和 FMX 目录加进去,某些同名单元会冲突。我的习惯是按项目类型分开配置:做 VCL 项目时只加 Common 和 VCL,做 FMX 时只加 Common 和 FMX。如果团队同时维护两种项目,可以用 IDE 的Build Configuration或不同 IDE 实例来隔离。
3. 用 FNC 控件搭一个可复用的数据管理界面
3.1 从零建一个带树形导航和网格的 VCL 窗口
这一节用一个具体场景把 FNC 控件串起来:左边树形导航,右边数据网格,顶部工具栏,这是业务系统里最常见的布局。用 FNC 的TTMSFNCTreeView、TTMSFNCGrid、TTMSFNCToolBar来实现,重点是让这套代码之后能低成本迁移到 FMX。
先建一个 VCL 项目,在窗体上放三个 FNC 控件。注意 FNC 控件在 Tool Palette 里的分类通常是TMS FNC开头的页签,找不到的话检查设计期包是否装好。
procedure TFormMain.FormCreate(Sender: TObject); begin // 初始化树形导航 TMSFNCTreeView1.DefaultItemType := itText; TMSFNCTreeView1.SelectionMode := smSingle; // 初始化网格列 TMSFNCGrid1.ColumnCount := 4; TMSFNCGrid1.Cells[0, 0] := '编号'; TMSFNCGrid1.Cells[1, 0] := '名称'; TMSFNCGrid1.Cells[2, 0] := '状态'; TMSFNCGrid1.Cells[3, 0] := '更新时间'; // 固定表头行 TMSFNCGrid1.FixedRows := 1; // 工具栏按钮 TMSFNCToolBar1.AddButton('新增'); TMSFNCToolBar1.AddButton('编辑'); TMSFNCToolBar1.AddButton('删除'); end;这段代码里几个参数值得说明。DefaultItemType决定树节点的默认类型,itText是纯文本节点,如果要带复选框就改成itCheckBox。FixedRows := 1把第一行固定为表头,滚动时表头不动。SelectionMode控制选择行为,smSingle是单选,多选用smMulti。工具栏的AddButton是简化写法,实际项目里通常用Items.Add配合TMSFNCToolBarButtonItem来设置图标和事件。
3.2 树节点与网格数据的联动绑定
光有界面不够,关键是树节点选中后网格要跟着刷新。FNC 控件的事件模型和原生控件类似,但要注意跨框架时事件参数类型可能不同。
procedure TFormMain.TMSFNCTreeView1SelectionChanged(Sender: TObject); var Node: TTMSFNCTreeViewNode; begin Node := TMSFNCTreeView1.SelectedNode; if Node = nil then Exit; // 根据选中节点刷新网格数据 LoadGridData(Node.Text); end; procedure TFormMain.LoadGridData(const CategoryName: string); begin TMSFNCGrid1.BeginUpdate; // 批量更新前挂起重绘,避免闪烁 try TMSFNCGrid1.RowCount := 1; // 保留表头行 // 这里接你的数据源,示例用模拟数据 AddGridRow('001', CategoryName + '-项目A', '启用', '2025-01-10'); AddGridRow('002', CategoryName + '-项目B', '停用', '2025-01-11'); finally TMSFNCGrid1.EndUpdate; end; end; procedure TFormMain.AddGridRow(const A, B, C, D: string); var R: Integer; begin R := TMSFNCGrid1.RowCount; TMSFNCGrid1.RowCount := R + 1; TMSFNCGrid1.Cells[0, R] := A; TMSFNCGrid1.Cells[1, R] := B; TMSFNCGrid1.Cells[2, R] := C; TMSFNCGrid1.Cells[3, R] := D; end;BeginUpdate和EndUpdate这对调用是 FNC 网格性能优化的关键。数据量大时如果不挂起重绘,每加一行都会触发一次界面刷新,几百行下来肉眼可见地卡。RowCount的增减会自动管理行对象,不需要手动创建销毁。SelectedNode在无选中时返回nil,必须先判空再访问,这是血泪经验,早期版本里直接访问会抛异常。
3.3 把 VCL 版本迁移到 FMX 需要改什么
FNC 的卖点就是迁移成本低,但不是零成本。把上面这个 VCL 项目改成 FMX,主要改三处:单元引用、控件实例类型、以及少量平台相关的属性。
单元引用从VCL.TMSFNCGrid改成FMX.TMSFNCGrid,VCL.TMSFNCTreeView改成FMX.TMSFNCTreeView。控件实例的类名不变,还是TTMSFNCGrid,但声明所在的单元变了。属性方法层面,绝大多数是通用的,少数涉及窗口句柄、字体渲染、鼠标事件坐标的会有差异。
// FMX 版本中获取鼠标位置的写法差异 // VCL 下常用: // P := TMSFNCGrid1.ScreenToClient(Mouse.CursorPos); // FMX 下应改为: P := TMSFNCGrid1.ScreenToLocal(TMSFNCGrid1.Scene.LocalToScreen(PointF(0, 0)));这类坐标转换的差异是迁移时最容易踩的坑,因为编译器不会报错,但运行时位置全错。我的做法是迁移前先把项目里所有涉及屏幕坐标、句柄、Canvas 直接操作的代码列出来,逐个对照 FNC 的跨框架 API 改。FNC 提供了TMSFNCUtils单元,里面有不少跨框架的辅助函数,优先用这些而不是自己写平台分支。
4. 源码版定制与编译期常见问题排查
4.1 修改 FNC 源码后如何避免被包覆盖
Full Source 版最大的价值是能改源码,但改完源码后如果直接重新编译安装包,你的修改可能被覆盖。正确做法是:把要改的单元从源码目录复制到项目自己的目录,在项目搜索路径里把项目目录排在源码目录前面,这样编译器优先用你改过的版本。
# 项目目录结构建议 MyProject/ Source/ Overrides/ # 放你改过的 FNC 单元 TMSFNCGrid.pas Lib/ # 编译输出搜索路径顺序:MyProject\Source\Overrides在前,TMSFNCUIpack\Source\Common在后。这样只有你覆盖的单元用改过的版本,其余仍用官方源码。升级 FNC 版本时,对比 Overrides 里的文件和官方新版的差异,手动合并,不要直接覆盖。
注意:改 FNC 源码前先确认许可证允许。Full Source 版通常允许修改自用,但再分发有限制,具体看授权条款。
4.2 编译报错「Unit not found」的四种排查方向
这个报错在源码版安装里出现频率最高,原因通常不出四种。第一种,搜索路径没加或加错,检查 IDE 库路径里是否有源码根目录。第二种,包没编译成功,设计期包依赖运行期包,运行期包没编译过,设计期包也装不上。第三种,同名单元冲突,比如你项目里有个TMSFNCUtils.pas和官方的重名,编译器不知道用哪个。第四种,Delphi 版本对应的包文件选错了,XE7 和 13 的包不通用。
排查顺序建议从简到繁:先看路径,再看包编译输出,再看单元名冲突,最后确认版本匹配。包编译时把输出窗口的警告也看一下,有些警告其实是致命问题的前兆。
4.3 运行时控件不显示或显示异常的排查
编译过了但控件在窗体上不显示,或者显示成一块空白,这类问题更隐蔽。常见原因:一是 FNC 控件的Parent没设置,动态创建的控件必须指定父容器;二是Visible属性被意外置为False;三是 FMX 下控件的Align和Position冲突,导致尺寸算出来是零;四是样式文件没加载,FNC 某些控件依赖样式资源。
// 动态创建 FNC 控件时的正确写法 var Grid: TTMSFNCGrid; begin Grid := TTMSFNCGrid.Create(Self); Grid.Parent := Self; // 必须设置父容器 Grid.Align := TAlignLayout.Client; Grid.Visible := True; // 显式确认可见 Grid.RowCount := 10; Grid.ColumnCount := 5; end;如果按这个写法还是不显示,检查窗体的OnCreate是否在控件创建之前就抛了异常导致后续代码没执行。用断点跟一下创建流程,比盯着属性面板猜要快得多。
5. 避坑记录:源码版安装与多端开发的五个真实翻车点
现象一:装完包后 Tool Palette 里找不到 FNC 控件。原因通常是只编译了运行期包,没安装设计期包。设计期包负责向 IDE 注册控件图标和属性编辑器,不装它控件就不会出现在面板上。解决方法是确认Packages目录下带Dcl前缀的包也编译并安装了。
现象二:VCL 项目编译通过,切到 FMX 项目报大量重复定义。原因是库搜索路径里同时存在 VCL 和 FMX 的源码目录,同名单元被重复找到。解决方法是按项目类型隔离搜索路径,或者用 IDE 的Build Configuration为不同平台配置不同的库路径。
现象三:改了源码里的绘制逻辑,运行时没生效。原因是编译器用了预编译的.dcu而不是你改过的.pas。Delphi 的编译策略是如果.dcu比.pas新,就用.dcu。解决方法是删除对应的.dcu文件,或者把项目目录的搜索路径排在源码目录前面,强制从源码编译。
现象四:FMX 下网格滚动卡顿,VCL 下却流畅。原因是 FMX 的渲染管线对频繁的 Canvas 操作更敏感,FNC 网格在 FMX 下默认的重绘策略需要调整。解决方法是开启BeginUpdate/EndUpdate批量更新,并适当增大ScrollUpdateInterval之类的刷新间隔参数,减少每帧的重绘次数。
现象五:升级 Delphi 小版本后 FNC 包编译报错。原因是 FNC 的包文件按 Delphi 版本区分,13.0 和 13.1 的包文件可能不通用,RTL 单元有变动。解决方法是找 FNC 官方对应新版本的包文件,或者用源码目录里的.dpk重新编译,不要直接复用旧版本的包。
6. 用条件编译和单元覆盖把 FNC 项目做成可长期维护的形态
前面讲的都是单点操作,这一节说一个能让你少返工的工程习惯:用条件编译配合单元覆盖,把 FNC 项目的多端差异收敛到最小范围。
核心思路是建一个Platform单元,所有平台相关的差异都封装在这里,业务代码只调这个单元暴露的统一接口。FNC 本身已经做了大量跨框架抽象,但总有一些边角需要你自己处理,比如文件路径分隔符、剪贴板操作、消息框样式。把这些集中到一个单元,比散落在各处写{$IFDEF}要好维护得多。
unit MyProject.Platform; interface type TPlatformHelper = class public class function GetConfigPath: string; class procedure ShowMessage(const Msg: string); class function ClipboardText: string; end; implementation {$IFDEF MSWINDOWS} uses Winapi.Windows, Vcl.Dialogs, Vcl.Clipbrd; class function TPlatformHelper.GetConfigPath: string; begin Result := ExtractFilePath(ParamStr(0)) + 'config\'; end; class procedure TPlatformHelper.ShowMessage(const Msg: string); begin Vcl.Dialogs.ShowMessage(Msg); end; class function TPlatformHelper.ClipboardText: string; begin Result := Vcl.Clipbrd.Clipboard.AsText; end; {$ENDIF} {$IFDEF MACOS} uses FMX.Dialogs, FMX.Platform; class function TPlatformHelper.GetConfigPath: string; begin Result := TPath.GetHomePath + '/.myproject/'; end; class procedure TPlatformHelper.ShowMessage(const Msg: string); begin FMX.Dialogs.ShowMessage(Msg); end; class function TPlatformHelper.ClipboardText: string; var Svc: IFMXClipboardService; begin Result := ''; if TPlatformServices.Current.SupportsPlatformService(IFMXClipboardService, Svc) then Result := Svc.GetClipboard.AsText; end; {$ENDIF} end.这个单元的价值在于:业务代码里永远只写TPlatformHelper.GetConfigPath,不关心底层是 Windows 还是 macOS。新增平台时只改这一个文件,不动业务逻辑。FNC 控件的使用也是同理,尽量用 FNC 提供的跨框架 API,实在没有的再走这个 Platform 单元兜底。
验证这套结构是否有效,有个简单方法:把项目从 VCL 切到 FMX 编译,如果报错只集中在 Platform 单元和少数 UI 初始化代码,说明抽象层做得对;如果业务单元里到处报错,说明平台相关代码泄漏了,需要继续收敛。
我自己的习惯是每接一个新平台,先花半天把 Platform 单元写扎实,后面能省掉大量「这个属性在另一个平台上叫什么」的翻查时间。FNC 已经把大部分脏活干了,剩下这点收尾工作做好,多端项目的维护成本才能真正降下来。希望帮到你。
本文还有配套的精品资源,点击获取