ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Delphi中XLSReadWriteII控件安装配置与Excel读写实战指南

Delphi中XLSReadWriteII控件安装配置与Excel读写实战指南

简介:XLSReadWriteII v6.01.17 控件包面向 Delphi 12.3 环境下的桌面应用开发者,用于在程序中高效读写 Excel 文件,解决报表导出、数据导入及复杂表格处理等问题。资源共 499 个文件,压缩后约 2.15MB,以 Pascal 源码文件为主,附带大量 HTML 帮助文档、DFM 窗体定义、DPR 工程文件以及示例数据与配置文件;Pascal 源码便于查看控件实现细节,HTML 文档提供接口参考,DFM 与 DPR 则展示了可视化窗体设计和工程组织方式。包内演示样例涵盖创建图表、自动筛选、发票模板、数据监控、交互式与高级透视表等典型业务场景,方便开发者对照学习各功能的调用方式,也能直接移植到自己的项目里。示例均基于实际工程整理,目录结构清晰,便于按功能模块快速检索。目前已有 65 人学习下载,适合需要为 Delphi 应用程序集成 Excel 读写能力的中高级开发人员收藏参考。

1. 为什么我把 XLSReadWriteII 当 Excel 读写的非买不可控件

读过 Excel 文件的 Delphi 开发者大多经历过这种时刻:OLE 自动化在装有精简版 Office 的服务器上突然失去响应,后台进程越杀越多,报表卡在进度条上。我在一个跨平台报表模块里被折腾到翻车之后,换成了 XLSReadWriteII。它不是用 COM 把 Excel 拉起来,而是在 VCL 线程里直接解析 xlsx/xls 文件流,运行时不依赖 Excel 是否安装。这份 v6.01.17 D12 压缩包正是针对 Delphi 12.3 编译的控件包,自带可打开复现的样例工程。适合写报表导出、数据导入、模板填充,或需要后端批量生成 Excel 文件的团队使用。

2. 安装 D12 包:把 cfg 样例工程跑起来的两个前提

拿到压缩包后第一件事不是翻源码,而是先明确你是打算做静态编译还是动态 BPL 分发。和我一样把控件当作唯一报表核心组件的人,一般都会选择静态连接方式:编译运行时包时不勾选 Build with runtime packages,然后把设计期包装进 IDE 的面板。这个决定直接影响后面所有样例题是否能正常打开。

2.1 包组与库路径:先决定静态连接还是 BPL 运行

打开 Delphi 12.3 的包管理器,能看到两类包:设计期包和运行时包。设计期包负责在组件面板里显示控件,运行时包负责提供实际单元。常见做法是先把运行时包编译一遍,再单独安装设计期包。注意,如果你机器上同时装了 DevExpress 之类的大型控件套件,不要把 XLSReadWriteII 的包并进同一个运行时包组,否则日后的组件升级会互相牵连,出现“包依赖版本不匹配”的玄学报错。

库路径设置是最容易出错的一步。打开 Tools > Options > Environment Options > Delphi Options > Library,把源码目录按顺序加进 Library Path:

D:\Dev\Components\XLSReadWriteII\Source D:\Dev\Components\XLSReadWriteII\Source\Delphi12 D:\Dev\Components\XLSReadWriteII\XLSGrid D:\Dev\Components\XLSReadWriteII\XLSGrid\Delphi12

这个顺序有讲究。Source 在最前面,因为 Delphi 编译时会按列表顺序搜索.dcu,先找到基础单元,再去子目录找功能单元。如果顺序反了,可能发生XLSRWII.dcu和XLSRWII.pas版本错位的问题。路径里不能有中文或空格,否则 Code Insight 会频繁提示 unit not found。加完路径后重启 IDE,在 Component > Install Packages 里勾选新装的运行时包。这里常见的坑是旧版本留下的.dcu缓存没有被清理,我一般会手动删掉$(BDSCOMMONDIR)\Bpl下所有 XLSReadWriteII 开头的 BPL 文件,同时清掉Source目录里的.dcu再重新编译一次。

