ARTICLE · INTELLIGENCE

战地情报 · 详情页

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

Delphi 12.3中XLSReadWriteII控件读写Excel的完整指南

Delphi 12.3中XLSReadWriteII控件读写Excel的完整指南

简介:针对 Delphi 12.3 开发环境的 XLSReadWriteII v6.01.17 控件包,面向需要在桌面程序中实现 Excel 文件读写、报表生成与数据导出的中高级 Delphi 开发者。整套资源共 499 个文件,压缩后约 2.15MB,以 pas 源码为核心,配套 dfm 表单、dpr 工程及 html 说明文档,便于阅读控件实现、查看界面布局与运行示例项目。压缩包内还包含 dproj、inc、res 等工程配置文件,以及少量 gif、jpg 插图与示例用 fmx、cds 文件,适合在 Delphi 12.3 环境下快速安装引用并对照学习。已有 65 人浏览学习。通过该控件包可获得完整的单元源码、丰富的示例工程(如网格、筛选、透视表、自动过滤等场景)以及相关的配置文件。对于需要处理复杂 Excel 模板、批量生成报表或集成表格交互的开发者来说,这套资料能直接提供可编译运行的参考框架,节省从底层协议开始摸索的时间,同时帮助熟悉 XLSReadWriteII 的常用对象模型与设计思路。

1. 一个 rar 包解决的 Delphi 12.3 读写 Excel 问题

拿到Delphi 12.3控件之XLSReadWriteII v6.01.17 D12.rar,你先要理解它解决的核心矛盾:Delphi 程序要导出带格式的 xls/xlsx、要解析用户丢过来的 Excel 报表,但你又不想在服务器上装 Office、不想跟 COM 的 Excel 进程纠缠。XLSReadWriteII 是 Delphi 生态里专门做这件事的第三方控件,D12后缀表示它跟随 Delphi 12(含 12.3 更新)的编译环境,v6.01.17是它的版本号。适合做 ERP 报表、工业数据导出、批量导入落库这类活儿的 Delphi 开发者。接下来的内容,我按解压安装、最小读写、格式与多 Sheet、踩坑排查的顺序,把这个包从装到用完整拆一遍。

2. 装进 Delphi 12.3:包编译、库路径与控件安装验证

2.1 先分清源码包和编译包,别把它当 ActiveX 控件装

XLSReadWriteII 的发布 rar 解压后,常见布局是 Bin 放编译好的 bpl/dcp,Source 放 .pas 源码,Packages 里放对应各版本 Delphi 的 .dpk/.dproj 工程文件,旁边一般还有 Docs 和 Demo 目录。v6.01.17 D12这种发布包通常源码和编译产物都齐,但我一般建议你直接走源码编译,不要贪图省事去拖 Bin 里现成的 bpl——Delphi 12.3 的 RTL 和编译器版本比 11、比 12.0 都有更新,别人机器上编出来的二进制包放到你这里,轻则点安装没反应,重则 IDE 直接报“模块版本不匹配”。

这里有个容易翻车的认知:XLSReadWriteII 是 VCL 原生控件包,不是 ActiveX。所以别照搬那些用regsvr32注册 dll 的流程,也不存在 PageOffice、LODOP 那种浏览器控件“安装后依然提示让安装”的注册表问题,更不用像远程桌面 ActiveX 那样去系统路径里找 rdclientax.dll。它在 Delphi 里的安装路径只有一条:配置 Library Path → 编译 .dpk → Install 设计时包。

2.2 配置 Library Path:让 IDE 找得到源文件

解压后第一件事是把 Source 目录加进 IDE 的库路径。打开 Tools > Options > Delphi Options > Library,在 Library Path 里追加一行。Delphi 12.3 里会同时存在 32 位和 64 位平台配置,Source 目录是平台无关的纯 Pascal 源码,所以两个平台的库路径都要加,别只改了 Win32 就以为完事。

D:\Components\XLSReadWriteII\Source D:\Components\XLSReadWriteII\Packages\D12

第一行是单元文件所在,第二行是包工程所在。加完点 Save 后,最简单验证方式是按 Ctrl+F12 打开 Project Manager(或直接搜索单元名),如果能在列表里看到 XLSReadWriteII2.pas 之类的单元,说明路径生效。注意 rar 解压时如果带了很深的子目录,比如Source\XLSReadWriteII\,你要加的是包含 .pas 的那一层,不是只加到Source就停,否则编译包时照样报Unit not found。

