Delphi操作Excel详解1

正文:         
 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  
  WS:   _Worksheet; 
  ……  
  Excel.Range['A1',  'B4'].Copy(EmptyParam);   //在一个工作表中复制数据到剪贴板 
  WS   :=  Excel.Activesheet   as  _Worksheet;   //改变活动工作表  
  WS.Range['C1',   'D4'].Select;   
  WS.Paste(EmptyParam,  EmptyParam,   lcid);  //把剪贴板中的内容粘贴到新的工作表中  
   
  格式设置  
 选择Excel作为报表服务器主要是因为它强大的格式化能力。我们首先把标题"通讯录"进行单元格合并,居中显示,然后修改字体为18磅的"隶书",粗体: 
  with  wkSheet.Range['A1','D1'],Font   do 
  begin  
  Merge(True);   //合并单元格 
  HorizontalAlignment:=  xlCenter;  
  Size:=18;  
  Name:='隶书';  
  FontStyle:=Bold;  
  end;  
 如果单元格内容较长,将有部分内容无法显示,通常的做法是双击选定区域右侧的边线是各列的宽度自动适应内容的长度。在Delphi中通过AutoFit方法也可实现自适应的列宽行高,需要注意的是该方法仅能用于整行整列,否则会提示OLE方法拒绝执行的错误: 
  wkSheet.Columns.EntireColumn.AutoFit; 
 中式报表通常需要上下封顶的表格线,可以使用Borders集合属性。要注意,VBA中的集合对象通常都有一个缺省的Item属性,Delphi中是不能省略的。Weight属性用于定义表格线的粗细: 
  with  Aname.RefersToRange,Borders   do 
  begin  
  HorizontalAlignment:=  xlRight;  
  Item[xlEdgeBottom].Weight:=xlMedium; 
  Item[xlEdgeTop].Weight:=xlMedium; 
  Item[xlInsideHorizontal].Weight:=xlThin; 
  item[xlInsideVertical].Weight:=xlThin; 
  end;  
   
  页面设置与打印  
    页面设置是通过工作表的PageSetUp对象属性设置的。Excel VBA中预设了40余种纸张常量,需要注意的是某些打印机只支持其中的一部分纸张类型。属性Orientation用于控制打印的方向,常量landscape  =  2表示横向打印。布尔属性CenterHorizontally和CenterVertically用于确定打印的内容是否在水平和垂直方向上居中。 
  with   wkSheet.PageSetUp  do  
  begin  
  PaperSize:=xlPaperA4;   //Paper  type   A4 
  PrintTitleRows   :=  'A1:D1';   //Repeat  this   row/page 
  LeftMargin:=18;   //0.25"  Left   Margin 
  RightMargin:=18;   //0.25"  will   vary  between   printers  
  TopMargin:=36;   //0.5" 
  BottomMargin:=36;   //0.5" 
  CenterHorizontally:=True; 
  Orientation:=1;  //横向打印(landscape)=2,   portrait=1 
  end;  
 打印报表可以调用工作表的PrintOut方法,VBA定义的该方法共有8个可选参数,前两个用于规定起止页,第三格式打印的份数,不过在Delphi中为其在最后增加了一个LCID参数,而且该参数不能使用EmptyParam。类似地,打印预览方法PrintPrevieTop

2daniel007(添)回复于 2003-01-08 18:07:23 得分 0

Delphi4及以前的版本  
 Delphi4没有提供TExcelApplication对象,需要引入类型库使用OLE自动化技术,Excel97的类型库是Excel8.olb。这两种方法的主要区别在于与服务器程序建立连接的方法,下面是通过类型库控制Excel的程序框架: 
  uses   Windows,  ComObj,   ActiveX,  Excel_TLB;   
  var    
  Excel:   _Application;   
  LCID:   integer; 
  Unknown:IUnknown;  
  Result:   HResult;   
  begin   
  LCID   :=  LOCALE_USER_DEFAULT;  
  Result   :=  GetActiveObject(CLASS_Application,   nil,  Unknown);   //尝试捕获运行中的程序实例 
  if   (Result  =   MK_E_UNAVAILABLE)   then 
  Excel   :=  CoApplication.Create   //启动新的程序实例 
  else   begin   
  {检查GetActiveObject方法调用过程中的错误} 
  OleCheck(Result);   
  OleCheck(Unknown.QueryInterface(_Application,  Excel));   
  end;    
   
  ……   //进行数据处理 
   
  Excel.Visible[LCID]   :=  True;  
  //   Excel.DisplayAlerts[LCID]  :=   False;  //显示提示对话框  
  Excel.Quit;   
  End;  
 这里没有采用通常的try…except结构,是因为例外处理机制要进行复杂的OLE检查,降低了except部分的执行速度。要注意,不同的Delphi版本生成的伴随函数CoApplication和一些常量名可能不同,应查看相应的类型库。在调用Quit方法之前,一定要释放程序中创建的所有工作簿和工作表变量,否则Excel可能驻留在内存中运行(可以按下Ctrl+Alt+Del查看)。调用GetActiveObject捕获程序实例还有一个小问题,如果Excel处于最小化运行状态,可能出现只显示程序主框架而用户区不可见的情况。 
 此外,如果不希望引入类型库,还可以采用滞后绑定的方法,不过速度要慢许多。下面的例子声明了一个Variant变量来代表Excel应用程序: 
  var    
  Excel:   Variant;   
  ……  
  try    
  Excel   :=  GetActiveOleObject('Excel.Application');   
  except   
  Excel   :=  CreateOleObject('Excel.Application');   
  end;    
  Excel.Visible   :=  True;   
 采用滞后绑定时,编译器不对调用的Excel对象方法进行检查,而把这些工作交给服务器程序在执行时完成,这样VBA所设置的大量默认参数(经常有十几个)就发挥了应有的作用,因此这种方法有一个意料不到的好处--代码简洁: 
  var  
  WBk,   WS,  SheetName:   OleVariant;  
  .…..  
  WBk   :=  Excel.WorkBooks.Open('C:\Test.xls');  
  WS   :=  WBk.Worksheets.Item['SheetName'];  
  WS.Activate;  
  ……  
  WBk.Close(SaveChanges   :=  True);  
  Excel.Quit;   
  除了运行速度慢以外,如果要使用类型库中定义的常量,就只能自己动手了: 
  const   
  xlWBATWorksheet   =  -4167;   
  ……  
  XLApp.Workbooks.Add(xlWBatWorkSheet);   
  最后不要忘记关闭Excel之后释放变量:  
  Excel   :=  Unassigned;    
   
   
  Top