2.2 cfg 文件是工程配置,不是控件配置文件

压缩包里有一批.cfg文件,比如CreateChartsSample.cfg、InvoiceSample.cfg、SampleAutoFilter.cfg。第一次接触这套控件的人容易误以为这是运行时配置,其实这些文件是 Delphi 的工程配置文件,等价于.dpr旁边的.cfg。IDE 打开对应样例工程时,会读取它记录的条件编译符号、搜索路径和编译目标。v6.01.17 给 D12 系列项目带的样例大多是 32/64 位双目标,所以根目录里会同时看到这些文件。

用一个简化后的片段来说明它的作用:

[Compiler] Project=CreateChartsSample UnitOutputDir=.\Win32\Debug Conditionals=XLSD6_LICENSE;DEBUG [Dirs] Source_Dirs=D:\Dev\Components\XLSReadWriteII\Source;D:\Dev\Components\XLSReadWriteII\Source\Delphi12

Source_Dirs指定编译演示工程时所需的库单元路径,Conditionals里的XLSD6_LICENSE是关键的授权开关。你在自己新建项目时不要照抄这个 cfg,因为里面是绝对路径,换一台机器大概率会提示找不到.dcu。正确做法是把 Source_Dirs 换成相对路径,或者干脆忽略 cfg,在.dproj的 Search Path 里写同样内容。直接把 cfg 复制到新机器还保持绝对路径不变,是最常见的翻车原因之一,现象是工程管理器里很多单元标红,编译时报File not found: 'XLSRWII.dcu'。

2.3 跑通最简验证:拖一个 TXLSReadWriteII 读 Sheet

安装完成后,新建一个 VCL 应用程序,在组件面板里把TXLSReadWriteII拖到窗体上。很多人到这里发现控件是灰色禁用状态,第一反应是控件损坏。其实常见原因只有两个:一是工程条件编译里没有定义XLSD6_LICENSE,二是设计期包和运行时包版本不一致。前者去 Project > Options > Delphi Compiler > Conditional defines 里加入XLSD6_LICENSE然后重新编译,后者回包管理器确认两个包都选中 D12 版本,不能混着用旧版本的 BPL。

验证读写器的代码通常放在 FormCreate 里:

procedure TForm1.FormCreate(Sender: TObject); var XL: TXLSReadWriteII; Value: string; begin XL := TXLSReadWriteII.Create(nil); try XL.Filename := 'D:\Dev\Test\Demo.xlsx'; XL.ReadFromFile; Value := XL.Sheets[0].Cells[1, 1].AsString; Caption := '第一个单元格: ' + Value; XL.Clear; finally XL.Free; end; end;

TXLSReadWriteII是读写器入口对象,Filename指定目标文件,ReadFromFile触发解析流程。Sheets[0]是第一个工作表,Cells[1, 1]的坐标是列/行,和 Excel 表格里的 A1 对应。出错时先检查文件是否被 Excel 或预览程序锁定,其次确认文件确实是 xlsx 结构而不是改后缀的假文件。如果这段代码能读到字符串,说明包安装与授权条件编译都生效,可以进入样例工程拆解阶段。

3. 用 CreateCharts 与 Invoice 样例看懂读写流程:XLSX 最小实现

样例项目里InvoiceSample和CreateChartsSample是最适合当模板的两个文件。一个演示数据填充,一个演示图表生成。读懂它们等于掌握了这套控件 80% 的日常用法。

3.1 读:把 Sheet 内容映射到网格控件

在发票场景中,流程是先读取模板里的固定表头,再把业务数据逐行填进去。最简单直观的验证方法是把工作表内容映射到 TStringGrid 上,代码如下:

procedure LoadSheetToGrid(XL: TXLSReadWriteII; Grid: TStringGrid); var Col, Row: Integer; begin Grid.ColCount := XL.Sheets[0].LastCol; Grid.RowCount := XL.Sheets[0].LastRow; for Col := 1 to XL.Sheets[0].LastCol do for Row := 1 to XL.Sheets[0].LastRow do Grid.Cells[Col - 1, Row - 1] := XL.Sheets[0].Cells[Col, Row].AsString; end;