2.3 编译并安装设计时包

建议在 Packages\D12 目录里找 .dproj 工程文件,Delphi 12.3 可以直接双击 dproj 打开,然后在 Project Manager 里对包工程右键,选 Build。这一步会生成 .bpl 和 .dcp。对于命令行习惯的人,用 msbuild 更可控:

cd /d D:\Components\XLSReadWriteII\Packages\D12 msbuild XLSReadWriteII.dproj /t:Build /p:Config=Release /p:Platform=Win64

这里把文件名换成包里的实际 dproj。Delphi 12 的 IDE 是 64 位进程,设计时包必须按 Win64 编译,否则组件面板加载不到;如果运行时的目标平台是 Win32,那你项目的编译器和运行库要另外按 Win32 处理,但 IDE 里装的设计时包必须是 Win64。Build 成功后菜单 Component > Install Packages,点 Add,选中刚生成的 .bpl 文件,确认后工具面板会多出一个 XLSReadWriteII 页签。

2.4 装完先跑一个最小工程验证

新建一个 VCL Application,把 XLSReadWriteII 组件拖到窗体上,放一个 Button,写一段最基础的导出代码,运行看能否正常生成文件。这一步能筛掉八成安装问题,不要跳过。

procedure TForm1.Button1Click(Sender: TObject); begin XLSReadWriteII1.Filename := 'D:\temp\demo.xlsx'; XLSReadWriteII1.Sheets[0].AsString[1, 1] := 'Hello from Delphi 12.3'; XLSReadWriteII1.Write; end;

逻辑说明:Filename不只决定存到哪里,还决定写出文件格式;Sheets[0]是默认第一张工作表;AsString[行, 列]是控件最常用的单元格写入接口,坐标从 1 开始计数。如果运行后 D:\temp 下出现了 demo.xlsx 且用 Excel 能打开看到字符串,安装链路就通了。如果运行报File not found,先回 2.2 检查 Library Path 是否真的包含源码目录,再把包重新 Build 一次。

3. 最小读写流程:Sheet、单元格与数据类型的调用模型

3.1 为什么是 Sheet + Cells 这套对象模型

XLSReadWriteII 内部把一本工作簿拆成 Sheets 集合,Sheet 是操作的核心,单元格读写都挂在 Sheet 上。这套模型的好处是贴合 Excel 本身的结构,不像早期的一些控件拿二维数组硬套,遇到合并单元格、多 Sheet 联动就露怯。读 Excel 时它把整个文件解析进内存,写 Excel 时它是按 BIFF 或 OOXML 规范直接生成文件,全程不启动 Excel 进程,所以能用在服务端无人值守的导出任务里。

3.2 写出多行数据:AsFloat、AsDateTime 与自动类型

只写字符串不够用,实际导出通常是混合类型。看这个示例:

var LSheet: TXLSSheet; begin LSheet := XLSReadWriteII1.Sheets[0]; LSheet.AsString[1, 1] := '订单号'; LSheet.AsInteger[1, 2] := 10023; LSheet.AsDateTime[1, 3] := Now; LSheet.AsFloat[1, 4] := 1999.99; XLSReadWriteII1.Filename := 'order.xlsx'; XLSReadWriteII1.Write; end;

逻辑说明:AsInteger写整数,AsDateTime写日期时间,AsFloat写小数。特别提醒AsDateTime:Excel 里的日期本质是序列号,如果你用AsString去写日期值,写出来的是“44835.5”这种数字文本,用户打开表格看到一串小数会以为导出程序坏了。参数说明:日期写入前确认Now的时区信息是否需要转换为本地时区,服务端生成报表尤其要注意 UTC 和本地时间的差异,最好在写入前统一var转换。

3.3 读回数据到界面:把握 LastRow 和 LastCol

读操作是重点场景,用户表格可能有空行、有合并单元格,但多数情况我们要的是规整的矩形区域。下面这段把内容读进 Memo,注意边界判定:

var LRow, LCol: Integer; LSheet: TXLSSheet; LLine: string; begin XLSReadWriteII1.Filename := 'input.xlsx'; XLSReadWriteII1.Read; LSheet := XLSReadWriteII1.Sheets[0]; for LRow := 1 to LSheet.LastRow do begin LLine := ''; for LCol := 1 to LSheet.LastCol do LLine := LLine + LSheet.AsString[LRow, LCol] + #9; Memo1.Lines.Add(LLine); end; end;

逻辑说明:LastRow和LastCol返回的是 Sheet 实际有内容的最后行和最后列,用它做循环上限不会越界。参数说明:AsString读取时会把其他类型自动转成字符串,但AsFloat取出来的小数位数受单元格格式影响,格式化时最好用Format('%.2f', [...])固定精度。这个读法适合中小文件,几十 MB 以内的 Excel 直接全量读入内存问题不大,超过这个量级,建议在 Excel 里先做预处理或换用流式方案,后面第六章会聊。

3.4 单元格是否为空:Type 判断和 IsEmpty 的坑

读用户传上来的 Excel,最麻烦的是混着空格和真空值。一行里某列既有公式算出空串,又有物理空单元格,统一读出来都是空字符串。

var LCell: TXLSCell; begin LCell := XLSReadWriteII1.Sheets[0].Cells[3, 2]; if LCell.IsEmpty then ShowMessage('这个单元格什么也没有') else if LCell.DataType = xlsString then ShowMessage('字符串:' + LCell.AsString); end;

逻辑说明:Cells[Row, Col]返回单元格对象,IsEmpty判断物理上是否没有任何值,DataType返回类型枚举。这个判断比直接比对AsString = ''可靠,因为公式计算出的空结果和真空值在后续落库时语义完全不同。参数说明:不同小版本里IsEmpty的命名可能略有差异,有的版本叫Empty,如果你编译时提示找不到,到随包 CHM 帮助里搜“IsEmpty”看准确名称,不影响整体逻辑。

4. 让表能看:格式、公式、列宽与多 Sheet 组合报表

4.1 单元格格式:字体、背景与边框的调用路径

导出 Excel 不只是把数据塞进去,客户的报表讲究标题行加粗、表头有底色、金额列加千分位。XLSReadWriteII 没有把格式摊平到每个单元格上属性,而是集中在 CellFormat 对象里,这是一个值得适应的设计:先取到单元格的格式对象,再逐项改属性。

var LFmt: TXLSCellFormat; begin LSheet := XLSReadWriteII1.Sheets[0]; LFmt := LSheet.CellFormat[1, 1]; LFmt.Font.Name := '微软雅黑'; LFmt.Font.Size := 11; LFmt.Font.Style := [fsBold]; LFmt.BackgroundColor := $00FDE9D9; LFmt.HorAlign := haCenter; end;

逻辑说明:CellFormat[Row, Col]返回该单元格格式对象,改动立即生效;Font.Style是集合类型,fsBold加粗;BackgroundColor是 BGR 格式的 COLORREF,$00FDE9D9是浅米黄色。参数说明:HorAlign的枚举名不同版本有差别,常见是haCenter或xlHAlignCenter,编译报错就按 Ctrl+Click 跳转到枚举定义里抄一个,这不是什么丢人的事。一个容易被忽略的性能点:如果整张表几千行都要统一字体,不要一行一行去设 CellFormat,而是先拿到行的默认格式对象统一设置,再只对个别单元格做覆盖,写入速度能差一个数量级。

4.2 表格三件套:列宽、行高与冻结窗格

报表头三样:列宽撑开、表头行加高、首列或首行冻结。XLSReadWriteII 的 Sheet 对象上直接提供了这几个入口。

XLSReadWriteII1.Sheets[0].Columns[1].Width := 18; XLSReadWriteII1.Sheets[0].Rows[1].Height := 28; XLSReadWriteII1.Sheets[0].FreezePanes := 1;

逻辑说明:Columns[N].Width是第 N 列的宽度,单位是字符宽度,18 差不多能放下 18 个半角字符;Rows[N].Height是行高,单位是磅;FreezePanes设置冻结区域,填 1 表示冻结首行,填 2 表示冻结前两行,这是导出报表时的惯用设置。参数说明:列宽给太小中文表头会被撑成“自动换行”的乱象,一般中文表头列给到 14 以上;行高 28 是表头常见值,正文行高保持默认即可,不要全局统一改行高,否则长数字会显示成#(Excel 的列宽不足提示),用户会误以为数据坏了。

