Delphi 12.3集成XLSReadWriteII实现Excel报表导出与读写
简介面向Delphi 12.3开发者的XLSReadWriteII控件包用于在应用程序中实现Excel工作簿的读取、写入与格式化处理适合需要导入导出报表、模板填充或批量数据交换的桌面软件开发场景。压缩包为RAR格式共收录499个文件以196个Pascal源文件为核心配合164个HTML函数/属性说明、48个DFM窗体文件以及DPR、DPK工程与项目配置既能查阅单元接口也能参考示例窗体搭建自有界面另有少量资源、截图与文本说明。整体仅2.15MB结构紧凑便于快速定位。资料覆盖图表、自动筛选、发票、监控、网格、透视表等示例场景还涉及单元格样式、数据读写与常用操作流程作者 tjsoft 已针对该版本整理可直接引入工程参考。已有65人浏览学习适合Delphi中高级开发者直接借鉴在较短时间内完成Excel读写功能的集成。1. XLSReadWriteII v6.01.17 D12Delphi 12.3项目里读写Excel的省事方案接到一个用 Delphi 12.3 维护的老桌面项目客户要求把对账单和库存报表直接导成 Excel不能给 CSV也不能弹窗让用户手动另存为。我翻了一圈方案Office OLE 自动化太脆且客户机器不一定装 Office用 Java 或 Web 服务转 Excel 又要多维护一套部署。最后还是回到 Delphi 圈子流传已久的 XLSReadWriteII v6.01.17 D12 这个控件包上——它直接在代码里读写 XLS 和 XLSX运行时不依赖本机 Office客户部署包小很多。但这类控件包不是解压出来拖进 IDE 就能用的D12 后缀对应的是 Delphi 12 平台安装时设计时包、运行时包、Library Path 和项目平台的匹配缺一不可。这篇笔记按我实际踩过的顺序写怎么装、最小代码怎么写、哪些参数决定报表能不能直接交付、哪几个坑最容易让人在深夜怀疑人生。2. 在Delphi 12.3里装好XLSReadWriteII解压RAR、设置库路径、让组件出现在面板上2.1 先看懂RAR包结构D12后缀、设计时包、运行时包三者的分工拿到XLSReadWriteII v6.01.17 D12.rar先别急着双击解压先看文件名里的 D12 是什么意思。它表示这份控件包是面向 Delphi 12 编译或源码适配的。我一般解压到固定目录比如D:\Components\XLSReadWriteII_6_01_17_D12避免把控件丢到 C 盘 Program Files 下Windows 的 UAC 权限会让你后面装包时莫名其妙失败。解压后通常会看到 source 目录、packages 目录和 docs 目录。packages 里往往按 Delphi 版本编号建了多个子目录里面放着编译产物bpl 文件是 Delphi 的运行时/设计时包dcp 是包编译后的描述文件dcu 是单元编译产物。这里最容易犯的错误是分不清设计时包和运行时包。命名上带 dcl 前缀的比如 dclXLSReadWriteII6D12.bpl是设计时包不带 dcl 的是运行时包。运行时包承载控件的真实实现编译和运行程序时需要设计时包只负责让控件出现在 IDE 的组件面板上方便你拖拽到窗体。如果 rar 里没有 bpl/dcu只有 .pas 源码那就得用 Delphi 自己编译包再安装步骤多一步但原理一样。我拿到包后第一件事就是打开 packages 目录看有没有现成的 bpl有就省事直接进入 2.2 的安装流程没有就去 Project Options Packages 里新建包项目编译这里不展开因为大多数流传的 D12 版本都是预编译好的。2.2 在IDE里安装bpl的完整步骤Library Path、Runtime Packages 两个入口都要设置我按 Delphi 12.3 实际界面顺序说。第一步是 Tools Options Environment Options Delphi Options Library在 Library Path 里加上解压目录下的 source 目录和 bpl/dcp 所在目录。路径建议用绝对路径两个目录都要加source 目录让 IDE 能找到单元文件bpl 目录让 IDE 能找到已编译的包。第二步是 Component Install Packages点击 Add 按钮定位到 packages 下 D12 子目录里带 dcl 前缀的 bpl 文件选中后点 Open。这时组件面板底部会多出一个分类里面就是 XLSReadWriteII 系列控件。如果 Add 时报“无法加载指定的包”先别怀疑包损坏九成是运行时 bpl 所在的目录没加到 Library Path或者当前 IDE 平台Win32/Win64与 bpl 编译平台不一致把对应平台目录加进 Library Path 再重试一次。第三步是 Project Options 里的 Runtime Packages 设置。如果项目启用了运行时包需要确认 XLSReadWriteII 的运行时 bpl 名出现在项目依赖里。我的习惯是干脆把项目 Runtime Packages 关掉全部静态链接这样客户机器上不用拷一堆 bpl运行环境干净很多。注意Library Path 是按平台分开保存的。Win32 配置里加的路径不会自动出现在 Win64 配置里改完路径后一定要确认当前 Project 的 Target Platform 是你要编译的平台否则会出现“IDE 里能看到控件编译时却找不到 dcu”的诡异问题。2.3 验证安装拖一个控件到窗体并跑通空项目的编译装完别急着写业务代码先做最小验证。新建一个 VCL 项目从组件面板把 TXLSReadWriteII2 拖到窗体上然后直接编译运行。能通过说明运行时包没问题。如果拖控件时提示“无法创建类”要么是设计时包没注册成功要么是 Library Path 里的 dcu 目录不对。我会按下面这个顺序排查不跳步检查项预期结果失败时看哪里Library Path 包含 source 目录单元文件能解析Project Log 里的 Path not found 提示Library Path 包含 bpl/dcp 目录运行时包能找到操作系统弹窗提示找不到 bplInstall Packages 已添加 dcl 包组件面板出现控件Add 包时弹出的异常信息项目平台与控件包平台一致编译链接通过dcui/dcu 缺失或版本不匹配提示这一步做完组件面板稳定后续功能调试才不是盲人摸象。我见过有人在组件没真正可用的情况下直接写大量代码最后报错分不清是控件没装好还是代码问题血泪教训。3. 最小读写代码TXLSReadWriteII2 对象的API用法与参数说明3.1 区分TXLSReadWriteII与TXLSReadWriteII2旧格式与新格式的对象边界XLSReadWriteII 套件早期只有 TXLSReadWriteII 这个对象主要面向老式 XLS 格式。到 v4 之后才分离出 TXLSReadWriteII22 代对象在 Sheet、单元格、样式管理上做了重构也是之后新功能的主要载体。v6.01.17 这个版本里项目里要写 xlsx 优先选 TXLSReadWriteII2 或 TXLSReadWriteII21二者差异在于对新格式细节的覆盖程度日常导出 xlsx 用 TXLSReadWriteII2 足够要兼容老系统只能用 XLS 格式时再考虑 TXLSReadWriteII。这个选择会影响后面每一行代码。两个对象的属性访问方式不同TXLSReadWriteII 用XLS[0]访问工作表而 TXLSReadWriteII2 用XLS.Sheets[0]单元格类型名、样式索引体系也有差异。我见过有人在同一个项目里混用两种对象写 XLS 用一个、写 XLSX 用另一个后期维护等于同时维护两套 API 心智模型非常容易翻车。新起的项目直接用 TXLSReadWriteII2默认输出 XLSX老格式只有在客户系统明确要求时才降级。3.2 写XLSX的最小代码建Sheet、写单元格、保存新项目里我从不一上来就封装通用导出类而是先写一个最小可跑通的例子确认控件环境正常后再往外扩。下面这段就是那个“Hello World”运行时创建对象不依赖窗体上的可见控件。var XLS: TXLSReadWriteII2; begin XLS : TXLSReadWriteII2.Create(nil); try // 生成一个Sheet并命名 XLS.Sheets.Count : 1; XLS.Sheets[0].Name : 订单报表; // 写入表头 XLS.Sheets[0].Cells[1, 1].AsString : 订单号; XLS.Sheets[0].Cells[1, 2].AsString : 客户; XLS.Sheets[0].Cells[1, 3].AsString : 金额; // 写入一行数据 XLS.Sheets[0].Cells[2, 1].AsString : SO-20240901-001; XLS.Sheets[0].Cells[2, 2].AsString : 某制造企业; XLS.Sheets[0].Cells[2, 3].AsFloat : 12800.50; // 先设置文件名再调用Write XLS.FileName : D:\temp\order_export.xlsx; XLS.Write; finally XLS.Free; end; end;单元格的行列参数是[行, 列]从 1 开始不是从 0 开始这一点第一次写的人几乎都会反一次。AsString、AsFloat、AsInteger、AsDateTime 是赋值的四个基本类型接口不需要先“创建”单元格控件内部用稀疏结构管理赋值即存在。Create(nil) 创建出来的对象用完 Free 即可不需要挂到窗体上。FileName 与 Write 的配合是关键先设置 FileName 再调用 Write控件根据文件后缀决定写 XLS 还是 XLSX。如果后缀是 .xls 而对象是 TXLSReadWriteII2虽然也能生成文件但部分新特性会丢失。我习惯在模块注释里写死“本模块输出格式为 XLSX”防止后来者图省事把后缀改成 .xls 引发隐性格式问题。提示如果同名字的文件正被 Excel 占用再调用 Write 会抛共享冲突。导出逻辑里最好先判断目标文件是否存在存在就 DeleteFile或者把输出文件名带时间戳避免这类偶发问题。3.3 读XLSX的最小代码遍历Sheet、空值判断与日期还原读取场景通常是把第三方发来的 Excel 数据解析后入库。最小读法如下var XLS: TXLSReadWriteII2; i, j: Integer; v: TXLSCellValue; begin XLS : TXLSReadWriteII2.Create(nil); try XLS.FileName : D:\temp\订单导入模板.xlsx; XLS.Read; // 遍历每一个Sheet for i : 0 to XLS.Sheets.Count - 1 do begin // LastRow拿实际最后一行比写死100靠谱 for j : 1 to XLS.Sheets[i].LastRow do begin // 用Value拿TXLSCellValue先判空再读 v : XLS.Sheets[i].Cells[j, 1].Value; if v.IsEmpty then Continue; case v.CellType of cValString: Memo1.Lines.Add(v.AsString); cValNumber: Memo1.Lines.Add(FloatToStr(v.AsNumber)); end; end; end; finally XLS.Free; end; end;这里三个参数要点值得单独说。第一空值判断用v.IsEmpty不要用Cells[j, 1].AsString 判断数值型空单元格读出来是 0AsString 会返回 0你会把空行当脏数据写进库里。第二日期在 Excel 底层是序列号读出来是浮点数需要自己用EncodeDate相关的换算逻辑转成 TDateTime不能指望 AsString 自动给格式化日期。第三遍历范围用LastRow而不是写死行数写死要么漏数据要么多余空转真实项目里 Excel 动不动有一堆残留格式的空行LastRow 是控件算好的实际数据边界用它最稳。这段代码在一般业务场景够用几千行数据量对速度没有压力。真正的大批量写入是第四章要处理的问题。4. 让导出的Excel像业务人员手工做完的样式、公式、合并单元格与页面参数设置4.1 单元格样式字体、边框、填充色和数字格式通过Style索引挂接XLSReadWriteII 的样式体系是“样式表 单元格引用索引”的模式。你要先把样式定义好再把样式索引赋给指定单元格。下面这段是给表头加粗体、底色和边框的写法var St: TXLSStyle; Idx: Integer; begin // 新建一个样式 XLS.Sheets[0].Styles.Add; St : XLS.Sheets[0].Styles[XLS.Sheets[0].Styles.Count - 1]; St.Font.Name : 微软雅黑; St.Font.Size : 10; St.Font.Style : [xfsBold]; St.BackgroundColor : $DDEBF7; St.Border.Top.Line : xblThin; St.Border.Bottom.Line : xblThin; St.Border.Left.Line : xblThin; St.Border.Right.Line : xblThin; // 把样式索引赋值给第1行的表头单元格 Idx : XLS.Sheets[0].Styles.Count - 1; XLS.Sheets[0].Cells[1, 1].StyleIndex : Idx; XLS.Sheets[0].Cells[1, 2].StyleIndex : Idx; end;关键在 StyleIndex 的使用习惯样式不是越细越好而是越少越好。表头一个样式、数据区一个样式、合计行一个样式三五个样式覆盖整个报表就够了。如果循环里每写一个单元格都新建样式生成的 xlsx 内部会积累大量冗余样式记录文件体积变大Excel 打开速度明显变慢。数字格式单独说。要显示千分位、保留两位小数不需要自己拼字符串设置St.NumberFormat : #,##0.00即可。写入时仍然用 AsFloat 赋数值Excel 按样式显示成格式化文本排序、求和都保持数值属性。这里的坑在于 NumberFormat 字符串是 Excel 语法控件只负责存进去不负责校验写错了 Excel 不报错单元格里直接显示原始数字。所以设置完数字格式我一定在 Excel 里打开看一眼效果不靠代码自检。4.2 列宽、行高、合并单元格与冻结窗格布局参数的归属对象导出报表时最常被问的是布局参数。先把归属理清楚列宽属于 Sheet 的 ColumnWidths 数组行高属于 RowHeights 数组合并单元格和冻结窗格由 Sheet 直接管理。// 设置列宽下标从1开始 XLS.Sheets[0].ColumnWidths[1] : 12; XLS.Sheets[0].ColumnWidths[2] : 16; XLS.Sheets[0].ColumnWidths[3] : 14; // 设置行高 XLS.Sheets[0].RowHeights[1] : 22; // 合并第1行第1列到第1行第5列 XLS.Sheets[0].MergeCells[1, 1, 1, 5]; // 冻结前2行和前1列 XLS.Sheets[0].FreezePane(2, 1);MergeCells 的参数顺序是[起行, 起列, 终行, 终列]先前后后。FreezePane 的参数是(冻结行数, 冻结列数)跟 Excel 界面里“冻结首行”“冻结首列”的点击顺序不一样记反了冻结区域会错位。我在封装导出服务时固定写注释“FreezePane(行数, 列数)”避免几个月后回看代码还要翻文档。列宽和行高的单位也不同列宽是字符宽度行高是磅值。列宽设 10 觉得太窄设 20 又太宽原因是被 Excel 的实际字体渲染影响不是线性关系。务实做法是给列宽留 15%-20% 的余量比反复试尺寸更符合用户视觉习惯。4.3 写公式、插入图片、设置打印区域这三个参数做到位才算可交付业务报表不能只有数据通常还要合计行、Logo 图和打印区域。公式直接在属性里赋字符串// 写合计公式 XLS.Sheets[0].Cells[20, 1].AsString : 合计; XLS.Sheets[0].Cells[20, 3].Formula : SUM(C2:C19);Formula 赋值后控件记录的是公式文本Excel 打开后会自动计算。但有个隐藏坑如果这个文件还要被另一个程序解析公式单元格读出来可能是空值因为公式结果只有 Excel 重算后才会固化。稳妥做法是在业务约定里写明“文件需经 Excel 打开后二次校验”或者导出后用 COM 跑一次重算——既然用了这个控件就是为了避免 COM那就在流程上约定清楚。插入图片走 Images 集合。v6.01.17 里不同小版本的 API 偶有差异方法名可能是 AddFromFile 或 AddPicture 之类写之前先打开单元源码看一眼 Images 类型的公开方法不要凭网上旧代码照抄。位置参数一般按左右上下的像素坐标传入可以从单元格坐标用函数换算。打印区域和页眉页脚是低成本提升交付观感的设置XLS.Sheets[0].PageSetup.PrintArea : A1:H20; XLS.Sheets[0].PageSetup.PageHeaderCenter : 订单对账单; XLS.Sheets[0].PageSetup.PageFooterCenter : 生成时间2024-09-01;PageSetup 的设置只有在打印预览时可见代码不报错不代表生效。我自己的习惯是写完导出代码后一定拿真实数据跑一遍并打开生成的 xlsx 看三样东西——表头格式、合计行公式、打印预览三样过了才交给业务方。5. XLSReadWriteII 6.x在Delphi 12.3下的避坑指南5条高频翻车现场与排查路径5.1 明明装了包运行时报“TXLSCell 类型未注册”或“类不存在”现象IDE 里能拖控件编译也通过但运行到创建 XLS 对象那一行抛异常提示 Class not found 或 Access Violation。原因有两种一是设计时包注册了运行时包没被当前项目引用程序在运行期拿不到控件实现二是项目启用了 Runtime Packages但包列表里没有加 XLSReadWriteII 的运行时 bpl。解决在 Project Options Runtime Packages 里检查运行时包列表把不带 dcl 前缀的 bpl 加进去或者干脆关掉 Runtime Packages 改静态链接。给客户机器部署的程序我建议直接静态链接省去拷贝一堆 bpl 的麻烦也杜绝版本冲突。5.2 生成的 xlsx 在 Excel 里提示“文件已损坏是否尝试修复”现象导出程序运行正常双击 xlsx 却弹出修复对话框。原因最常见是 FileName 后缀与实际写入格式不匹配。比如 TXLSReadWriteII2 内部默认写 XLSXFileName 却写成 .xls或者反过来用 XLS 模板生成文件却给了 .xlsx 后缀。Excel 打开时会做格式嗅探嗅探失败就报损坏。另一个低频原因是 FileName 在 Write 之后才赋值第一次写入时控件不知道目标格式生成临时文件结构不完整。解决固定顺序是先赋值 FileName再调用 Write。同一个对象重复导出时每次 Write 前重新设置 FileName。如果已经生成问题文件用压缩软件直接打开 xlsx 看内部目录结构是否完整也能帮助定位是哪个环节出了问题。5.3 中文内容变成乱码或者单元格里显示成现象中文内容在 Excel 里显示成“鏉庢櫊”之类的乱码字符数值列显示成 ####。原因乱码通常和字符集有关。Delphi 12 的 string 是 UTF-16如果直接把一个 AnsiString 赋给 AsString 且没有做编码转换Excel 侧读到非法字节序列就会乱码。而 #### 根本不是乱码问题是列宽太窄数字显示不下。解决写入时统一用 UnicodeString赋值前先确认字符串变量内部编码是 UTF-16。老系统导出的 GBK 文本要先用TEncoding.GetEncoding(936)转成 UTF-16 再赋给单元格。列宽问题直接调大 ColumnWidths和字符集无关别混在一起排查。5.4 写入上万行数据速度慢到无法接受现象循环写 3 万行乘 20 列的数据运行十几秒用户明显感觉到卡顿。原因最常见的是每写一个单元格都重新设置样式或者赋值后频繁访问 StyleIndex 触发内部状态刷新。XLSReadWriteII 单行写入本身不慢慢的是样式索引的频繁切换单元格每换一个样式索引文件内部都要记录一次样式变化。解决先建好全部样式循环体内只做 AsString/AsFloat 赋值StyleIndex 只对表头行、合计行这类特殊行设置。如果数据量更大把数据先整理到二维数组再调用批量写入接口导出。我把逻辑改成“先建样式、后整块写数据”后3 万行导出时间从 14 秒降到 2 秒左右这个优化顺序比什么技巧都管用。5.5 Win64 项目下编译报“Undeclared identifier: TXLSReadWriteII2”现象Library Path 检查过没问题设计时包也装了但项目里编译时报 TXLSReadWriteII2 未声明。原因这多半不是控件没装好而是项目平台配置问题。RAR 包如果只带了 Win32 编译的 dcu/bplWin64 项目编译时找不到 Win64 的 dcu类型名自然就不认。解决在 Project Options 里把目标平台切成 Win32 先跑通如果必须出 Win64 版本找压缩包里有没有 Win64 子目录把对应 dcu 路径加到 Win64 配置下的 Library Path 里。切记 Library Path 是按平台分的不能把 Win32 的路径复制到 Win64 配置里就当完事。这个坑让我当年熬了一整晚最后发现只是少加了一个平台的目录。6. 进阶读模板填数据把XLSReadWriteII封装成通用导出服务用的时间越长我越觉得导出功能最值得沉淀的不是某一处代码而是一套“模板驱动”的用法所有格式在 Excel 模板里做好代码只负责填数据。下面的过程封装了一个通用导出入口procedure ExportFromTemplate(const AData: TDataSet; const ATemplateFile, AOutputFile: string); var XLS: TXLSReadWriteII2; r: Integer; begin XLS : TXLSReadWriteII2.Create(nil); try // 读模板保留全部样式、页面设置、打印区域 XLS.FileName : ATemplateFile; XLS.Read; // 约定模板第1行是表头数据从第2行开始 AData.First; r : 2; while not AData.Eof do begin XLS.Sheets[0].Cells[r, 1].AsString : AData.FieldByName(OrderNo).AsString; XLS.Sheets[0].Cells[r, 2].AsString : AData.FieldByName(Customer).AsString; XLS.Sheets[0].Cells[r, 3].AsFloat : AData.FieldByName(Amount).AsCurrency; Inc(r); AData.Next; end; // 另存为新文件模板文件保持干净 XLS.FileName : AOutputFile; XLS.Write; finally XLS.Free; end; end;这套做法有几个约定要先定好模板第一行永远是表头数据从第二行开始数据区的表头不要放在合并单元格里否则按行列定位会错位模板里可以留一个专门的“填写说明”Sheet给业务方写规则导出时按 Sheet 名忽略掉即可。格式调整需求频繁时只改模板文件代码一行不用动。表头底色、边框、页眉页脚、打印区域全部在模板里固定代码维护成本降到最低。这招把“导出样式改崩代码”的翻车概率直接降到了零——模板是业务方自己改的代码逻辑根本不受影响。希望帮到你。本文还有配套的精品资源点击获取