LastCol和LastRow返回的是工作表里有内容的边界,不是全表最大范围,所以这个循环不会穷举几万行。合并单元格只有在左上角有值,其它区域读出来是空字符串,这是常被忽略的行为。逻辑上先取边界再逐行读,能减少对Cells接口的调用频率。如果要读超大文件,逐单元格读仍然偏慢,更高效的做法是调用区域批量接口把二维块一次性取到内存变量里。

3.2 写:把 TDataSet 内容填进 Invoice 模板

InvoiceSample的典型做法是用一个客户地址数据集挂到模板 sheet 上,然后逐条写入。压缩包里的Addresses.cds就是这类数据集的样例。看完它的结构,可以对应写出下面的代码:

procedure WriteDataSetToSheet(XL: TXLSReadWriteII; DS: TDataSet); var Col, Row: Integer; begin XL.Sheets[0].Clear; for Col := 0 to DS.FieldCount - 1 do XL.Sheets[0].Cells[Col + 1, 1].AsString := DS.Fields[Col].FieldName; Row := 2; DS.First; while not DS.Eof do begin for Col := 0 to DS.FieldCount - 1 do XL.Sheets[0].Cells[Col + 1, Row].AsString := DS.Fields[Col].AsString; DS.Next; Inc(Row); end; XL.WriteToFile; end;

这段代码有两个容易踩的细节。第一,所有字段都通过AsString转成字符串输出,Excel 里可以显示,但数值列不再是数字类型,后面对账公式会算不出来。正确的做法是按字段类型分流,金额字段用AsFloat,日期字段用AsDateTime,字符串字段才用AsString。第二,WriteToFile之前必须已经设置过Filename,否则控件会直接抛异常。很多报表项目最后导出的单价列变成文本,原因就是这里图省事全用了 AsString。

3.3 图表不是画出来的:CreateChartsSample 的关键参数

CreateChartsSample.cfg对应的样例工程重点不在 UI 绘图,而在数据区域绑定。图表对象挂在 sheet 上,由图表类型、系列来源、坐标范围三个参数决定。精简后的写法如下:

var Chart: TXLSChart; begin Chart := XL.Sheets[0].Charts.Add; Chart.ChartType := xlLCChartBar; Chart.Title := '月度营收'; Chart.Series.Add( XL.Sheets[0].Range['B2:B13'], XL.Sheets[0].Range['A2:A13'], '营收' ); Chart.Top := 20; Chart.Left := 200; Chart.Width := 400; Chart.Height := 250; end;

Range['B2:B13']是值区域,Range['A2:A13']是分类轴标签区域,最后的'营收'是系列名称。ChartType换成xlLCChartLine得到折线图,换成xlLCChartPie得到饼图。图表的位置属性Top/Left在部分版本里单位是像素,部分版本是英磅,我的经验是直接沿用默认值,打开 Excel 时位置就很合理,不必强求换算。

填充格式这一块也不复杂。列宽调整、合并单元格、批注写入都可以在写数据之后顺手完成:

with XL.Sheets[0] do begin Columns[1].Width := 30; Cells[2, 2].AsString := '批注测试'; Cells[2, 2].Comment.Text := '由 Delphi 写入'; end;

Columns[1]是第一列,Width按 Excel 的字符宽度单位计算。Comment.Text是单元格批注内容,换行用#13#10,另外批注文本最大为 32767 个字符,超过会被截断。这套组合在填模板、做报表头、加说明注释时够用了。

4. 避坑与排查:cfg 加载、Unicode、单元格坐标与许可证

这套控件真正让人摔跤的往往不是读写逻辑,而是版本协调、类型转换和工作表坐标这三类问题。我按自己踩过的顺序记录成避坑清单,每一条都是现象到处理连着的。

4.1 五条高频翻车记录