4.3 多 Sheet 操作:新增、改名与顺序调整

多 Sheet 是 ERP 报表的常态:一个汇总页加一个明细页,或者按月份分 Sheet。XLSReadWriteII 的 Sheets 集合提供了与 Excel 操作一致的方法。

var LNewSheet: TXLSSheet; begin LNewSheet := XLSReadWriteII1.Sheets.Add('明细'); LNewSheet.AsString[1, 1] := '2026-01-01'; XLSReadWriteII1.Sheets[1].Name := '汇总-2026'; XLSReadWriteII1.Write; end;

逻辑说明:Sheets.Add('明细')在末尾新增 Sheet,返回新 Sheet 对象;Name直接改工作表名,注意工作表名不能超过 31 个字符,不能包含冒号、斜杠、问号等 Excel 保留字符,这也是常见报错来源。参数说明:Sheets[0]是默认第一个 Sheet,新增后它在末尾,要把它移动到前面可以用Move方法,具体名字以帮助为准;如果要从另一个 Sheet 复制整块区域,直接两层循环读 A 写 B 最稳,别指望一次调用搞定。

4.4 公式导出:保存公式还是保存计算结果

写带公式的 Excel 有两种路线:一是把公式字符串写进去让 Excel 打开时自动算,二是把程序里已经算好的值写进去。XLSReadWriteII 两者都支持,但用法不同。

// 方式一:写公式主体 XLSReadWriteII1.Sheets[0].Cells[5, 4].Formula := 'SUM(B2:B4)'; // 方式二:写入以等号开头的公式字符串 XLSReadWriteII1.Sheets[0].AsString[5, 4] := '=SUM(B2:B4)';

逻辑说明:Formula属性是结构化公式接口,写入时不带等号;AsString写=SUM(...)这种整串带等号的文本,打开文件后会被 Excel 识别为公式。区别在于:Formula接口更严格,非法公式会在写入前报错;AsString更宽容,写错了也只是在 Excel 里显示#NAME?。参数说明:如果你希望用户打开文件就能看到计算结果,公式里引用的单元格必须在同一工作表或用完整跨表引用格式'明细'!B2;如果程序里已经有现成的计算结果,我的建议是直接写数值而不是写公式,一是文件体积小,二是避免用户误改公式导致报表错误。

5. 避坑:安装、编译与运行期的高频问题排查

5.1 组件面板里没有 XLSReadWriteII 页签

现象:按 2.3 的步骤走完,Components 面板里找不到对应的页或组件。

原因:最常见的是只 Build 了运行时包,没有执行 Install;或者安装时选的 bpl 是 32 位编译产物,而 Delphi 12 的 IDE 是 64 位进程,加载不了。

解决:回到包工程,在 Project Manager 里确认活动配置是 Win64 Release,重新 Build,再到 Component > Install Packages 里 Add 新生成的 bpl。装完后如果页签还是没出现,关掉 IDE 重开一次——设计时包注册偶尔需要重启 IDE 刷新工具面板,这不是玄学,是包缓存没刷新。

5.2 拖控件到窗体报 Class Not Found 或 EReadError

现象:新建工程没问题,但打开一个老工程、把控件拖到窗体时,IDE 报“类不存在”,窗体 DFM 加载失败。

原因:这个 erar 里的版本和工程里记录的类名/版本不一致。常见于你之前装过另一个版本的 XLSReadWriteII,DFM 文件里存的是旧类名,新包没有这个类。

解决:先把旧版本包卸载干净(Component > Install Packages 里移除旧 bpl),再装新版;老工程打开前,用文本方式(右键 DFM 选 Open As Text)检查一下记录的控件类名,如果确实找不到对应类,手动删除这一行再转回窗体视图。这一步要小心操作,改 DFM 前先备份文件,免得把工程改废了。

5.3 编译程序报内存错误或 IDE 直接崩溃

现象:平时编译正常,引用 XLSReadWriteII 后一编译就报 Out of Memory 或 IDE 闪退。

原因:bpl/dcp 版本残留,IDE 加载了两套同名单元的编译缓存,构建时符号表冲突;另外 Delphi 12.3 如果开了杀毒软件实时扫描构建目录,大量读写临时文件也可能触发异常。

