Delphi 中使用 XLSReadWrite 读写 Excel 的安装与实战 简介针对 Delphi Tokyo 10.2 推出的 XLSReadWriteII 6.00.16 组件包面向需在 Delphi 中读写 Excel 文件的开发者可在不安装 Microsoft Office 的前提下直接处理 xls 与 xlsx 格式兼顾历史版本与新版工作簿。压缩包共 816 个文件大小 13.47MB内含 hpp 头文件、pas 源码、dcu/obj 编译单元以及大量 html 帮助文档和 dpr/dproj 示例工程方便开发者直接查看接口并集成至 IDE。包内附带图表创建、数据透视表交互、自动筛选等示例配置可快速掌握单元格赋值、公式写入、样式调整等核心操作配套的 html 说明也能辅助排查常见错误。已有 312 人学习下载适合正在使用 Tokyo 10.2 或需要高效生成报表的中高级 Delphi 程序员。使用该组件能降低项目对 Office 环境的依赖提升大批量 Excel 数据的读写与处理效率。1. XLSReadWrite 不是黑匣子纯 Delphi 读写 Excel 的落地路径项目交付时最怕现场说 Excel 版本不对。XLSReadWrite 系列组件就是为这种场景准备的它把 Excel 文件的读写逻辑直接编译进 exe运行时不依赖 Office COM也不要求目标机器装 Office。6.00.16 这个版本明确面向 RAD Studio Tokyo 10.2Delphi 项目里用这个版本能省掉一大半兼容性扯皮。适合桌面管理软件、检测设备上位机这类要稳定导出报表、又不想被 Office 版本绑架的团队。把组件包从下载、安装到读写、排错完整走一遍中间会给可直接抄的代码和参数说明照着做就能跑通。2. 装进 Tokyo 10.2组件包编译与 IDE 注册的完整路径2.1 先看包结构设计期包、运行期包和源码目录拿到资源包先别急着双击安装。XLSReadWriteII 这种带设计期注册的第三方组件包通常分成两部分运行期包负责纯读写逻辑设计期包负责把组件注册进 IDE 工具栏。源码目录一般叫 Source核心单元都在里面包工程以 .dproj 或 .dpk 结尾。第一次动手我习惯把整个包放在一个无空格路径下比如 C:\Lib\XLSRW避免后续编译路径解析出问题。某工控项目的开发机把库放在“D:\三号产线\库文件”下编译包时 msbuild 解析路径连续报错改成纯英文路径后一次通过。包结构上常见的是两到三个工程文件加一批源码。编译顺序必须是先运行期包后设计期包。顺序反了设计期包找不到运行期类直接报 unit not found 或 cannot load package。可以用下面的命令行把两个包依次编出来cd C:\Lib\XLSRW msbuild /t:Build /p:ConfigRelease /p:PlatformWin32 运行期包.dproj msbuild /t:Build /p:ConfigRelease /p:PlatformWin32 设计期包.dproj第一行编译运行期包第二行编译设计期包。包名替换成下载目录里实际存在的 .dproj 文件名以资源包内的工程文件清单为准。参数说明ConfigRelease 表示发布配置避免 Debug 配置混入额外调试符号PlatformWin32 指 IDE 用的 32 位平台即使项目最终编成 64 位 exe组件包这一步仍建议用 Win32注册进 IDE 后 64 位由项目编译时单独处理。Release 和 Win32 这对组合是我在这个版本上验证过最稳的。编译产物里有两个文件容易混淆bpl 是运行时动态库dcp 是包编译后的接口描述。项目如果启用了 Runtime Packages链接器从 bpl 拿实现项目关闭运行时包编译器从 Source 目录的 dcu 拿实现。理解这层关系就明白为什么装完包仍然报单元找不到。我一般会把编译好的 bpl 放在独立目录用绝对路径注册后续升级组件只要重新编译再覆盖不用来回复制文件。2.2 库路径和 bpl 注册的完整配置编译通过只是第一步注册属性也要配齐。Delphi 第三方组件安装有两个关键入口Library Path 和 Install Packages。Library Path 告诉编译器源码在哪里菜单路径是 Tools Options Environment Options Delphi Options Library在 Library Path 追加 C:\Lib\XLSRW\Source。这一步不做IDE 加载包后打开项目仍会报单元找不到并且报错位置常常指向项目文件自身容易误判成项目坏了。Install Packages 负责把设计期 bpl 挂进 IDE菜单路径是 Components Install Packages点 Add 选择编译好的设计期 bpl。Tokyo 10.2 编译出来的包文件带版本尾号这个版本体系里对应 260。如果 bpl 显示 invalid package 或 version mismatch说明包不是用当前 IDE 版本编的解决方法是重新编译而不是硬加载。运行期包和设计期包的分工整理成表格更好对照包类型编译产物安装方式缺失时的表现运行期包带类实现的 bpl / dcp加进库路径打开项目报 unit not found设计期包注册 IDE 组件的 bplInstall Packages组件面板不出现组件装完之后组件面板会多出一整套读写组件拖一个到窗体上大多场景只需要关注主控件本身的属性就够了。验证是否装好的标准动作是新建一个空项目拖组件放一句断点在加载调用上跑起来能进断点就算通。一个容易忽略的点包安装只影响设计期真正编译目标项目时项目 Options 里的 Runtime Packages 如果设置为不使用运行时包链接时会按源码重新编译一份组件实例这时 Source 路径的优先级就会体现出来。所以我把 Source 目录放在 Library Path 的最前面避免 IDE 拉到旧版本残存。升级 IDE 大版本时也不要直接拷贝 bpl必须回到当前版本里重编否则下次打开项目还会触发版本不一致的报错。到这里组件包才真正可用。接下来进入读文件的部分。3. 读 Excel 文件真实格式判定、表单遍历与单元格取值3.1 先判断文件真实格式再加载写读取代码前我先做一步很多资料里不会提的检查看文件头而不是看扩展名。业务系统里经常遇到这种情况某系统导出的文件叫 .xlsx实际内容是从 HTML 表格或 CSV 改的扩展名。组件按 xlsx 解析一个 HTML 文件不会立刻崩溃而是读出来一个空表单或者报文件头错误。我写过一个工具函数读前几个字节function IsRealXlsx(const AFilePath: string): Boolean; var FS: TFileStream; Bytes: array[0..1] of Byte; begin Result : False; FS : TFileStream.Create(AFilePath, fmOpenRead or fmShareDenyWrite); try FS.Read(Bytes, 2); Result : (Bytes[0] $50) and (Bytes[1] $4B); // PK 压缩包头 finally FS.Free; end; end;逻辑说明xlsx 本质是 zip 压缩包前两个字节固定是 PKxls 老格式前 8 个字节是 D0 CF 11 E0 A1 B1 1A E1 这种 OLE2 复合文档头。判断完再决定用哪个分支去加载能省掉大量诡异异常。参数说明fmShareDenyWrite 表示只读共享文件正被 Excel 占用时也能安全读取这两个字节常量是文件格式规范定的写死在代码里没有问题。确认完格式再走正式加载。主类用 TExcelFile加载和遍历的基础代码var Xls: TExcelFile; i: Integer; begin Xls : TExcelFile.Create(nil); try Xls.LoadFromFile(C:\Data\订单明细.xlsx); for i : 0 to Xls.SheetCount - 1 do begin Xls.SheetIndex : i; // 业务按 SheetName 分支处理 end; finally Xls.Free; end; end;逻辑说明Create(nil) 表示组件不挂接窗体用完手动 Free这个模式在无界面服务里更安全LoadFromFile 根据扩展名和内部解析器识别文件不调用 Office COM循环里 SheetIndex 是当前表单索引切换后用 SheetName 拿名称。参数说明SheetCount 从 0 开始文件里只有一个表单时这个值是 1循环到最后一个索引是 0。组件一般也提供按名索引表单的方法具体名称不同小版本略有差异以资源包内的帮助文档为准。加载过程的异常也要处理。如果文件不是合法 Excel 结构LoadFromFile 会抛出文件头或结构相关的异常。捕获后用 3.1 里的文件头判断函数分诊是伪装文件就走别的读取分支是文件损坏就提示用户重新导出不要用裸异常打在界面上。3.2 单元格坐标模型和类型枚举读完表单读格子。这套组件的坐标模型先列后行从 0 开始和 Excel 的 A1 风格正好相反刚切换时容易把自己绕晕。下面代码扫描当前表单的所有有效单元格并分类型取值var Xls: TExcelFile; r, c: Integer; v: Variant; t: TXlsCellType; begin Xls : TExcelFile.Create(nil); try Xls.LoadFromFile(C:\Data\采样数据.xls); Xls.SheetIndex : 0; for r : 0 to Xls.SheetHeight - 1 do for c : 0 to Xls.SheetWidth - 1 do begin if Xls.CellExists(c, r) then begin t : Xls.CellType[c, r]; case t of xlsCellFloat: v : Xls.AsFloat[c, r]; xlsCellString: v : Xls.AsString[c, r]; xlsCellDateTime: v : Xls.AsDateTime[c, r]; else v : ; end; end; end; finally Xls.Free; end; end;逻辑说明双层循环遍历有效行列范围CellExists 先判断格子存在性避免空单元格走类型判断CellType 拿到枚举后按类型分支取值。参数说明SheetHeight 和 SheetWidth 返回当前表单有效数据边界不是 Excel 里的 1048576 行上限xlsCellDateTime 是日期时间专用类型读出来直接是 Delphi 的 TDateTime可以格式化输出不用手工做浮点转换。这里有一个典型误用有人为了图省事对日期列直接用 AsFloat拿到 44300 这种序列值再自己减 25569、乘 86400 换算结果总有小误差。正确做法就是走类型枚举分支让组件完成内部转换。读文件时如果发现表单名重复或为空遍历时要跳过空名表单这类文件多半是第三方程序生成的不做保护容易在按名索引时崩掉。3.3 稀疏表和大文件的遍历策略Excel 表不保证从 A1 开始连续写。设备软件导出的表经常前两行是空行中间还有整列空列。如果直接按 0 到 SheetHeight 循环会读出一堆空壳逻辑浪费在无意义的判断上。我一般会先取实际占用区域边界再在这个范围内遍历。这个过程组件内部有现成的属性可以用效果等同于 Excel 里的 UsedRange。6.00.16 在解析大文件时会一次性把表格结构读进内存文件越大首次加载越慢但遍历时不再有磁盘 IO。如果目标文件有几十 MB建议在加载前先显示等待状态不要在 UI 线程里同步等。加载放后台线程时要注意组件实例和主线程之间不要共享每次后台任务创建独立实例避免界面卡死和数据错乱。循环里如果对每个单元格都做字符串格式化再拼接日志时间开销会被放大。常见做法是先收集原始值和类型最后统一格式化。读文件的性能瓶颈通常不在组件而在取完值之后的处理代码上。4. 写报表文件新建、格式、公式与大数据量导出参数4.1 新建文件与连续导出的状态重置读是分析已有数据写是造新文件。新建文件的流程和读的最大区别是必须先 NewFile 初始化内存模型否则组件会沿用上一次文件的状态。我见过连续导出两份报表时第二份文件里出现第一份残留数据的案例原因就是没调 NewFile。下面这段是写入几行数据的最小骨架var Xls: TExcelFile; begin Xls : TExcelFile.Create(nil); try Xls.NewFile(xlsx); Xls.SheetName : 采样报告; Xls.SheetIndex : 0; Xls.Cells[0, 0].AsString : 编号; Xls.Cells[1, 0].AsString : 时间; Xls.Cells[2, 0].AsString : 温度; Xls.WriteToFile(C:\Output\report.xlsx); finally Xls.Free; end; end;逻辑说明NewFile 的字符串参数决定文件格式传 xlsx 生成 OOXML 格式SheetName 设置当前表单显示名中文一般可直接写入WriteToFile 把内存模型一次性落盘。参数说明如果目标环境是老版 Excel 2003要改成 NewFile(xls)但 xls 格式行数上限只有 65536 行超出会写不进去。每次导出循环开始前强制调 NewFile把文件状态清干净这是我在连续报表里固定执行的第一行。写文件还容易遇到目标文件被占用。WriteToFile 的目标文件如果正被 Excel 打开Windows 会拒绝写入并抛共享冲突。我习惯先写到一个临时文件落盘成功后再 CopyFile 覆盖目标文件这样既避开占用也防止写一半程序崩溃把原文件搞坏。事后在代码里把这个临时文件路径清理掉避免磁盘堆积。4.2 格式控制字体、颜色、合并、列宽与冻结单元格值写完了报表还差格式。格式模型里每个单元格的 Format 属性持有字体、对齐、背景、边框等子对象。下面这段给表头加了常用样式并做了一个合并单元格Xls.Cells[0, 0].AsString : 序号; Xls.Cells[0, 0].Format.FontName : 微软雅黑; Xls.Cells[0, 0].Format.FontSize : 12; Xls.Cells[0, 0].Format.Bold : True; Xls.Cells[0, 0].Format.BackgroundColor : $00DDEBF7; Xls.Cells[0, 0].Format.Alignment : xlsAlignCenter; Xls.MergeCells(0, 1, 0, 2);逻辑说明FontName、FontSize、Bold 是字体族属性BackgroundColor 是 BGR 颜色顺序这和网页的 RGB 相反写反了颜色会变MergeCells(Col1, Row1, Col2, Row2) 把坐标范围内区域合并。参数说明跨列合并和跨行合并都走同一个方法。合并顺序有一条硬性经验先给范围内每个单元格设置边框格式再调 MergeCells。如果反过来Excel 只保留左上角格式边框会整块消失这个坑在第一次做采集报表时翻过车。列宽和行高在报表里同样常用。列宽用 ColWidth 属性按列设置行高用 RowHeight 按行设置单位分别是字符宽度和磅值。如果表单里有长文本摘要我一般把该列宽度设到 40 到 50中文在 xlsx 里按字符宽度估算设置为 40 大约能显示 20 个汉字。显式设置列宽还能绕开组件默认列宽与 Excel 渲染策略不一致的问题某次交付客户反馈列宽不对就是显式设置后解决的。冻结首行是导出表格的另一个高频需求。组件提供支持冻结行列的属性或方法名称在不同小版本里不完全一致效果等价于 Excel 的冻结窗格。设置时机在写完数据和格式之后、WriteToFile 之前顺序错乱可能导致冻结区域引用错误。页面设置如纸张方向、缩放比例等也有对应属性导出打印报表时统一设置一次避免客户打开后打印排版错乱。4.3 公式、数字格式与缓存结果报表里公式的需求通常来自两类合计行和百分比列。公式赋值走 AsFormulaString传入是 Excel A1 风格公式文本Xls.Cells[4, 5].AsFormulaString : SUM(B2:B5);逻辑说明赋值对象是公式文本组件在 OOXML 里写的是公式定义不会替你计算。Excel 打开后会触发重算如果目标机器关了自动重算打开会显示缓存结果。参数说明公式区域引用是 A1 风格坐标不是内部 Cells 索引公式里带表单名且名称含空格必须写成 采样报告!A1 这种带单引号的形式否则 Excel 会提示公式错误。数字格式和公式不一样它控制显示。日期列常见的做法是给整列设置数字格式串 yyyy-mm-dd hh:mm:ss然后循环只写 AsDateTime 值。格式串放在 WriteToFile 前统一设置避免每个单元格重复触发格式对象分配。xls 和 xlsx 两种格式在能力上有差异选型时对照一下能力xlsxlsx最大行列65536 行 x 256 列1048576 行 x 16384 列中文字符集依赖代码页UTF-8 内建公式复杂度基本公式完整 OOXML 公式文件体积相对小相对大4.4 大数据量导出的节奏与内存释放大数据量导出的核心问题是格式对象数量。5 万行数据如果每行都单独设置边框和背景组件内部要给每个单元格创建独立格式对象时间和内存都上去了。我一般把写值和写格式拆成两遍第一遍循环用 AsString、AsFloat、AsDateTime 写值第二遍按区域统一设置格式而不是逐格设。如果数据源本身有条件判断也可以在写入循环里只在条件成立时设置格式其他行保持默认。关键点是减少格式对象的创建次数而不是减少格式代码行数。另一个容易忽略的是对象释放顺序。TExcelFile 内部持有整个工作簿结构Free 之后才会真正释放内存。在窗口关闭事件里如果只关了窗体没 Free 组件下次打开会出现内存地址持续增长。我一般用 try/finally 保证 Free 一定执行像对待数据库连接一样对待组件实例。5. 避坑排查安装报错、中文乱码与格式丢失的常见现场5.1 安装阶段包版本不匹配与路径残留现象把 6.00.16 的包在 Tokyo 10.2 里编译报 E2382 或提示包版本不一致装不进组件面板。原因资源包明确写的是 for Tokyo 10.2实际 IDE 版本不是 10.2或机器上有旧版组件 dcu 残留在 Library Path编译器优先拉到了旧文件。第三方组件安装 80% 的报错都出在这两类。解决先确认 IDE 版本代号是 Tokyo 10.2再用 msbuild 重编两个包Library Path 里移除指向旧版 XLSReadWrite 的目录清理旧 dcu 后重新 Build。如果换了 IDE 大版本不要指望老 bpl 直接兼容重编是唯一可靠做法。现象bpl 能加载新项目拖组件时报 unit not found。原因Library Path 没加 Source 目录IDE 找不到实现单元。解决把 Source 目录追加到 Library Path 最前面并确认路径无空格无中文。这两条是安装阶段的高频现场处理完基本能通。5.2 读写阶段伪装文件、中文乱码与日期序列值现象LoadFromFile 一个 xlsx读出来全是空表单Excel 打开正常。原因文件实际是 HTML 表格或 CSV 改了扩展名组件按 xlsx 解析自然没有结果。解决加载前读文件头判断真实格式xlsx 以 PK 开头xls 以 D0CF 开头伪装的直接拒掉或换分支读取。判断函数可以复用 3.1 节里的工具代码。现象中文 Sheet 名和单元格中文变成 ?? 或乱码。原因文件声明编码和组件默认字符集不一致。解决加载前把组件编码选项切到 UTF-8先设编码再 LoadFromFile不要在文件打开后再改字符集否则内部解析状态会不一致。现象日期列读出来是 44300 这样的浮点数。原因走了 AsFloat 路径没有触发日期类型转换。解决用 CellType 判断分支xlsCellDateTime 走 AsDateTime写文件时也显式用 AsDateTime 加数字格式串不要用裸浮点拼字符串。5.3 格式类问题合并边框消失与公式引用失效现象合并单元格保存后再打开边框整块消失。原因先合并后设边框Excel 只接受合并区域左上角的格式记录其他区域在合并后失去了独立格式通道。解决先给范围内每个单元格设置边框格式再调 MergeCells。不只是边框填充色和字体也遵循这个顺序。现象公式引用带空格的表单名保存后显示 REF 错误。原因公式文本里表单名没有加单引号OOXML 解析时把表单名当成了单元格引用的一部分。解决按 Excel 规则写成 采样报告!A1不要在代码里手工拼裸名称。如果公式来源是业务侧配置的做一层校验至少替换掉空格外的特殊字符。这五类问题覆盖了从安装到读写的大部分翻车现场。排查顺序建议按安装、读取、格式三步走不要跳过文件头判断直接怀疑组件有 bug。多数情况是文件格式和代码路径的隐性冲突不是组件本身的问题。6. 模板填充实战一次读取模板坐标只写值不改格式模板填充是导出报表时最省事的做法把一份做好的 Excel 当模板代码只负责填值格式由模板文件自己留着。这个思路稳在格式和业务数据彻底分离业务调整时改模板就行代码不用跟着改。操作上分三步走var Xls: TExcelFile; begin Xls : TExcelFile.Create(nil); try Xls.LoadFromFile(C:\Templates\检测报告模板.xlsx); Xls.Cells[0, 2].AsString : 设备编号A-2024-017; Xls.Cells[1, 3].AsInteger : 36; Xls.Cells[2, 3].AsFloat : 24.5; Xls.ExportToFile(C:\Output\检测报告_20241018.xlsx); finally Xls.Free; end; end;逻辑说明模板加载后只往固定坐标写值不碰任何格式对象ExportToFile 直接落盘跳过格式再解析环节。参数说明AsInteger 和 AsFloat 写入时要求目标单元格类型匹配如果模板格子是文本格式用 AsFloat 写进去会被当作字符串存储最后显示出来的是文本而不是数字直接在 Excel 里做后续计算时会出问题。验证导出结果时把生成文件和模板放在同一个 Excel 窗口里对照重点看合并区域、边框和列宽是否一致。坐标要调整时先在模板里数清行列再改代码不要靠肉眼估计。从那以后我每次做报表导出都强制先跑一遍模板流程不再从零堆格式。坐标对了、格式不丢交付现场少一个沟通点。希望帮到你。本文还有配套的精品资源点击获取