现象一:设计期拖到窗体上的控件是灰色,运行后整个面板报 Access violation。原因是设计期包没装好,或者工程里缺少条件编译定义。解决方法是先确认 Project Options 里的 Conditional defines 包含XLSD6_LICENSE,再回到 Component > Install Packages 查看设计期包是否被勾选。还不行就手工删除缓存 BPL 重装一次。

现象二:复制样例 cfg 到另一台电脑后,打开工程提示File not found: 'XLSRWII.dcu'。原因是 cfg 里的 Source_Dirs 用了绝对路径。解决方法是把路径改成相对路径,或在.dproj的 Search Path 里重新添加源码目录。不要直接改别人机器上的绝对路径,改成..\Source这类相对路径最稳。

现象三:中文字符写入 Excel 后变成乱码,打开文件看到的是类似??的占位符。原因是字符在某个环节被转成 AnsiString,而目标 xlsx 内部是 UTF-8 编码。解决方法是在写入端统一用字符串类型的属性,不要手动做CharToOEM之类的转换,控件在WriteToFile时会自己处理编码。如果手写 XML,则必须声明encoding="UTF-8"。

现象四:写几万行数据时耗时几十秒,Excel 打开还提示内容损坏。原因是每个单元格单独触发一次内部变更通知,并且文件没设置压缩选项。解决方法是写入期间调用BeginUpdate,全部写完后调用EndUpdate,保存时打开压缩标记。这样既减少刷新次数,也让生成的 xlsx 容量更小。

现象五:发布到服务器后提示无法定位程序输入点。原因是运行时包缺失或版本不匹配。如果你不是发布 BPL 分发,就必须把编译选项设置为静态连接,确认目标机器不需要额外带 XLSReadWriteII 的动态库文件。

4.2 兜底手段:用文件结构反推问题源头

当控件报错信息很模糊时,最直接的办法是绕开控件,直接检查生成的 xlsx 文件内部结构。因为 xlsx 本质是一个 zip 压缩包,可以把扩展名改成.zip后查看条目,或者用命令行检查:

unzip -l Demo.xlsx

正常文件里至少应该能看到xl/workbook.xml、xl/worksheets/sheet1.xml、xl/styles.xml。如果xl/worksheets/sheet1.xml缺失,多半是保存时工作簿结构没有被正确建立。如果字符串都写进了xl/sharedStrings.xml但 sheet 里引用为空,则说明单元格坐标写入有偏移。这个方法能快速区分是控件用法问题还是文件流问题,是我的排错底线。

5. 逐文件拆解样例:PivotTable 与 AutoFilter 的工程写法

剩下这批 cfg 文件不是摆设,每个都对应一个独立主题。这里把 PivotTable、AutoFilter、MonitorFile、XLSGrid 这四类拆开讲,参考它们能少走很多弯路。

5.1 PivotTableSimpleSample:最少字段也能生成透视表

简单透视表样例演示的是最基本的数据块登记:指定源数据区域,声明行字段和值字段,然后控件在打开时自动重建透视缓存。核心参数如下:

var PT: TXLSPivotTable; begin PT := XL.Sheets[0].PivotTables.Add; PT.SourceData := XL.Sheets[0].Range['A1:D100']; PT.AddRowField('品类'); PT.AddColumnField('月份'); PT.AddValueField('金额', xlLCFSum); end;

SourceData是透视表的数据源区域,需要包含表头行。AddRowField声明透视表的行分组字段,AddColumnField声明列分组字段,AddValueField决定汇总指标和统计方式。xlLCFSum表示求和,换成xlLCFCnt变成计数。透视表样例里最容易忽略的一点是源区域不要带上整列,否则透视缓存会膨胀,文件体积变大,打开速度明显变慢。

5.2 PivotTableAdvancedSample:多 Sheet 联动与汇总字段

高级透视表样例在简单样例基础上增加了数据关联和多个汇总字段。做法上不再把数据源限定在同一个 sheet,而是把透视表的数据源指向另一张工作表的区域,然后在当前 sheet 展示结果。示意代码:

PT.SourceData := XL.Sheets[1].Range['A1:F200']; PT.AddValueField('数量', xlLCFSum); PT.AddValueField('金额', xlLCFSum); PT.AddPageField('区域');