解决:先在项目里删掉所有 .dcu、.dcp 缓存,实际处理是进到 Packages\D12 目录删除编译产物,重新 Build 一次;把 Delphi 的缓存目录(默认是C:\Users\用户名\AppData\Local\Embarcadero\BDS\xx.0\cache)加进杀毒软件白名单。还不行就把 bpl 从系统目录(比如 System32)里也清一遍,有些安装脚本会把 32 位 bpl 丢进 System32,Delphi 12 的 64 位 IDE 去加载时对不上就崩。这个排查顺序我踩过两次,基本都是缓存问题,不是控件本身的锅。

5.4 生成的 xlsx 打开提示格式不匹配或 WPS 打不开

现象:代码运行正常,文件也生成了,但用户反馈 Excel 弹“文件格式和扩展名不匹配”,或者 WPS 直接打不开。

原因:控件内部统一按 BIFF 格式写文件,文件后缀却叫 .xlsx;或者反过来,设置了 xlsx 格式但Filename后缀是 .xls。

解决:在写文件前显式指定格式版本,不要只靠后缀推断。到帮助文件里搜 FileVersion 或文件类型枚举,按版本设置对应的 xlsx 常量;如果不纠结 xlsx,最省事的是把Filename后缀统一改成 .xls,老格式兼容性反而更稳。还有个小坑:Excel 对“内容含公式但文件后缀是 xlsx、格式版本还是 xls”的组合特别敏感,描述就是这条。修法只有一个:格式版本和后缀保持一致,没有捷径。

5.5 Read 大文件时内存飙升

现象:Read 一个 100MB 以上的 xlsx,程序内存占用到 1GB,界面卡死。

原因:控件默认把整个工作簿解析到内存,包括每个单元格的格式对象。带大量样式的大文件,内存会被放大好几倍。

解决:文件能拆就先拆,在源端就把单 Sheet 控制在一个合理行数;控件文档里搜优化相关选项,开启低内存写入模式后,生成侧的内存占用能明显降下来。读侧如果没有优化开关,务实做法是在 Excel 里另存为 CSV 再让 Delphi 解析文本,速度快且内存可控。CSV 丢格式,但你要的核心是数据,格式本来也不该由导入端来保证。

6. 一个值得养成的习惯:用内存流读写并做写入后自校验

批量导出报表的常见翻车现场,是多个任务并发写同一个目录下的文件,互相把对方的文件锁挤掉,或者写到一半进程被 kill 留下半个文件的脏数据。避免这些问题,我的做法是让控件不碰文件系统,直接用内存流打交道,写完立刻把流读回来自校验一遍。

var LStream: TMemoryStream; begin LStream := TMemoryStream.Create; try XLSReadWriteII1.Filename := 'report.xlsx'; XLSReadWriteII1.Sheets[0].AsString[2, 2] := '2026-09-01'; XLSReadWriteII1.SaveToStream(LStream); LStream.Position := 0; XLSReadWriteII1.LoadFromStream(LStream); Assert(XLSReadWriteII1.Sheets[0].AsString[2, 2] = '2026-09-01', '写入后自校验失败'); finally LStream.Free; end; end;

逻辑说明:SaveToStream把当前工作簿写到流里,这里不落盘;LoadFromStream从流重建工作簿,重建后读回刚才写的单元格做断言。Assert失败说明控件内部生成的 xlsx 数据有问题,多数是格式版本不对导致写坏。参数说明:流操作后必须手动Position := 0回卷,否则 LoadFromStream 从末尾开始读,读出来的内容是空的;如果你是多线程生成报表,每个线程有自己的 XLSReadWriteII 实例和流对象,不要共享,控件内部有全局状态,跨线程共享实例会随机崩溃。

这套“内存流 + 自校验”的习惯,是从一次血泪教训里长出来的。那次是服务器上定时任务生成几十份报表,直接写盘,结果某天文件锁冲突,用户拿到一份半截报表,误以为是系统算错了数据。后来所有报表统一改成流模式,写完先把关键单元格读回来核一遍,再统一落到磁盘和备份目录。合同数据、财务对账这类场景,宁可多花几十毫秒校验,也不能把坏文件发出去。希望帮到你。

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

RELATED READING

延伸阅读

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