3daniel007(添)回复于 2003-01-08 18:07:38 得分 0

以下是本文例子中所用的源代码,在Delphi6+MSOffice2000下通过。 
  unit   Unit1; 
   
  interface  
   
  uses  
  Windows,   Messages,  SysUtils,   Variants,  Classes,   Graphics,  Controls,   Forms, 
  Dialogs,   OleServer,  Excel2000,   Grids,  StdCtrls;  
   
  type  
  TForm1   =  class(TForm)  
  Button1:   TButton; 
  StringGrid1:   TStringGrid; 
  Excel:   TExcelApplication; 
  procedure  FormActivate(Sender:   TObject); 
  procedure  Button1Click(Sender:   TObject); 
  private  
  {   Private  declarations   }  
   
  procedure   Write2Xls; 
  procedure   OpenExl; 
  procedure   CloseExl; 
  procedure   AddFormula; 
  procedure   NameSheet; 
  procedure   Formats; 
  procedure   AddMacro; 
  procedure   Retrieve; 
  procedure   Printit; 
  public  
  {   Public  declarations   }  
  end;  
   
  var  
  Form1:   TForm1; 
   
  implementation  
   
  {$R   *.dfm} 
  uses  
  VBIDE2000;  
   
  var  
  ir,ic:Integer;  
  wkSheet:_WorkSheet;  
  LCID:Integer;  
  wkBook:_WorkBook;  
  AName:Excel2000.Name;  
   
  procedure  TForm1.FormActivate(Sender:   TObject); 
  begin  
  with   StringGrid1  do  
  begin  
  Rows[0].CommaText:='姓名,性别,年龄,电话'; 
  Rows[1].CommaText:='张三,男,25,010-33775566'; 
  Rows[2].CommaText:='李四,男,47,012-6574906'; 
  Rows[3].CommaText:='周五,女,18,061-7557381'; 
  Rows[4].CommaText:='孙涛,女,31,3324559'; 
  end;  
  end;  
   
  procedure   TForm1.OpenExl; 
  begin  
  with   Excel  do  
  begin  
  Connect;  
  LCID:=GetUserDefaultLCID(); 
  wkBook:=WorkBooks.Add(EmptyParam,LCID); 
  wkSheet:=wkBook.Sheets[1]   as  _WorkSheet;  
  end;  
  end;  
   
  procedure   TForm1.Write2Xls; 
  var  
  Datas:Variant;  
  i,j:Integer;  
  begin  
  ir:=StringGrid1.RowCount; 
  ic:=StringGrid1.ColCount; 
  Datas:=varArrayCreate([1,ir,1,ic],varVariant); 
  for   i:=1  to   ir   do 
  for   j:=1  to   ic   do 
  Datas[i,j]:=StringGrid1.Cells[j-1,i-1]; 
   
  with   wkSheet  do  
  begin  
  Activate(LCID);  
  Cells.Item[1,1].Value:='通讯录'; 
 Range[cells.Item[3,1],cells.Item[ir+2,ic]].Value:=Datas; 
  end;  
  //   Excel.Visible[LCID]:=True; 
   
  Datas:=Unassigned;  
  end;  
   
  procedure   TForm1.Retrieve; 
  var  
  Datas:Variant;  
  i,j:Integer;  
  begin  
  with   wkSheet  do  
  begin  
 Cells.SpecialCells(xlCellTypeLastCell,EmptyParam).Activate; 
  ir:=Excel.ActiveCell.Row; 
  ic:=Excel.ActiveCell.Column; 
 Datas:=Range[Cells.Item[1,1],Cells.Item[ir,ic]].Value; 
  with   StringGrid1  do  
  begin  
  ColCount:=ic;  
  RowCount:=ir;  
  ScrollBars:=ssBoth;  
  for   i:=0  to   ir-1   do 
  for   j:=0  to   ic-1   do 
  Cells[j,i]:=Datas[i+1,j+1]; 
  end;  
  Datas:=UnAssigned;  
  end;  
  end;  
   
  procedure   TForm1.CloseExl; 
  const  
  SaveAsName='test.xls';  
  begin  
  wkBook.Close(True,SaveAsName,EmptyParam,LCID); 
  Excel.Quit;  
  Excel.Disconnect;  
  end;  
   
  procedure   TForm1.NameSheet; 
  begin  
 AName:=wkBook.Names.Add('通讯录','=Sheet1!$A$3:$D$7',EmptyParam,EmptyParam, 
  EmptyParam,EmptyParam,EmptyParam,EmptyParam, 
  EmptyParam,EmptyParam,EmptyParam); 
  end;  
   
  procedure   TForm1.AddFormula; 
  var  
  AFormula:String;  
  begin  
  AFormula:='=Rand()';  
  wkSheet.Range['F3','G6'].Value:=AFormula; 
  end;  
   
  procedure   TForm1.Formats; 
  begin  
  with  wkSheet.Range['A1','D1'],Font   do 
  begin  
  Merge(True);   //合并单元格 
  HorizontalAlignment:=  xlCenter;  
  Size:=18;  
  Name:='隶书';  
  FontStyle:=Bold;  
  end;  
  wkSheet.Columns.EntireColumn.AutoFit; 
  with  Aname.RefersToRange,Borders   do 
  begin  
  HorizontalAlignment:=  xlRight;  
  Item[xlEdgeBottom].Weight:=xlMedium; 
  Item[xlEdgeTop].Weight:=xlMedium; 
  Item[xlInsideHorizontal].Weight:=xlThin; 
  item[xlInsideVertical].Weight:=xlThin; 
  end;  
  end;  
   
  procedure   TFOrm1.AddMacro; 
  var  
  LineNo:   integer; 
  CM:   CodeModule; 
  sDate:String;  
  begin  
  CM   :=  WkBook.VBProject.VBComponents.Item('ThisWorkbook').Codemodule; 
  LineNo   :=  CM.CreateEventProc('BeforeClose',   'Workbook'); 
  SDate:='上次访问日期:'+DateToStr(Date()); 
  CM.InsertLines(LineNo   +  1,   '  Range("B2").Value   =  "'+sDate+'"');  
  end;  
   
  procedure   TForm1.Printit; 
  begin  
  with   wkSheet.PageSetUp  do  
  begin  
  PaperSize:=xlPaperA4;   //Paper  type   A4 
  PrintTitleRows   :=  'A1:D1';   //Repeat  this   row/page 
  LeftMargin:=18;   //0.25"  Left   Margin 
  RightMargin:=18;   //0.25"  will   vary  between   printers  
  TopMargin:=36;   //0.5" 
  BottomMargin:=36;   //0.5" 
  CenterHorizontally:=True; 
  Orientation:=1;  //横向打印(landscape)=2,   portrait=1 
  end;  
   
  wkSheet.PrintOut(EmptyParam,EmptyParam,1, 
  EmptyParam,EmptyParam,EmptyParam, 
  EmptyParam,EmptyParam,LCID); 
   
  end;  
   
  procedure  TForm1.Button1Click(Sender:   TObject); 
  begin  
  try  
  OpenExl;  
  Write2xls;  
  AddFormula;  
  NameSheet;  
  Formats;  
  PrintIt;  
  AddMacro;  
  ReTrieve;  
  finally  
  CloseExl;  
  end;  
  end;  
   
  end.  
  Top

4haoguozhong(郝国忠)回复于 2003-01-13 09:54:27 得分 0

请举例一个简洁的例子,可以是读取Excel文件中的第二行,第三列的值然后赋给Lable,请高手写出code如何?这篇文章我看过,方法不是很好用。Top

5snakymouse(咪咪琪)回复于 2003-01-15 20:10:41 得分 18

procedure  TNewCardForm.Button2Click(Sender:   TObject); 
  var  
         ExcelApp,MyWorkBook:  OLEVariant;  
  begin  
      if  opendialog1.Execute   then 
      begin 
         try  
            ExcelApp:=CreateOleObject('Excel.Application'); 
            MyWorkBook:=CreateOleobject('Excel.Sheet'); 
         except 
            application.Messagebox('无法打开Xls文件,请确认已  经安装EXCEL.','',  
                    mb_OK+mb_IconStop);  
            Exit;  
         end;  
         MyworkBook:=  ExcelApp.workBooks.Open(opendialog1.FileName); 
         //打开文件后,对文件进行操作 
         label1.caption  :=  MyWorkBook.WorkSheets[1].Cells[i,1].Value;  
       end;  
  end;  


你可能感兴趣的:(Delphi操作Excel详解1)