尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
FireMonkey StringGrid点击表头排序:从事件绑定到数据重排的实现
简介Delphi XE FireMonkey FMX 环境下的 StingGridStringGrid表格控件点击表头排序实现资源面向需要为桌面或移动应用增加数据表格交互排序功能的 Delphi 开发者。压缩包共 72 个文件、约 7.71MB包含 pas 源码、dfm/fmx 界面文件、dcu 编译单元、dpr 工程文件及 exe 示例程序等完整覆盖 VCL 与 FMX 两套实现。包内 string_grid_sort_vcl 与 string_grid_sort_fmx 两个工程目录结构清晰可直接打开工程对照学习核心代码 sort_stringgrid.pas、Unit1.pas 演示了列头点击事件捕获、排序方向切换以及与 TClientDataSet 等数据源 SortFields 的结合方式同时提供 Android 部署配置与 Win32 编译产物便于跨平台验证。目前已有 431 人学习下载适合正在使用 Delphi XE 开发数据展示界面、希望快速掌握 FMX 表格排序技巧的中高级开发者参考。1. 点击表头排序为什么 FMX StringGrid 不给你现成的标题里的 StingGrid 其实就是 FireMonkey 的 TStringGrid少一个 r 的拼写在检索里反而常见。做 Delphi XE 桌面工具的人第一次在 FMX 里放 StringGrid 都会下意识找排序属性VCL 里 DBGrid 点表头虽然不是开箱即用但至少还有事件能接FMX 这张表倒好表头点下去毫无反应连个 OnTitleClick 都不给你。这个题目要解决的就是把“点击表头 → 取出排序列 → 升降序切换 → 整行重排”这四件事在 FireMonkey 里手动补完。适合未绑定 DataSource 的手工填表、行数几百到几千的小型管理界面不适合几万行的大数据集。下文按数据模型、事件挂载、排序实现、比较规则、常见坑的顺序展开代码以 Delphi XE 可编译为准。2. FMX StringGrid 的存取模型Cells、RowCount 与 Header想排序先搞清楚你要动的是谁的数据。FMX 的 TStringGrid 和 VCL 的 TStringGrid 除了名字像数据模型完全不同。VCL 里直接操作 StringGrid.Cells[Col, Row] 这种行列二维数组FMX 则把数据按列拆开每一列是一个 TStringColumn 对象里面维护一个字符串数组。这个差异决定了排序时不能直接写行列交换必须绕一层。下面三节把存取、事件、填表三件事分别讲清楚。2.1 TStringColumn.Cells 是数据本体不是界面上那一格在未绑定模式下StringGrid 的数据本体是 TStringColumn.Cells 数组。通过(StringGrid1.Columns[0] as TStringColumn).Cells[2]才能读到第三行第一列的文本而不是直接读什么 Row 对象。这个数组的行数由 StringGrid 的 RowCount 属性决定RowCount 不是“当前显示行数”而是这张表当前维护的总行数默认值是 3。很多人第一次写 Cells 前不设 RowCount结果索引越界或者写进去的值后面一滚动就丢了原因就是没理解 RowCount 才是数组长度。为什么要强调“列优先”因为排序算法习惯按行思考但 FMX 的存储偏偏按列组织。这诱导不少人写出“逐行交换两个 Cell”的代码逻辑上确实能跑但一旦漏掉某一列整行数据就错位了。正确思路是先把 Cells 理解成若干列字符串数组排序时排“行号数组”最后统一重写每一列这样永远不担心漏列。2.2 Header 不是字符串是运行时控件事件该挂在哪TColumn.Header 只是表头显示的文本它不接收鼠标事件。真正渲染表头、响应点击的是一个叫 Header 的运行时对象类型是 THeader。StringGrid 在样式应用阶段才创建 Header所以你在 FormCreate 里写StringGrid1.Header.OnColumnClick : Xxx经常拿到空引用这不是代码写错是对象还不存在。常见做法是放到 FormShow 之后再挂procedure TForm1.FormShow(Sender: TObject); begin // Header 此时已经建好可以安全挂事件 StringGrid1.Header.OnColumnClick : DoHeaderClick; end;FormShow 时样式已经应用过Header 不会是 nil。如果你是在 FormCreate 里动态创建 StringGrid那就要在创建完成后先调用一次 ApplyStyle再挂事件不然还是会踩空。我这边的习惯是坚持在 FormShow 挂简单且不会因为样式重建而丢事件。重复 Show 时同一方法再次赋值不会有副作用事件还是那一个不会叠加。2.3 先填数据再谈排序RowCount、列类型与 BeginUpdate填数据这一步看起来简单但 RowCount 和列类型转换是后面所有排序的地基。下面用 TStringList 做演示数据源NameValue 的格式分别填到两列procedure TForm1.FillGrid(const AData: TStringList); var I: Integer; ColName, ColValue: TStringColumn; begin StringGrid1.BeginUpdate; try // 行数由数据条数决定必须先设置 StringGrid1.RowCount : AData.Count; for I : 0 to AData.Count - 1 do begin ColName : StringGrid1.Columns[0] as TStringColumn; ColValue : StringGrid1.Columns[1] as TStringColumn; if ColName nil then ColName.Cells[I] : AData.Names[I]; if ColValue nil then ColValue.Cells[I] : AData.ValueFromIndex[I]; end; finally StringGrid1.EndUpdate; end; end;RowCount 必须在循环前设置它决定 Cells 数组的可用索引范围。as 转换后要判空因为设计器里某列可能被换成 TProgressColumn 或其他类型直接强转会抛异常。BeginUpdate 不是所有 FMX 版本都有如果你用的 XE 早期版本找不到这个方法删掉这两行也能跑只是行数上千时重绘会明显卡顿。还有一个小技巧循环里反复StringGrid1.Columns[0]会做多次类型转换数据量大时可以把列对象缓存在循环外排序阶段更值得这么做。3. 点击表头排序事件绑定、索引排序与箭头切换数据填好了接下来是把点击表头变成有序表格。FMX 没有 OnSortColumn我们就从 Header 事件自己补。实现分三步绑定事件入口、排行索引数组、按新顺序重写所有列。排序期间用 BeginUpdate 包住避免每写一格就重绘一次。3.1 事件分发入口Header.OnColumnClick 的完整写法先定义排序状态和表头文本缓存。FSortCol 记录当前排序列索引-1 表示从未排序FSortOrder 记录升降序FHeaderTexts 保存每列原始表头文本用来之后画箭头和还原type TSortOrder (soAsc, soDesc); // Form 的 private 区域 FSortCol: Integer; FSortOrder: TSortOrder; FHeaderTexts: array of string; procedure TForm1.FormShow(Sender: TObject); begin StringGrid1.Header.OnColumnClick : DoHeaderClick; InitHeaderTexts; end; procedure TForm1.InitHeaderTexts; var I: Integer; begin SetLength(FHeaderTexts, StringGrid1.ColumnCount); for I : 0 to StringGrid1.ColumnCount - 1 do if StringGrid1.Columns[I] nil then FHeaderTexts[I] : StringGrid1.Columns[I].Header; end; procedure TForm1.DoHeaderClick(Sender: TObject; const Column: TColumn); begin if Column nil then Exit; if FSortCol Column.Index then begin // 同一列切换升降序 if FSortOrder soAsc then FSortOrder : soDesc else FSortOrder : soAsc; end else begin // 新列默认升序 FSortCol : Column.Index; FSortOrder : soAsc; end; SortGrid; UpdateHeaderArrows; end;点击表头空白间隙时 Column 可能是 nil必须先退出。Column.Index 是列索引用它而不是缓存对象指针因为 FMX 表头在样式重建后旧对象可能失效索引更可靠。点击同一列切换方向点击新列重置为升序这是绝大多数表格软件的习惯用户上手没有学习成本。3.2 排序不动单元格只排行索引数组最容易翻车的写法是双层循环交换 Cells——先读排序列的值按大小交换两行再把所有列的对应行一起交换。逻辑上没大错但代码极易漏列漏掉任一列整行就对不上。更稳的做法是排“行索引数组”最后统一重写漏列问题从根上消失procedure TForm1.SortGrid; var Col: TStringColumn; I, J, Tmp: Integer; Idx: array of Integer; begin if (FSortCol 0) or (FSortCol StringGrid1.ColumnCount) then Exit; if not (StringGrid1.Columns[FSortCol] is TStringColumn) then Exit; Col : StringGrid1.Columns[FSortCol] as TStringColumn; // 生成 0..RowCount-1 的行号数组 SetLength(Idx, StringGrid1.RowCount); for I : 0 to StringGrid1.RowCount - 1 do Idx[I] : I; // 插入排序几百行最稳不用写复杂快排 for I : 1 to StringGrid1.RowCount - 1 do begin J : I; while (J 0) and (CompareRows(Idx[J], Idx[J - 1], Col) 0) do begin Tmp : Idx[J]; Idx[J] : Idx[J - 1]; Idx[J - 1] : Tmp; Dec(J); end; end; ApplyOrder(Idx); // 排序完回到首行视觉上更直白 StringGrid1.Row : 0; end;CompareRows 是下一章要给的比较函数这里只需要它返回负、零、正。Idx 数组保存的是“排序后的原行号”比如 Idx[0] 5表示新第一行是旧第五行。插入排序对几百行足够几千行就会明显变慢那就不如换成快速排序或者干脆走数据集排序。RowCount 为 0 时循环自动跳过不会崩。3.3 升降序状态与表头箭头点击同一列切换点新列重置排序后表头如果不给任何提示用户根本不知道当前按哪列排、是升序还是降序。FMX 的 TColumn 没有 VCL 那种排序指示器最省事的做法是改 Header 文本先把所有列还原再给排序列加一个“ ↑”或“ ↓”procedure TForm1.UpdateHeaderArrows; var I: Integer; begin // 先全部还原成原始表头 for I : 0 to StringGrid1.ColumnCount - 1 do if StringGrid1.Columns[I] nil then StringGrid1.Columns[I].Header : FHeaderTexts[I]; // 再给排序列追加箭头 if (FSortCol 0) and (FSortCol StringGrid1.ColumnCount) then begin if FSortOrder soAsc then StringGrid1.Columns[FSortCol].Header : FHeaderTexts[FSortCol] ↑ else StringGrid1.Columns[FSortCol].Header : FHeaderTexts[FSortCol] ↓; end; end;“↑”和“↓”是双字节符号在某些字体下会让列宽多出一点。如果界面把表头文本和列宽卡得很死可以换成“(升序) / (降序)”或者“A / D”效果一样只是没那么直观。FMX 的列宽有时会根据内容自适应动手改 Header 文本后列宽变化属于正常现象不用慌。4. 排序规则文本、数字、中文和空行怎么处理排序列本质上是字符串数组但用户眼里的数据可能是数字、日期、中文。字符串排序“看起来能用”一遇到 10 和 9 就露馅。这一章把比较规则拆开照着写就能避开最常见的排序翻车点。4.1 文本排序Trim 与 CompareStr 的使用普通文本列直接用 Trim 去掉首尾空格后 CompareStr。CompareStr 在 Delphi XE 里按 Unicode 编码逐字符比较速度快、结果稳定。不要拿 Cells 原始值直接比用户输入的空格会导致“abc”和“ abc”排到不同位置体验很差。代码里需要注意用 AnsiCompareStr 会受系统区域设置影响中文系统下近似按拼音排但结果不稳定换一台英文系统顺序就变了。我一般不用它做排序判据。如果产品确实要求拼音排序只能对字符串做拼音转码后额外存一个排序键这属于另一个复杂度不在本标题的范围内。4.2 数字列排序先把字符串转浮点再比为什么数字列要单独说因为if SA SB then这种写法在字符串比较规则下10 一定排在 9 前面。字符串逐字符比较1 小于 9所以“100”排在“9”之前是必然结果不是玄学。要按数值排就得先把文本转成 Double。下面这段比较逻辑既是文本排序也是数字排序的统一入口function TForm1.CompareRows(A, B: Integer; Col: TStringColumn): Integer; var SA, SB: string; VA, VB: Double; NumA, NumB: Boolean; begin SA : Trim(Col.Cells[A]); SB : Trim(Col.Cells[B]); // 空行永远沉底不受升/降序影响 if (SA ) and (SB ) then Exit(0); if SA then Exit(1); if SB then Exit(-1); // 两列都能转数字按数值比 NumA : TryStrToFloat(SA, VA); NumB : TryStrToFloat(SB, VB); if NumA and NumB then begin if VA VB then Result : -1 else if VA VB then Result : 1 else Result : 0; end else Result : CompareStr(SA, SB); // 降序在此统一翻转 if FSortOrder soDesc then Result : -Result; end;空行分支放在方向翻转之前目的是让空行在升序和降序时都固定在最后。如果你希望空行排最前把那两个 Exit 的返回值对调再挪到方向翻转之后处理但绝大多数表格场景都是“空数据不许插队”。TryStrToFloat 使用系统区域设置中文系统默认小数点没问题如果程序会部署到欧洲等用逗号做小数点的环境建议提前创建一个固定的 TFormatSettings把 DecimalSeparator 设成 . 再传入 TryStrToFloat否则排序在不同机器上会得到不同结果。4.3 中文排序不追求拼音要追求稳定中文字符串排序是入门者最容易纠结的点。CompareStr 对中文按 Unicode 码点排序也就是“一”和“聿”的先后看起来没什么规律但至少结果稳定。对多数内网工具、管理后台、数据导出界面来说用户真正在意的是点一下表头能变顺序、再点一下能反过来而不是中文必须按拼音排。如果硬要拼音序字符串本身不带读音信息必须引入拼音库或预先存拼音列这已经超出“点击表头排序”的范畴。我的原则是所有比较固定成同一种规则宁可排序结果“看起来没规律”也不要不同机器上顺序不一致。稳定比玄学重要。5. FMX 表头排序常见问题与排查从事件不触发到数据错行这一章写给正在改代码但排不出来的读者。每个问题都是我在实际代码里见过或排查过的按“现象 → 原因 → 解决”写对照自己的代码逐条查。5.1 Header 事件一直不触发挂载时机和 Header 重建现象按前面代码写了 FormShow 挂事件点击表头没任何反应断点都不进。原因有两种。一是 Header 对象在挂载时还没创建完事件挂到了一个旧引用或者空引用上点击自然不触发。二是运行时动态增删列、切换样式主题触发了 Header 重建旧的事件丢失。解决把挂事件封装成一个独立过程FormShow 里调用每次动态增删列后也调用一次。增删列后重新绑定不会重复触发因为赋值同一个方法只会覆盖不会叠加。如果还不放心在事件处理函数里临时加一句弹窗或 Output 输出确认到底有没有进去。5.2 点击 A 列却排了 B 列Index 与 Columns 的对应关系现象点“日期”列结果数据按“数量”列排好了或者排序列完全对不上。原因Header 事件里的 Column 是 Header 内部管理的列对象有的 FMX 版本它和 Grid.Columns 里的对象是同一批有的版本只是索引相同、对象不同。如果运行期隐藏列、删除列后再添加索引对应关系就可能错位。解决处理函数里不要直接用传入的 Column 去读 Cells而是用StringGrid1.Columns[Column.Index]重新取列对象。同时打印一下 Column.Index 和 StringGrid1.ColumnCount确认索引范围没有越界。这个习惯能帮你快速定位到底是事件问题还是索引问题。5.3 排序后整行错位只重写了排序列现象第一列按字母排对了后面的“数量”“备注”列还是原来的顺序整行数据被拆开了。原因排序时只把排序列的 Cells 按新顺序重写其他列没有跟着动。这是最典型的漏列问题也是为什么前面反复强调“排索引数组最后统一重写所有列”。解决写一个 ApplyOrder 过程遍历 StringGrid 的所有列每一列都按 Idx 数组重写 Cells。哪怕某一列不是 TStringColumn 也要跳过而不是直接跳过整列数据。这样不管后面加了几列排序都能把整行一起带走。5.4 绑定了 DataSource 时改 Cells 无效现象表格绑定了 TDataSource 或者活绑定数据排序时改 Cells 没效果滚动一下又恢复原样。原因绑定模式下显示内容由数据集和绑定系统控制Cells 数组不一定是数据源头手工改写只会改到界面层数据一变就“打回原形”。解决绑定模式不要走 Cells 排序。正确做法是排底层数据集换 ClientDataSet 并设置 IndexName或者重新执行带 ORDER BY 的查询。如果项目确实需要在内存里排序后再塞回表格把数据集的记录先读到一个临时数组解除绑定或改用未绑定模式再按前面 FillGrid 的方式重填。这个标题的所有代码都是针对未绑定模式先确认你的表格模式再动手。5.5 动态增删列后数组越界、箭头错乱现象运行期用 AddObject 加了列或者删了一列再点表头就报数组越界或者表头箭头指向错误列。原因FSortCol 和 FHeaderTexts 是静态维护的列数变了索引数组没有同步。删除排序列后 FSortCol 仍指向旧索引更新箭头时自然出错。解决增删列后重新调用 InitHeaderTexts重建 FHeaderTexts。删除列时如果被删的正好是排序列把 FSortCol 重置为 -1清掉箭头。动态列多的界面建议在每次列集合变化后统一走一遍“重置排序状态”的过程不要靠人工记得改索引。6. 把排序稳定下来一个可复用流程与两个验证习惯到这里核心代码已经齐了最后给一个能直接用的流程清单以及我自己的验证习惯。不建议每次都从零开始写 FMX 排序。我一般按下面十步走每完成一步就检查一次能省掉大半排查时间确认 StringGrid 没有绑定 DataSource绑定模式必须走数据集排序。填数据前先设 RowCount再用 TStringColumn.Cells 填充。FormShow 里挂 Header.OnColumnClick。保存当前所有列原始表头到 FHeaderTexts。点击表头时更新 FSortCol 和 FSortOrder。生成 0 到 RowCount-1 的行索引数组。用统一的 CompareRows 对索引数组排序。遍历所有列按排序后的索引重写 Cells。更新表头箭头排序列加 ↑ 或 ↓。排序完成后把 StringGrid.Row 设回 0。验证习惯上我会准备两批测试数据。第一批是 10 行混合数据里面必须有空行、重复值、纯数字和纯文本连续点击 A 列升序、降序再切到 B 列最后切回 A 列观察空行是否一直垫底、重复值是否稳定、升降序是否正确翻转。第二批是运行期动态删掉排序列、再加一个新列然后点击新表头确认不报数组越界、箭头不乱指。这两个用例基本覆盖了 FMX 表头排序八成以上的坑。我自己在 FMX 项目里被 Header 挂载时机坑过不止一次后来养成的习惯是一定先确认事件有没有进、Column.Index 是不是自己想的那个再碰排序数据。排序算法本身不会骗人会骗人的往往是拿错了列、挂错了事件、写漏了某列数据。按这个流程走希望你不用重走我这些弯路希望帮到你。本文还有配套的精品资源点击获取
RELATED

