正文:
Delphi与Excel的亲密接触
Delphi作为一个出色的RAD,强大的数据库功能是其最重要的特色之一,但是操纵困难的QuickReport控件常常不能满足数据库报表的需要。如果你的报表非常复杂,或者要求灵活地改变格式,那么使用Excel作为报表服务器是一个不错的选择。Delphi从版本5开始提供的Excel组件极大地简化了OLE自动化技术的应用。不过缺漏多多的帮助文件一直是Delphi最令人诟病的地方,这些新组件也不例外,本文试图对此作一较详细地介绍。
Excel的对象模型是一个树状的层次结构,根是应用程序本身,工作簿WorkBook是根对象的属性对象,本文主要讨论的用于数据交换的WorkSheet则是工作簿的属性对象,详情参阅MSOffice提供的Excel VBA帮助文件。在Delphi中控制Excel首先要与服务器程序建立连接,打开工作簿,然后与目标工作表交换数据,最后断开连接。
打开Excel工作簿
我们的例子从一个带有TStringGrid(当然要填上一些数据)和两个按钮的主窗体开始,从控制面板的Servers页签中拖一个TExcelApplication控件放到窗体上。首先把ConnectKind设为ckRunningOrNew,表示如果能够检测到运行的Excel实例则与其建立联系,否则启动Excel。另外,如果希望程序一运行即与服务器程序建立联系,可以把AutoConnect属性设为True。
与Excel建立联系只要一条语句就可以了:
Excel . Connect;
也许你已经注意到Servers页签上还有其他几个Excel控件,这些控件通过ConnectTo方法可以与前面的Excel联系在一起:
ExcelWorkbook1.ConnectTo(Excel . ActiveWorkbook);
ExcelWorksheet1.ConnectTo(Excel . ActiveSheet as _Worksheet);
ExcelWorksheet2.ConnectTo(Excel . Worksheets.Item['Sheet2'] as _Worksheet);
要注意,使用ConnectTo方法前必须先打开相应的工作簿或工作表,另外这些控件在多数情况下并不会带来额外的便利,因此最好只使用一个TExcelApplication。
一旦与Excel服务器建立联系,就可以创建新的工作簿:
var
wkBook : _WorkBook;
LCID : Integer;
...
LCID := GetUserDefaultLCID();
wkBook := Excel.Workbooks.Add(EmptyParam, LCID);
Add函数的第一个参数用于定义新建工作簿所使用的模板,可以使用xlWBATChart、xlWBATExcel4IntlMacroSheet、 xlWBATExcel4MacroSheet或者xlWBATWorksheet常量,也可以是已有的xls文件名。这里的EmptyParam是Variants单元与定义的变量,表示使用默认的通用模板创建新工作簿。
如果打开已有的xls文档,则应把要打开的文件名作为第一个参数传递给Open函数:
wkBook:=Excel.WorkBooks.Open(edtDesFile.text,EmptyParam,EmptyParam,
EmptyParam,EmptyParam,EmptyParam,EmptyParam,
EmptyParam,EmptyParam,EmptyParam,EmptyParam,
EmptyParam,EmptyParam,LCID);
要知道,所有的数据操作主要是针对活动工作表而言的,下面的语句使用一个_WorkSheet变量代表当前的活动单元格。如果知道工作表的名称,其中的索引号可以用工作表名代替:
wkSheet:=wkBook.Sheets[1] as _WorkSheet;
完成数据交换后需要保存工作簿:
Excel.ActiveWorkBook.SaveAs ('MyOutput', EmptyParam,EmptyParam,
EmptyParam, EmptyParam, EmptyParam,
EmptyParam, EmptyParam, EmptyParam,
EmptyParam, EmptyParam, LCID);
或者:
Excel.ActiveWorkBook.Save(LCID);
最后要关闭工作簿并断开与Excel的连接:
wkBook.Close(True, SaveAsName, EmptyParam, LCID);
//Excel.Quit;
Excel.Disconnect;
这里的Close方法包含有保存的功能,第一个参数说明在关闭工作簿之前是否保存所做的修改,第二个参数给出要保存的文件名,第三个参数用于多位作者处理文档的情况。第二行要求终止Excel的运行。
与工作表交换数据
输入数据是对活动工作表的某个单元格或区域进行的,Range与cells都是工作表的对象属性。Cells是单元格的集合,如果没有指定具体位置可以代表整个工作表的所有单元格,但一般使用它是为了引用某个具体的单元格,比如WS.Cells.Item[1,1]就表示最左上角的单元格A1,注意在VBA中Item是Cells的默认属性可以省略,但在Delphi中就没有这种便利了。为单元格赋值要引用其Value属性,不言而喻,该属性是一个Variant变量,例如:
wkSheet.Cells.Item[1, 1].Value := '通讯录';
当然你也可以为单元格指定公式:
var
AFormula:String;
……
AFormula:='=Rand()';
wkSheet.Range['F3','G6'].Value:=AFormula;
上面的方法非常直接简单,但是速度非常慢,不适合作大型报表。那么能不能把所有的数据依次传递给Excel呢?我们可以使用Range,这个对象代表工作表中的一个区域,象我们用鼠标拖出的那样,一般是一个矩形区域,只要给定其左上角和右下角单元格的位置就可以了,如Range['C3','J42']。
这里还有一个小问题,因为如果数据超出26列(比如有100列)或者需要在运行中确定目标区域范围的话,使用字符名称标记单元格就比较麻烦。回想一下,既然"C3"是单元格的标记,那么我们当然也可以使用Cells,比如Range[Cells.Item[1,1], Cells.Item[100,100]]。
可以想象,Range的值应该是数组,但是绝对不能用Delphi中的Array给它赋值!要记住,在Delphi中,Excel对象的值总是Variant类型的。
var
Datas : Variant;
Ir, ic: Integer;
……
Datas:= varArrayCreate([1,ir,1,ic],varVariant); //这里创建100*100的动态数组
…… //这里为数组元素赋值
with wkSheet do
Range[cells.Item[3,1],cells.Item[ir+2,ic]].Value:=Datas;
要注意,工作表与Range都有Cells属性,为了明确起见,这里使用了with语句。此外,Range是有方向性的,用VarArrayCreate建立的一维数组只能赋给单行的Range,如果要为单列的Range定义值,必须使用二维数组,比如:
Datas:=VarArrayCreate([1,100,1,1], varVariant);//创建100*1的动态数组。
顺便提一下,Cells.Item[]实际上返回的也是Range对象。
从工作表中取回数据基本上是写数据的逆过程,稍微需要注意的是如何确定工作表的数据范围:
var
ir, ic : Integer;
……
wkSheet.Cells.SpecialCells(xlCellTypeLastCell,EmptyParam).Activate;
ir := Excel.ActiveCell.Row;
ic := Excel.ActiveCell.Column;
这里巧妙地利用特殊单元格函数SpecialCells取得包含数据的最后一个单元格。
数据编辑
下面是数据编辑的两个例子。
var
DestRange: OleVariant;
begin
DestRange := Excel.Range['C1', 'D4'];
Excel.Range['A1', 'B4'].Copy(DestRange);
上面的例子复制了8个单元格的内容。如果给Copy函数传递一个空参数,则该区域的数据被复制到剪贴板,以后可以用Paste方法粘贴到别的位置。
var