数据源不在当前 sheet 是合法的,控件内部会建立透视缓存。AddValueField可以加多个,数量、金额各一行,Excel 打开后会自动排列到值区域。页码字段AddPageField对应透视表的筛选器区域,常用于按区域拆分统计。这里要多看几眼生成的 xlsx 文件,确认透视表缓存字段顺序和源表一致,否则打开时可能提示刷新数据。

5.3 SampleAutoFilter:筛选不靠循环,用行区域过滤

AutoFilter 样例展示的是自动筛选,不遍历每一行做条件判断,而是把筛选条件挂在工作表上。示例代码:

with XL.Sheets[0].AutoFilter do begin Range := XL.Sheets[0].Range['A1:D100']; Active := True; Items[0].Criteria := '>=1000'; end;

Range声明筛选区域,第一行会被视作表头。Items[0]对应第一列,Criteria是筛选条件,写法与 Excel 自身的筛选语法一致。需要注意,Criteria设置的是自定义条件,和下拉勾选值不同,Excel 打开后会在该列显示漏斗图标。不是所有列都需要条件,只对目标列设置即可。

5.4 MonitorFileSample:文件锁定与占用监控

MonitorFile 样例的主题是监控 Excel 文件变化。常见做法是定时器轮询目标文件的修改时间,发现变化后重新读取,避免文件被占用时反复读取失败。代码示意:

procedure TForm1.Timer1Timer(Sender: TObject); var NewTime: TDateTime; begin NewTime := FileAgeToString(Edit1.Text); if NewTime <> FLastWriteTime then begin FLastWriteTime := NewTime; XL.Filename := Edit1.Text; XL.ReadFromFile; RefreshGrid; end; end;

FileAgeToString返回字符串形式的修改时间,和上次记录的值比较,不同就重新加载。这个方法不依赖系统文件监控 API,跨 Windows 版本行为一致。要注意的是文件如果正在被写入,读取会触发共享冲突。这时要么捕获异常等待下一轮定时器,要么先复制一份临时文件再读取。

5.5 XLSGridSample:把 Sheet 数据交给网格预览

XLSGrid 样例不是读写器的核心功能,而是把读取结果展示到界面层。它演示了控件在交互场景下的延迟加载策略:只加载可视区域的单元格,滚动时再补充数据,避免一次拉全量几十万行。如果你做的是桌面端 Excel 查看器,这个样例可以直接作为模板。它和 TStringGrid 的区别在于列类型、行高、合并单元格都保留原样,适合做轻量级预览模块。

6. 把大文件写成流式的最后一步:BeginUpdate、缓存与压缩

平时小文件怎么写都行,一旦面对十万行级别的导出,就要把控件当成流式写入器来用。核心技巧是把整批写入包在一个 BeginUpdate 块里,结束后统一提交,避免每个单元格触发一次内部事件通知。流程上是这样:

XL.BeginUpdate; try for Row := 1 to 100000 do begin for Col := 1 to 10 do XL.Sheets[0].Cells[Col, Row].AsFloat := DataArray[Row][Col]; end; finally XL.EndUpdate; end; XL.WriteToFile;

逻辑上BeginUpdate暂停界面刷新和内部状态同步,EndUpdate时统一计算。这样十万行数据的写入时间能从几十秒降到几秒,代价是期间不能读取用户输入。保存时打开压缩选项,生成的 xlsx 会使用 zip 压缩,大小明显减小。注意EndUpdate必须放在 finally 块中,否则异常时控件一直处于更新挂起状态,后续读文件会表现异常。

验证时可以做一层额外检查:生成后用压缩软件打开 xlsx,查看xl/worksheets/sheet1.xml里<row>节点的数量和预期行数一致,同时确认xl/sharedStrings.xml里字符串分组正常。坚持这个检查,能发现大部分隐藏的坐标偏移问题。从那以后我每次交付报表模块前,都强制走一遍“小文件能读、大文件能写、图表能刷新”三步自测,再没出现过导出后用户反馈 Excel 打不开的情况。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进