BCB & Delphi访问Excel文件编程那些事儿
□ 前一段时间学习研究了在程序中访问Excel文件,使用的环境是Boland的Delphi和C++ Builder 6.0。刚开始时,在网上查了一些前人的文档,经启发并在环境中测试,基本上对程序访问Excel有了一个全面的概念和进一步研究的一般性方法,现总结一下分享给大家。
一、准备工作
C++ Builder时,在头文件或cpp文件中包含Comobj.hpp
#include <Comobj.hpp>
在Delphi时,在单元文件的顶部的interface段加入uses ComObj,它会接联uses Variants。
二、相关概念
□ 在程序中访问Excel文件,首先要在程序中启动一个Excel实例(就像我们手动打开Excel,注意:不是双击打开一个Excel文件,而是打开Excel应用程序),这个实例在后台运行,就像是一个服务器,我们对Excel文件的访问都是通过向它发送指令(或者叫请求)来完成的。例如让它打开、新建或保存及另存为Excel文件;激活或新加一个sheet;选择一个单元格或一块3x4共12个单元格并复制它等操作。像这样来启动一个Excel实例:
C++ Builder时: Variant app = CreateOleObject(“Excel.Application”);
Delphi时: var app : Variant;
app := CreateOleObject(‘Excel.Application’);
程序中不要频繁启动Excel实例,应保证只启动一次,上面变量app可以使用全局变量。
1、对象和属性
□ 就像上面的代码,在编程中所使用的技术都是OLE自动化对象,它们返回的都存于可变类型的变量中( Variant)。这些对象是WorkBooks及其WorkBook、WorkSheets(Sheets)及其WorkSheet、Rows、Columns、Cells、Range。
□ 对象是一个实例,是一个具体的实际存在,在内存中都有其独有的空间。每一类对象都有其特定的各种属性和方法(方法:函数和过程),各个属性就像是其对象的窗口来反映该对象的固有特性,正是所有这些特性体现了该对象的面貌,而各个方法也用于操作该对象自身内部的各类变量来实现对象的各种功能,这些操作的内部细节是被隐藏起来的。
□ 在Excel操作方面 ,通过OlePropertyGet函数来访问某对象的属性,而得到的又是一个对象,两个对象是父子关系。就这样,对象会一个套一个,子子孙孙一条链。
2、对象间的关系
□ 正像上面所描述的,Excel的名类对象间的层次递进关系如下(→左侧的是父对象,→右侧的是其子对象):
Excel服务对象→WorkBooks(工作簿集)→WorkBook(1个工作簿)→
WorkSheets(Sheets)(工作表集) →WorkSheet(1个工作表集)→Range(范围)
WorkSheet→Rows(工作表中的行) →(Row) (1行)
WorkSheet→Columns(工作表中的列) →(Column)(1列)
WorkSheet→Cells(工作表中的单元格)→(Cell) (1个单元格)
WorkSheet→Range→Rows(范围中的行) →(Row)
WorkSheet→Range→Columns(范围中的列) →(Column)
WorkSheet→Range→Cells(范围中的单元格)→(Cell)
注:其中括号中的对象只能通过相应属性得到,在微软的文档中是没有该对象的。
□ 从上面的链条中看到要得到一个对象,必须先得到其父对象。链条中末尾有S的有5个对象都是集合对象。
三、对象及其属性和方法操作详解
在BCB中,对象的操作一共有4个函数,2个操作属性、2个操作方法:
Variant OlePropertyGet(“属性名“ [,可能的参数]…) - - - 访问指定的属性,得到对应的子对象
OlePropertySet(“属性名“,新值) - - - 设定指定的属性为新的值
Variant OleFunction(“函数名”[,可能的参数]…) - - - 执行对象的函数,得某个对象或其它值
OleProcedure(“过程名”[,可能的参数]…) - - - 执行对象的过程
注:双引号是语法的一部分
为了加快代码的输入速度和可阅读性,定义如下宏:
#define PG OlePropertyGet
#define PS OlePropertySet
#define FN OleFunction
#define PR OleProcedure
从而变成形式:Variant PG(“属性名“ [,可能的参数]…)
1、"Excel.Application"对象
有了如上面app这个服务:
属性 | BCB | Delphi |
---|---|---|
WorkBooks | Variant MyWorkBooks= app.PG(“WorkBooks”); 得到WorkBooks对象 |
Var MyWorkBooks: Variant ; MyWorkBooks:=app. WorkBooks; |
DisplayAlerts | app.PS(“DisplayAlerts s”,false); 使Excel不会弹出确认窗口 |
app. DisplayAlerts:=false; |
visible | app.PS(“visible”,false); 启动Excel后,使Excel窗口不可视而在后台运行 |
app. Visible:=false; |
ActiveWorkbook | Variant aWorkBook=app.PG(“ActiveWorkbook”); Excel服务的当前工作簿(激活状态) |
var aWorkBooks: Variant ; aWorkBooks:= app.ActiveWorkbook; |
Quit函数 | app.FN(“Quit”); //退出Excel | app.Quit; |
2、 WorkBooks对象
属性 | BCB | Delphi |
---|---|---|
Count | int n=MyWorkBooks.PG(“Count”); 当前已打开的工作簿数量(包括新建的工作簿) |
Var n: integer ; n:= WorkBooks. Count; |
Open函数 | String fn= d:\test\tab.xlsx"; Variant aWorkBook=MyWorkBooks.FN(“Open”, fn.c_str() ) ; 打开一个Excel文件并返回它的工作簿对象 |
var aWorkBook: Variant; aWorkBook=MyWorkBooks.add; |
Add函数 | Variant aWorkBook=MyWorkBooks.FN(“Add”) 新建1个工作簿并返回它的工作簿对象 |
aWorkBook=MyWorkBooks.add; |
Close过程 | MyWorkBooks.PR(“Close”); 关闭所有已打开或新建的工作簿 |
MyWorkBooks. Close; |
Item | Variant aWorkBook=MyWorkBooks.PG(“Item”,i); 通过item属性获取n个工作簿中的第i个 |
aWorkBook=MyWorkBooks.Item[ i ]; 或 aWorkBook=MyWorkBooks [ i ]; |
3、WorkBook对象
属性 | BCB | Delphi |
---|---|---|
Sheets或 WorkSheets |
Variant MySheets=aWorkBook.PG(“WorkSheets”); 或 Variant MySheets=aWorkBook.PG(“Sheets”) 工作簿aWorkBook目前拥有的sheet集 |
Var MySheets: Variant; MySheets:= aWorkBook.WorkSheets; MySheets:= aWorkBook. Sheets; |
ActiveSheet | Variant aSheet=aWorkBook.PG(“ActiveSheet”) 工作簿aWorkBook的当前Sheet(激活状态) |
aSheet=aWorkBook.ActiveSheet |
Name | String name=aWorkBook.PG(“Name”); Name为不包含路径的文件名 |
name=aWorkBook.Name; |
Saved | aWorkBook.PS("Saved ",true); 使Excel服务以为工作簿aWorkBook已保存过(之后对aWorkBook的任何操作会使该属性变为False),从而aWorkBook关闭时不会被弹窗询问。 |
aWorkBook.Saved:=true; |
Close过程 | aWorkBook.PR(“Close”); //关闭工作簿WorkBook | aWorkBook.Close; |
Save过程 | aWorkBook.PR(“Save”); //保存工作簿 | WorkBook aWorkBook.Save; |
SaveAs过程 | aWorkBook.PR(“SaveAs”,“新文件名”) 工作簿WorkBook另存为 |
aWorkBook.SaveAs(“新文件名”); |
Activate过程 | aWorkBook.PR(“Activate”) 激活某工作薄,使其成为当前工作簿 |
aWorkBook.Activate |
4、WorkSheets对象
属性 | BCB | Delphi |
---|---|---|
Count | int count=MySheets.PG(“Count”) | count=MySheets.Count |
Add函数 | Variant aSheet=Sheets.FN(“Add” [ ,其它参数 ]…) 新增1个Sheet到工作簿中并返回它的sheet对象 |
aSheet=Sheets.Add [ ( 其它参数…) ] ; |
5、WorkSheet对象
属性 | BCB | Delphi |
---|---|---|
Index | int N=aSheet.PG(“Index”); 该Sheet在工作簿中的序号(最左边的为1) |
N=aSheet.Index |
Name | String Name=aSheet.PG(“Name”) Sheet的名称(在Excel的左下方可以看到) |
Name=aSheet.Name |
Activate过程 | aSheet.PR(“Activate”) 激活某Sheet使其成为工作簿中的当前Sheet |
aSheet. Activate |
Delete过程 | aSheet.PR(“Delete”) 删除本Sheet |
aSheet.Delete |
Paste函数 | aSheet.FN(“Paste”,aSheet.PG(“cells”,1,4)); 在aSheet的1行第4列处粘贴剪切板上的内容 |
Sheet.Paste(aSheet. Cells[1,4]); |
Range | Variant aRange=aSheet.PG(“Range”,某行1,某行2); //行1和行2之间的所有行 Variant aRange=aSheet.PG(“Range”,某列1,某列2); //列1和列2之间的所有列 Variant aRange=aSheet.PG(“Range”,某单元格1,某单元格2);//两单元格为对角,组成的方块 |
Range对象:返回Sheet的某1个范围 Var aRang: Variant; aRang:= aSheet. Range[aCell, aSheet. Cells[2,3]]; |
UsedRange (属于Range) |
Variant aRange=aSheet.PG(“UsedRange”); //单元格A1和最后行最右列组成的方块 |
aRange:=aSheet.UsedRange; |
Rows | Variant MyRows=aSheet.PG(“Rows”); //返回Range对象:Sheet的"行集" |
MyRows:=aSheet. Rows; |
Rows[i] | Variant aRow=aSheet.PG(“Rows”,1); //返回Range对象:Sheet的首行 |
aRow:=aSheet. Rows[1]; |
Columns | Variant MyCols=aSheet.PG(“Columns”); //返回Range对象:Sheet的"列集" |
MyCols:=aSheet. Columns; |
Cells | Variant MyCells=aSheet.PG("Cells “); //返回Range对象:Sheet的"单元格集” |
MyCells:=aSheet. Cells; |
Cells[i,j] | Variant aCell=aSheet.PG(“Cells”,2,3); //Sheet的第2行第3列单元格 |
aCell:=aSheet. Cells[2,3]; |
6、Range对象
属性 | BCB | Delphi |
---|---|---|
Rows | Variant MyRows = aRang.PG(“Rows”); //返回Range对象的行"行集" | Variant MyRows := aRang.Rows; |
Columns | Variant MyCols = aRang.PG(“Columns”); //返回Range对象的行"列集" | Variant MyCols:= aRang. Columns; |
Cells | Variant MyCells = aRang.PG(“Cells”); //返回Range对象的行"单元格集" | Variant MyCells:= aRang. Cells; |
Address | String s=aRange.PG(“Address”); //形如:“$B 4 : 4: 4:D$8” | S:= aRange. Address; |
Font | aRange.PG(“Font”).PS(“Size”,20) ; //设置Rang内各单元格的字体大小 aRange.PG(“Font”).PS(“Name”,”宋体”) ; //设置Rang内各单元格的字体为“宋体” |
aRange.Font.Size:=20 ; aRange.Font. Name:=’ 宋体’; |
7、Rows对象
属性 | BCB | Delphi |
---|---|---|
Count "行集"的总行数 |
int N= aRang.PG(“Rows”).PG(“Count”); //返回Range对象的行数 | N:= aRang.Rows.Count; |
Item 访问Rows中的行 |
Variant aRow=MyRows.PG(“item”, 3); //aRow为”行集”中的第3行 |
8、Columns对象
属性 | BCB | Delphi |
---|---|---|
Count "列集"的总列数 |
int N= aRang.PG(“Columns”).PG(“Count”); //返回Range对象的列数 | N:= aRang. Columns.Count; |
Item 访问Columns中的列 |
Variant aCol=MyCols.PG(“item”, 3); //aCol为”列集”中的第3列 |
9、Cells对象
属性 | BCB | Delphi |
---|---|---|
Count 单元格总数 |
int N= aRang.PG(“Cells”).PG(“Count”); //返回对象aRang的单元格总数 | |
Item 访问Cells中的单元格 |
Variant aCell=MyCells.PG(“item”, 2,3); //aCol为”单元格集” MyCells中的第2行3列 |
10、Range对象(包括Rows、Columns、Cells、Cell) 的方法:
属性 | BCB | Delphi |
---|---|---|
Copy函数 | aRow.FN(“Copy”); aRange.FN(“Copy”); | aRange.Copy; |
PasteSpecial函数 | aSheet.PG(“Cells”,2,3).FN(“PasteSpecial”); //在工作表aSheet的第2行3列处粘贴剪贴板上的内容 |
aSheet.Cells[2,3].PasteSpecial ; //把上面复制的内容粘贴到此单元格处 |
Replace函数 | aRange.FN(“Replace”, old.c_str() , new.c_str() ); //内容替换函数。 |
aRange.Replace( old , new ); |
□ 对上面的表中的Range对象要特别补充一下。Range的含义是范围和区域,我在全文使用”范围”,而一般的相关文章中使用”区域”。 1、Range可以小到1个单元格、1行或1列,大到连续的多个单元格、多行或多列,甚至是工作表中的全部(UsedRange)或全部的行或列。2、可应用于1个单元格的好多方法和属性往往也可应用于其它Range对象。3、Range对象在结构上还可以嵌套,也就是物理上大的Range范围内部可以有小的Range,甚至与大的一样大小。
□ 在使用Range属性来构造Range对象时(表中WorkSheet对象的Range属性 ),两个参数所代表的对象必须有一致的性质,即要么用两个行来构建1个行Range、要么用两个列来构建1个列Range、要么用两个单元格来构建1个长方形单元格Range,而且这两个参数的前后次序可以颠倒。如:
行的情形:
Variant aRow1=aSheet.PG("Rows", 2 ); //第2行
Variant aRow2=aSheet.PG("Rows", 200 ); //第200行
Variant aRange1=aSheet.PG("Range", aRow1 , aRow2 ); //行2和200之间(含)的所有行,或
Variant aRange2=aSheet.PG("Range", aRow2 , aRow1 ); //行200和2之间(含)的所有行
//aRange1 和aRange2具有相同的效果
单元格的情形:
Variant aCell1=aSheet.PG("Cells", 2 , 3 ); //第2行第3列
Variant aCell2=aSheet.PG("Cells ", 9, 8 ); //第9行第8列
Variant aRange3=aSheet.PG("Range", aCell1 , aCell2 );
// aCell1和aCell2不仅可以颠倒(左上角和右下角),而且列参数对换也可以:
//变成aSheet.PG("Cells", 2 , 8 )和aSheet.PG("Cells ", 9, 3 ),也就是变成了右上角和左下角来构建,或倒过来。
Range嵌套时,基于上例的范围aRange3,它包含8行* 6例=48个单元格。我们可以在这之中构建一个小的Range:
Variant aRow1= aRange3.PG("Rows", 1 ); // aRange3中的第1行
Variant aRow2= aRange3.PG("Rows", 4 ); // aRange3中的第4行
Variant aRange4= aRange3.PG("Range", aRow1 , aRow2 ); // aRange3中的1到4行,也就是4行6列组成的“范围”
在指定行、列或单元格的形式上,除了上面一直使用的数字方式外,还有另一种方式:地址。即:
情形 | 数字形式 | 等效地址形式 |
---|---|---|
1个单元格 | aCell1=aSheet.PG(“Cells”, 2 , 3 ) | aCell1=aSheet.PG(“Cells”, “C2” ) |
1行 | aRow1=aSheet.PG(“Rows”, 2 ) | aRow1=aSheet.PG(“Rows”, “2” ) |
1列 | aCol1=aSheet.PG(“Columns”, 2 ) | aCol1=aSheet.PG("Columns |