相关推荐

Java并发常见问题复盘:可见性、锁、线程池与排查实战

Java并发常见问题复盘:可见性、锁、线程池与排查实战

并发编程难,难在它不会按照我们写代码时的顺序来执行。我写Java这七八年里,真正让我觉得“自己敢碰并发”的,不是背了哪本面试题,而是线上事故一个一个踩出来的。今天这篇所谓“Java 并发常见问题总结”,我更愿意把它当…

📅 2026/10/13 19:23:01
SSM框架社区信息管理系统毕设全流程实战指南

SSM框架社区信息管理系统毕设全流程实战指南

每年毕业设计季,SSM框架的“社区信息管理系统”都是出镜率极高的一个题目。很多人觉得它“太土”“太基础”,但换个角度看,这个题目恰恰是最适合用来稳过毕设的那一类:技术栈经典、需求明确、工作量可控,论文又能围绕完…

📅 2026/10/13 19:23:01
Codex插件实战:12个提升开发效率的配置方案与避坑指南

Codex插件实战:12个提升开发效率的配置方案与避坑指南

最近一直在折腾Codex的插件机制,前后试了三十多个插件,最后沉淀下来十二个真正每天都在用的。这篇文章直接把这十二个插件按使用场景拆开讲,包括每个插件解决什么问题、TOML配置怎么写、踩过哪些坑,全部是基于真实使用经验的总结。…

