简介:DevExpress ExpressSpreadSheet v1.38 for Delphi/BCB 完整源代码,面向需要集成类似Excel电子表格能力的桌面应用开发者。使用该组件可快速实现数据录入、公式计算、图表展示与格式排版,适用进销存、报表生成、数据看板等场景,也适合中高级Delphi、C++ Builder程序员深入研究组件内部机制。资源包共382个文件,体积仅2.81MB,主要包括73个pas、26个dfm等Delphi源码与窗体定义,45个dpk及24个bpk包工程,38个cpp、10个h等C++混合代码,还有83个res、8个bmp界面资源和6个xls示例表格,目录组织清楚,便于按模块定位。目前已有446人学习下载。源码完整覆盖电子表格引擎、公式解析、数据绑定、样式渲染等核心模块,并关联ExpressGDI+绘图支持、ExpressLibrary通用基础库和XP主题管理器,附带的CustomFunction、FeaturesDemo等演示工程,可直接观察自定义函数、单元格绘制、图表交互与系统主题适配的具体实现,为二次开发或学习DevExpress架构提供了高质量参考,无论是直接编译使用还是借鉴其设计模式,都有很强的实践价值。
1. 这套源码包是什么:老项目接盘时为什么它比安装盘更可靠
如果你被拉去接手一个放着多年的遗留工程,多半见过这种场面:IDE 重装后,工程里十几个第三方控件全部变红,编译报错一屏接一屏,其中就有找不到 dxSpreadSheet 单元之类的错误。标题里的 DevExpress.ExpressSpreadSheet.v1.38.for.Delphi.BCB 源代码,就是当年项目买下后留在代码库里的一套完整源码,里面通常有 pas 源文件、dfm 资源、dpk 与 cbpk 包定义。这篇文章讲的是怎么把这套源码重新编译进 Delphi 和 C++ Builder,让控件回到组件面板、工程恢复编译,同时把最常见的几个坑避开。适合手上握着老工程但安装盘早已丢失、或者想读源码改控件默认行为的开发者,按这篇文章走完,能省下大半天的试错。
2. 编译前先摸清环境:包依赖、IDE 版本和路径规划
2.1 为什么这套源码值得自己编一次,而不是找现成的 DCU
很多人的第一反应是去网上找一个编译好的 dcu 或 bpl 塞进 Lib 目录,这确实能解决“编译报错找不到单元”的问题,但副作用很隐蔽。DCU 是和编译器版本强绑定的产物,Delphi 7 编出来的 dcu 放到 Delphi XE 里,轻则报 “Unit was compiled with a different version”,重则直接 Fatal Error。更麻烦的是,工程里其他第三方控件也在等某个特定版本的公共单元,你换一个 dcu 就可能牵连别的包一起崩。
而源码包的价值在于它把所有 pas 和 dfm 都摆在你面前,你可以用当前 IDE 重新编出一套完全匹配的 dcu 和 bpl,还能顺手改掉源码里的默认行为。我一般拿到这种包的第一件事不是急着编,而是先确认工程当年用的 IDE 主版本。v1.38 这个年代的东西,大概率是配合 Delphi 5/6/7、BCB 5/6 这一代环境,编译模式和新版 IDE 的包机制不完全一样,后面有单独的章节讲兼容性坑。
另外,源码包也给了你排查问题的能力。编译期报错能精确到某个 pas 文件的某一行,运行期控件行为不对也能直接跟进去看属性初始化逻辑。相比之下,装一个现成的发布版像在用黑匣子,出了问题只能靠猜。
2.2 编译前置三件事:IDE 版本、Library 路径、公共依赖
安装这套源码前,先做三件准备,缺一件后面都会翻车。
第一件是确认 IDE 版本。Delphi 7 编出来的 bpl 不能装进 Delphi 2010,BCB 6 编出来的包也不能给 BCB 2009 用。原因是每个 IDE 的 RTL/VCL 版本不同,包在加载时要和 IDE 自身的运行库做符号匹配,版本差太远会直接拒绝加载。所以先打开 IDE 的 About 窗口,把主版本号记死,后面所有编译参数都跟着这个版本走。
第二件是规划 Library 路径。VCL 包编译的基本逻辑是:dpk 源文件经过 dcc32 编译,产出 bpl 运行期包和 dcu 中间产物;IDE 安装包时加载 bpl,并把 dcu 路径写进 Library Path 供工程引用。这里最容易犯的错是路径里混着多个版本的源码目录,编译器搜路径时先撞见旧 dcu,导致你改了源码却不生效。我一般会单独建一个干净的目录放这套包的源码,编译输出也固定到一个 Bin 目录,避免和系统 Lib 混在一起。
第三件是查清公共依赖。ExpressSpreadSheet 这一系控件并不是孤立的,它依赖 DevExpress 自己的一套公共基础库,常见的是 dxCore、dxCommon 这类单元。如果源码包里带了这些公共单元的 pas 文件,那一起编译即可;如果包里没有,就得先把公共库编译安装好,再回来编这张表。判断方法很简单,看源码根目录下有没有 dxCore.pas 或类似文件,没有就用 IDE 的搜索功能全盘找一遍。
cd /d C:\DevExpress\ExpressSpreadSheet.v1.38 dir /s /b *.pas > pas_list.txt findstr /i "dxCore dxCommon" pas_list.txt第一条 dir 命令把源码目录下所有 pas 文件列出,输出到文件里。第二条 findstr 在清单里搜公共依赖单元名,能快速确认依赖是否齐备。如果你搜到的是空结果,就去公共库源码目录里再搜一轮,仍然没有就说明这套包还缺前置组件,得先补上。注意,pas 文件名和单元名不完全一致,文件名是 dxCore.pas,单元名通常是 dxCore,findstr 搜文件名就够了。
2.3 包文件类型速览:dpk、bpl、dcu、cbpk 各自干什么
在动 dcc32 之前,先把源码目录里那几类文件认清楚。很多人一进目录看到几十个文件就头晕,其实按扩展名分就四类事。
| 文件后缀 | 作用 | 出现在哪个阶段 |
|---|---|---|
| .dpk / .dpkw | Delphi 包的源码工程定义,.dpkw 是设计期包 | 编译入口 |
| .cbpk | C++ Builder 的包工程定义 | BCB 编译入口 |
| .pas / .dfm | 源码和窗体资源,dfm 是控件属性的设计期描述 | 编译输入 |
| .bpl / .dcu / .lib / .obj | 编译产物,bpl 是运行期包,dcu 是 Delphi 中间码,lib 和 obj 是 BCB 的导入库和中间文件 | 编译输出 |
简单理解:dpk 是给 Delphi 看的“编译任务书”,cbpk 是给 BCB 看的;pas 是本体,dfm 描述控件在 IDE 里长什么样;编译完成后 bpl 是你要装进 IDE 的东西,dcu 给工程链接用,lib 给 BCB 链接用。安装这套控件的本质,就是把这些文本和资源文件变成 IDE 能加载的二进制包,并注册到组件面板上。
我习惯在编译前把所有旧 dcu、旧 bpl 先清一遍,只保留源码。因为残留的旧物会让编译器优先使用陈旧产物,造成“代码改了但行为没变”的假象,这种玄学问题最浪费时间。
3. 在 Delphi 里编译安装:dcc32 命令行与组件面板注册
3.1 用 dcc32 命令编运行期包:先清理再全量重编
Delphi 安装三方控件的标准路径是:先编译运行期包得到 bpl,再编译设计期包并注册到组件面板。运行期包负责控件运行时的所有逻辑,设计期包负责在 IDE 里显示图标和属性编辑器。两步缺一不可,只装运行期包的话组件面板上什么都不会出现。
命令行编译的好处是可控、可重复,而且能看到完整报错。打开 cmd,切到源码目录,执行下面这段:
cd /d C:\DevExpress\ExpressSpreadSheet.v1.38 del /s /q *.dcu dcc32 -B -Q -DDELPHI7 -U"C:\DevExpress\Source;C:\DevExpress\Bin" -R"C:\DevExpress\Source" -L"C:\DevExpress\Bin" ExpressSpreadSheet_D7.dpkdel 命令先清掉目录里所有 dcu,避免缓存干扰,这个动作很关键。dcc32 的是 Delphi 的编译器,-B 表示全量重编译,忽略已有的中间产物;-Q 是安静模式,只输出错误和警告,不刷屏;-DDELPHI7 是一个条件编译开关,告诉源码“我现在用的是 Delphi 7”,如果你在别的版本下编,要把这个宏改成对应版本,否则源码里的一些版本分支代码不会走入正确路径。-U 指定单元搜索路径,编译器靠它找到 pas 和 dcu,第一段路径是源码目录,第二段是公共库输出目录;-R 是资源搜索路径,让编译器能找到 dfm 和 res;-L 是输出目录,编出来的 bpl 和 dcu 会落到这里。
这里特别提醒:-U 的顺序有讲究,越靠前的路径优先级越高。如果你把公共库目录写在前面,而那里恰好有一份旧 dxCore.dcu,编译结果可能混入不匹配的版本。我都是把当前源码目录放第一位,并且保证目录里没有旧产物。
编译成功后,在 Bin 目录下会看到生成好的 ExpressSpreadSheet_D7.bpl。如果这一步报错,先检查 -U 路径是否包含依赖的所有公共单元目录,九成以上的失败都出在这个参数上。
3.2 设计期包安装:让控件出现在组件面板上
运行期包编译通过后,接着处理设计期包。很多老版本的源码包里同时带 dpk 和 dpkw 两种包文件,后者的“w”代表 design-time,也就是设计期专用的包。找到对应的 dpkw 文件,用同样的方式编译:
dcc32 -B -Q -DDELPHI7 -U"C:\DevExpress\Source;C:\DevExpress\Bin" -R"C:\DevExpress\Source" -L"C:\DevExpress\Bin" ExpressSpreadSheet_D7.dpkw编译参数和上一个命令一致,唯一的区别是入口文件从 dpk 换成了 dpkw。设计期包编译完成后,打开 Delphi IDE,进 Component > Install Packages 对话框,点 Add,在 Bin 目录里选中刚才生成的 bpl。安装成功后,组件面板上会出现一个新页签,名字一般是 ExpressSpreadSheet 或类似叫法,拉一个控件到窗体上能正常显示,就说明注册成功。
如果你的源码包里没有 dpkw 文件,常见做法是找以 Reg 或 Design 开头的单元,比如 dxSpreadSheetReg.pas,它里面包含 RegisterComponents 过程。手动写一个空 dpk 把设计期注册单元编进去也是可以的,但要小心别把运行期和管理期逻辑混在一个包里,否则安装时会报 “Package contains both runtime and design-time units” 的警告。
3.3 编译完成后先跑一个小工程验证
包安装成功不代表万事大吉,工程里引用这套控件时的编译路径可能还有问题。我习惯立刻建一个最小工程验证,只放一个窗体和一个控件,编译通过后再打开老工程,避免在几千行的老工程里排查环境问题。
// MiniSpread.dpr:最小验证工程 program MiniSpread; uses Vcl.Forms, dxSpreadSheet in '..\Source\dxSpreadSheet.pas'; // 路径替换成你实际的源码位置 var Form: TForm; Sheet: TdxSpreadSheet; // 类名以你源码里的 pas 声明为准 begin Application.Initialize; Application.CreateForm(TForm, Form); Form.Caption := 'ExpressSpreadSheet 验证窗口'; Form.Width := 800; Form.Height := 600; // 创建控件实例并铺满窗体 Sheet := TdxSpreadSheet.Create(Form); Sheet.Parent := Form; Sheet.Align := alClient; // 老版本 API 的单元格写法差异很大,注释掉演示代码,编译通过即可 // Sheet.Cells[0, 0].AsString := 'Hello'; Application.Run; end.这段代码里 uses 子句直接引用了源码目录里的 dpp 文件,这样一来编译器会强制重新编译这个单元,不受 Lib 路径里旧 dcu 的影响,能更准确判断源码本身有没有编过。TdxSpreadSheet 的类名,以你手上的 pas 文件里的声明为准,不同小版本的控件类名可能带后缀,我见过不少把类名写错导致编译失败的案例。代码能编译、窗体能跑起来,就证明 dcu 路径、bpl 注册、资源文件三项都正常,这时候再去打开老工程,心里就有底了。
4. 在 C++ Builder 里编译:BCB 与 Delphi 包的桥接方式
4.1 BCB 不能直接编译 pas,它靠 hpp 和 lib 桥接
C++ Builder 的工程文件后缀是 cbpk,但你打开它编译时,BCB 实际干的活是把 C++ 代码和 Delphi 产物的导入库做链接。BCB 的编译器 bcc32 并不直接吃 pas 文件,它需要三个东西配合:bpl 运行期包、lib 导入库、hpp 头文件。lib 文件记录了 bpl 里所有可用的导出符号,hpp 告诉编译器这些符号的 C++ 声明长什么样。
这套机制对没接触过 BCB 的 Delphi 开发者来说就是个黑匣子,很多人直接在 BCB 里尝试添加 pas 文件,结果编译器报错完全不认识这个语法。正确思路是:要么用源码包里自带的 cbpk 在 BCB 里整个编译一次,要么先让 Delphi 编出 bpl,再用 BCB 的工具生成配套的 hpp 和 lib。
源码包里如果直接带 cbpk 文件,优先用这个入口,因为它把包依赖和链接顺序都定义好了,省去手工配置的麻烦。如果只有 dpk 文件,可以先用第 3 章的 Delphi 编译器把包编出来,然后打开 BCB 的命令行工具,用 implib 命令从 bpl 生成导入库。这个顺序反了不行,先有 bpl 才能有 lib。
4.2 用 bcc32 编译 cbpk 包:参数怎么设
拿到 cbpk 文件后,在 BCB 的命令行环境里执行编译。BCB 6 的命令行工具路径一般在 RAD Studio 安装目录的 bin 下,打开时它会自动配置好环境变量,直接敲命令即可。
cd /d C:\DevExpress\ExpressSpreadSheet.v1.38 bcc32 -B -I"C:\DevExpress\Source;C:\DevExpress\Include" -L"C:\DevExpress\Lib;C:\DevExpress\Bin" ExpressSpreadSheet_BCB6.cbpkbcc32 是 BCB 的 C++ 编译器,-B 表示重建所有依赖项,和 dcc32 的 -B 意思一致。-I 指定头文件和源文件的搜索路径,注意 BCB 里源文件既包含 .cpp 也包含 .hpp,编译器要靠这条参数找到它们;-L 指定库文件路径,包括 .lib 和 .obj 的搜索位置。cbpk 文件里已经写好了包内含哪些单元,编译器会按文件里的清单逐个编译并链接。
这条命令跑完,会在输出目录里生成 bpl、lib 和 hpp 三样东西。如果编译中途报找不到头文件,检查 -I 路径是否包含源码根目录,以及公共库的头文件目录。如果报链接错误,检查 -L 路径里能不能找到依赖的 lib,特别是公共库的导入库。这一步常见的翻车点是只配了 -I 没配 -L,结果编译过了、链接挂了。
4.3 在 BCB 工程里引用控件:include 和链接配置
包编译完成后,还要让 BCB 的组件面板认识它。操作路径和 Delphi 一样,在 IDE 里进 Component > Install Packages,Add 刚才生成的 bpl。注意 BCB 只会加载符合当前 IDE 版本要求的包,版本不对会直接拒绝,这个在避坑章节详细说。
然后在你的工程里引用这控件,需要三行:
#include <vcl.h> #include "dxSpreadSheet.hpp" // 头文件名以源码包实际生成为准 #pragma link "ExpressSpreadSheet.lib" // 导入库,告诉链接器去哪个lib找实现 TdxSpreadSheet *Sheet = new TdxSpreadSheet(Form); Sheet->Parent = Form; Sheet->Align = alClient;第一行引入 VCL 基础库,第二行引入控件头文件,第三行是关键,它告诉链接器把 ExpressSpreadSheet.lib 里的符号链接进最终程序。很多人忘了写 #pragma link,编译阶段一切正常,到链接阶段报一堆 unresolved external,就是这个原因。类名后的*是 C++ 指针语法,代表对象在堆上创建,new 出来的实例记得在窗体销毁时释放,老版本控件有的不支持自动释放。
头文件的搜索路径在 BCB 的 Project Options > Directories/Conditionals 里配置,把源码目录和 Include 目录加进去,不然编译器找不到 hpp。做完这些,拖一个控件到窗体上,能看到设计期预览,工程编译链接通过,BCB 这条线就算通了。
5. 避坑指南:v1.38 源码编译安装中的 5 个常见问题
5.1 编译期避坑:公共依赖缺失与缓存污染
第一个坑是找不到公共单元。现象很直接,执行 dcc32 或 bcc32 后,编译器报 Fatal: File not found: 'dxCore.dcu'。原因是这套控件的源码里 uses 了公共库单元,而 -U 或 -I 路径里没有指到它们所在的目录。解决方式是把公共库目录路径加进 -U 参数,如果公共库本身也是源码,先把它编译一次。这里有个细节:公共库编译后的 dcu 目录和当前源码目录最好分开,不然两套东西混一起,后面升级时又是一笔烂账。
第二个坑是缓存污染导致的“改了源码没反应”。现象是你明明在某段实现里加了代码、重新编译了包,IDE 里跑起来还是老行为,翻车了还不知道为什么。原因分析下来,大半是搜索路径里存在旧 dcu,编译器的搜索顺序让你新编的 dcu 根本没被用上。解决方式是先扫一遍路径里所有相关旧产物,全删掉后再全量重编,并且每次编译前都把 del 命令跑一遍,养成习惯。这里我用一个笨办法验证:在源码里故意敲一个语法错误,重新编译,如果编译器没报错,说明编的根本不是你改的那个文件。
5.2 安装与链接避坑:bpl 版本不匹配和 unresolved external
第三个坑出现在 IDE 加载包的时候。现象是点 Add 选择 bpl 后,IDE 弹窗提示 cannot load package 或者 module not found,后面还经常跟着一串找不到的单元名。原因是包的编译版本和当前 IDE 不匹配,最常见的是用 Delphi 7 编的 bpl 往 Delphi 2010 里装,系统底层库符号完全对不上。解决方式是严格用当前 IDE 对应的编译器版本重编,别想着跨版本用现成产物。如果确认版本正确仍加载失败,还有一个可能:包依赖的公共库 bpl 没先装上,IDE 加载这个包时发现它的依赖项缺失,一并报错。把公共库先装好,再回来装这个,顺序反了都会出问题。
第四个坑是 BCB 特有的链接错误。现象是 BCB 工程编译通过,但链接阶段报一堆 unresolved external,全是对 TdxSpreadSheet 构造、析构、属性访问的未定义引用。原因是 C++ 代码里用了控件类,但没把导入库链接进工程。#pragma link "ExpressSpreadSheet.lib"这行没写,或者 Project Options 的 Linker 路径里没有指向 lib 所在目录,链接器拿到符号名却找不到实现,只能报错。解决方式是在工程里加上 #pragma link,并把 -L 路径配置正确。这种错对新手特别劝退,因为错误信息几百行,其实核心就一句话:链接器没找到库。
5.3 运行期避坑:设计期拖放崩溃和新版 IDE 兼容性
第五个坑也比较隐蔽。现象是在 IDE 里拖一个 ExpressSpreadSheet 控件到窗体上,设计期直接 Access Violation,或者控件显示空白,运行时反而正常。原因是设计期包和运行期包的资源不匹配:dpkw 编出来的设计期包引用的 dfm 资源版本不对,IDE 在设计期尝试加载预览界面时崩掉。解决方式是确认设计期包和运行期包用的源码目录、资源路径完全一致,重新全量编译两个包,并且清掉 dfm 资源目录下的旧缓存文件。
再补一个常被追问的兼容性坑:有人拿 v1.38 的源码往新版 Delphi(比如 Delphi 10.4 以上)里编,报错 F1026 File not found 或者一堆语法过时警告。原因是老代码里用了旧版条件编译指令、个别已移除的库单元,以及 IDE 底包机制的变化。我的建议是别硬编新版,v1.38 本身就是跟着老 IDE 走的;如果必须用新版,先跑一遍源码里的语法转换工具,把旧的$R指令和废弃类型别名换成新写法,再逐个处理报错。这只是“能做”,不代表“值得做”,维护老项目时保持和当年一致的 IDE 版本,是成本最低的选择。
6. 把这套源码用透:日志埋点与默认行为修改
6.1 在绘制流程里埋日志,验证数据到底走没走到 UI 层
装好包只是第一步,真正体现源码包价值的是你能进去改行为。我接手一个老报表项目时,遇到过单元格数据对不上,运行时表格里显示的值和底层数据结构不一致,查了半天不知道是数据源的问题还是绘制层的问题。后来直接在绘制方法里埋了日志,一跑就定位到了。
procedure TdxSpreadSheet.DrawCell(ACanvas: TCanvas; ARow, ACol: Integer); begin // 只在调试版输出,避免刷屏拖慢滚动速度 {$IFDEF DEBUG} OutputDebugString(PChar(Format('DrawCell Row=%d Col=%d', [ARow, ACol]))); {$ENDIF} inherited DrawCell(ACanvas, ARow, ACol); end;这段代码在绘制每个单元格时输出它的行号和列号到 IDE 的调试输出窗口。OutputDebugString 是 Win32 的调试输出函数,Delphi 封装好可以直接调,对运行性能影响很小。实际定位时,你在数据层给某个单元格设值,看绘制层有没有收到对应的 Row/Col 日志,就能判断是上游数据没传进来还是绘制层自己画错了。注意别在绘制循环里做重操作,滚动时每帧要画几十上百个单元格,日志频率过高会把 IDE 拖死,用 DEBUG 条件编译包一层,发布版自动去掉。
6.2 改默认列宽和字体:绕过属性面板直接改初始化代码
另一个高频诉求是调整默认样式。属性面板能改单个控件的列宽和字体,但每个新窗口都要重新设一遍,很烦。真正的做法是在源码的初始化流程里改默认值,一劳永逸。我一般会先搜源码里的 DefaultColWidth、DefaultFont 之类的属性名,然后在控件构造函数或 Loaded 方法里直接改数值。
// 在 TdxSpreadSheet.Create 或 Loaded 方法里 DefaultColWidth := 24; // 把默认列宽从 18 改成 24 DefaultRowHeight := 28; // 默认行高调大,适配中文显示 Font.Name := 'Microsoft YaHei'; Font.Size := 9;如果你手上的版本里没有这几个属性名,就在源码里搜 ColWidth、RowHeight 的初始化赋值点,在那附近改。这样做的效果是工程里所有新建的表格控件都自动带上这套默认值,比在设计期每个窗体加一遍代码干净得多。改完记得重新编译运行期包并在 IDE 里重装,不然改动不生效。
回头说说我自己栽过的一次教训:接一个老项目时,我改完默认字体后编译、安装一气呵成,但打开程序发现字体纹丝不动,折腾了一下午才发现是 Library 路径里另一份旧源码的 dcu 抢先被引用,我改的根本不是编译进包的那份源码。从那以后我养成了两个习惯:一是编译前先 del 清掉目录里全部 dcu,二是装包前检查 Library Path 是否严格指向当前源码目录。这两个习惯让我在后续处理各种老控件源码时省下了大量时间。这套 ExpressSpreadSheet v1.38 的源码编译安装,本质就是一次环境、缓存和版本的游戏,摸清这三个变量的脾气,后面就顺了。希望帮到你。
本文还有配套的精品资源,点击获取