📅 2026/10/13 19:23:01
MORE NEWS

更多资讯

📰

Windows 连接 SAP HANA 最小驱动方案:ODBC 链路配置与排错指南

简介:这是面向SAP HANA数据库的64位Windows客户端安装包,内含ODBC驱动,适合需要在Windows平台上连接SAP HANA的开发、运维及数据分析人员,用于解决驱动安装、数据源配置和多语言接口连接等常见问题。整个压缩包共六十四个文件&…

📰

LiteLLM统一模型网关实战:解决多模型接入与治理难题

多数接触大模型应用开发的团队,迟早都会遇到同一个坎:模型服务商不止一家,调用格式五花八门,切换一个模型几乎要重写一遍接入层代码。我自己在做一个多模型模拟项目X的时候就被这个问题卡过很久,后来用litellm把整个调…

📰

《数据库原理与应用(第四版)》实操指南:从SQL验证到生产调优

简介:本资源是《数据库原理与应用(第四版)》核心知识点精要整理PDF,面向计算机专业本科生、数据库初学者及备考人员,系统解决数据库基础概念理解难、知识体系零散、应试要点不清晰等痛点。文件共1个PDF,大小…

📰

Litellm实战:用开源API网关统一大模型接入与治理

1. 先说说我为什么盯上了litellm:多模型接入的混乱必须治过去两个月我们团队手上有三条AI业务线在并行推进,每一条连接的模型来源都不一样。第一条业务线用的是某头部云厂商的闭源模型接口,第二条业务线接了一个开源模型的私有化部署&#xf…

📰

用LiteLLM构建统一LLM API网关:模型路由与成本追踪实战

1. 为什么我在项目里选择了litellm 先聊个实际场景。做过AI应用开发的兄弟几乎都经历过这个阶段:最开始接OpenAI的API,跑通了,效果不错;过两天想对比一下其他家模型的效果,发现要改一堆代码;再往后&#xf…

📰

达梦数据库TPCC基准测试:BenchmarkSQL 5.0改版实战指南

简介:这是一份面向数据库管理员、测试工程师与IT选型人员的BenchmarkSQL达梦适配版工具包,用于对达梦数据库开展标准化基准测试,评估其在高并发事务场景下的TPS、响应时间等性能指标,并可与Oracle、MySQL、PostgreSQL等主流数据库…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