a亚洲精品_精品国产91乱码一区二区三区_亚洲精品在线免费观看视频_欧美日韩亚洲国产综合_久久久久久久久久久成人_在线区

首頁(yè) > 編程 > Delphi > 正文

Delphi與Excel的親密接觸

2019-11-18 18:40:45
字體:
來(lái)源:轉(zhuǎn)載
供稿:網(wǎng)友
Delphi與Excel的親密接觸 [王安鵬(anpengwang@263.net) 2002/4/14] Delphi作為一個(gè)出色的RAD,強(qiáng)大的數(shù)據(jù)庫(kù)功能是其最重要的特色之一,但是操縱困難的QuickReport控件常常不能滿足數(shù)據(jù)庫(kù)報(bào)表的需要。如果你的報(bào)表非常復(fù)雜,或者要求靈活地改變格式,那么使用Excel作為報(bào)表服務(wù)器是一個(gè)不錯(cuò)的選擇。Delphi從版本5開(kāi)始提供的Excel組件極大地簡(jiǎn)化了OLE自動(dòng)化技術(shù)的應(yīng)用。不過(guò)缺漏多多的幫助文件一直是Delphi最令人詬病的地方,這些新組件也不例外,本文試圖對(duì)此作一較詳細(xì)地介紹。 Excel的對(duì)象模型是一個(gè)樹(shù)狀的層次結(jié)構(gòu),根是應(yīng)用程序本身,工作簿W(wǎng)orkBook是根對(duì)象的屬性對(duì)象,本文主要討論的用于數(shù)據(jù)交換的WorkSheet則是工作簿的屬性對(duì)象,詳情參閱MSOffice提供的Excel VBA幫助文件。在Delphi中控制Excel首先要與服務(wù)器程序建立連接,打開(kāi)工作簿,然后與目標(biāo)工作表交換數(shù)據(jù),最后斷開(kāi)連接。 打開(kāi)Excel工作簿我們的例子從一個(gè)帶有TStringGrid(當(dāng)然要填上一些數(shù)據(jù))和兩個(gè)按鈕的主窗體開(kāi)始,從控制面板的Servers頁(yè)簽中拖一個(gè)TExcelapplication控件放到窗體上。首先把ConnectKind設(shè)為ckRunningOrNew,表示如果能夠檢測(cè)到運(yùn)行的Excel實(shí)例則與其建立聯(lián)系,否則啟動(dòng)Excel。另外,如果希望程序一運(yùn)行即與服務(wù)器程序建立聯(lián)系,可以把AutoConnect屬性設(shè)為True。與Excel建立聯(lián)系只要一條語(yǔ)句就可以了: Excel . Connect; 也許你已經(jīng)注意到Servers頁(yè)簽上還有其他幾個(gè)Excel控件,這些控件通過(guò)ConnectTo方法可以與前面的Excel聯(lián)系在一起: ExcelWorkbook1.ConnectTo(Excel . ActiveWorkbook); ExcelWorksheet1.ConnectTo(Excel . ActiveSheet as _Worksheet); ExcelWorksheet2.ConnectTo(Excel . Worksheets.Item['Sheet2'] as _Worksheet); 要注意,使用ConnectTo方法前必須先打開(kāi)相應(yīng)的工作簿或工作表,另外這些控件在多數(shù)情況下并不會(huì)帶來(lái)額外的便利,因此最好只使用一個(gè)TExcelApplication。一旦與Excel服務(wù)器建立聯(lián)系,就可以創(chuàng)建新的工作簿: var wkBook : _WorkBook; LCID : Integer; ... LCID := GetUserDefaultLCID(); wkBook := Excel.Workbooks.Add(EmptyParam, LCID); Add函數(shù)的第一個(gè)參數(shù)用于定義新建工作簿所使用的模板,可以使用xlWBATChart、xlWBATExcel4IntlMacroSheet、 xlWBATExcel4MacroSheet或者xlWBATWorksheet常量,也可以是已有的xls文件名。這里的EmptyParam是Variants單元與定義的變量,表示使用默認(rèn)的通用模板創(chuàng)建新工作簿。如果打開(kāi)已有的xls文檔,則應(yīng)把要打開(kāi)的文件名作為第一個(gè)參數(shù)傳遞給Open函數(shù): wkBook:=Excel.WorkBooks.Open(edtDesFile.text,EmptyParam,EmptyParam, EmptyParam,EmptyParam,EmptyParam,EmptyParam, EmptyParam,EmptyParam,EmptyParam,EmptyParam, EmptyParam,EmptyParam,LCID); 要知道,所有的數(shù)據(jù)操作主要是針對(duì)活動(dòng)工作表而言的,下面的語(yǔ)句使用一個(gè)_WorkSheet變量代表當(dāng)前的活動(dòng)單元格。如果知道工作表的名稱,其中的索引號(hào)可以用工作表名代替: wkSheet:=wkBook.Sheets[1] as _WorkSheet; 完成數(shù)據(jù)交換后需要保存工作簿: Excel.ActiveWorkBook.SaveAs ('MyOutput', EmptyParam,EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, LCID); 或者: Excel.ActiveWorkBook.Save(LCID); 最后要關(guān)閉工作簿并斷開(kāi)與Excel的連接: wkBook.Close(True, SaveAsName, EmptyParam, LCID); //Excel.Quit; Excel.Disconnect; 這里的Close方法包含有保存的功能,第一個(gè)參數(shù)說(shuō)明在關(guān)閉工作簿之前是否保存所做的修改,第二個(gè)參數(shù)給出要保存的文件名,第三個(gè)參數(shù)用于多位作者處理文檔的情況。第二行要求終止Excel的運(yùn)行。 與工作表交換數(shù)據(jù)輸入數(shù)據(jù)是對(duì)活動(dòng)工作表的某個(gè)單元格或區(qū)域進(jìn)行的,Range與cells都是工作表的對(duì)象屬性。Cells是單元格的集合,如果沒(méi)有指定具體位置可以代表整個(gè)工作表的所有單元格,但一般使用它是為了引用某個(gè)具體的單元格,比如WS.Cells.Item[1,1]就表示最左上角的單元格A1,注意在VBA中Item是Cells的默認(rèn)屬性可以省略,但在Delphi中就沒(méi)有這種便利了。為單元格賦值要引用其Value屬性,不言而喻,該屬性是一個(gè)Variant變量,例如: wkSheet.Cells.Item[1, 1].Value := '通訊錄'; 當(dāng)然你也可以為單元格指定公式: var AFormula:String; …… AFormula:='=Rand()'; wkSheet.Range['F3','G6'].Value:=AFormula; 上面的方法非常直接簡(jiǎn)單,但是速度非常慢,不適合作大型報(bào)表。那么能不能把所有的數(shù)據(jù)依次傳遞給Excel呢?我們可以使用Range,這個(gè)對(duì)象代表工作表中的一個(gè)區(qū)域,象我們用鼠標(biāo)拖出的那樣,一般是一個(gè)矩形區(qū)域,只要給定其左上角和右下角單元格的位置就可以了,如Range[‘C3’,’J42’]。這里還有一個(gè)小問(wèn)題,因?yàn)槿绻麛?shù)據(jù)超出26列(比如有100列)或者需要在運(yùn)行中確定目標(biāo)區(qū)域范圍的話,使用字符名稱標(biāo)記單元格就比較麻煩。回想一下,既然“C3”是單元格的標(biāo)記,那么我們當(dāng)然也可以使用Cells,比如Range[Cells.Item[1,1], Cells.Item[100,100]]。可以想象,Range的值應(yīng)該是數(shù)組,但是絕對(duì)不能用Delphi中的Array給它賦值!要記住,在Delphi中,Excel對(duì)象的值總是Variant類型的。 var Datas : Variant; Ir, ic: Integer; …… Datas:= varArrayCreate([1,ir,1,ic],varVariant); //這里創(chuàng)建100*100的動(dòng)態(tài)數(shù)組 …… //這里為數(shù)組元素賦值 with wkSheet do Range[cells.Item[3,1],cells.Item[ir+2,ic]].Value:=Datas; 要注意,工作表與Range都有Cells屬性,為了明確起見(jiàn),這里使用了with語(yǔ)句。此外,Range是有方向性的,用VarArrayCreate建立的一維數(shù)組只能賦給單行的Range,如果要為單列的Range定義值,必須使用二維數(shù)組,比如: Datas:=VarArrayCreate([1,100,1,1], varVariant);//創(chuàng)建100*1的動(dòng)態(tài)數(shù)組。順便提一下,Cells.Item[]實(shí)際上返回的也是Range對(duì)象。從工作表中取回?cái)?shù)據(jù)基本上是寫(xiě)數(shù)據(jù)的逆過(guò)程,稍微需要注意的是如何確定工作表的數(shù)據(jù)范圍: var ir, ic : Integer; …… wkSheet.Cells.SpecialCells(xlCellTypeLastCell,EmptyParam).Activate; ir := Excel.ActiveCell.Row; ic := Excel.ActiveCell.Column; 這里巧妙地利用特殊單元格函數(shù)SpecialCells取得包含數(shù)據(jù)的最后一個(gè)單元格。 數(shù)據(jù)編輯下面是數(shù)據(jù)編輯的兩個(gè)例子。 var DestRange: OleVariant; begin DestRange := Excel.Range['C1', 'D4']; Excel.Range['A1', 'B4'].Copy(DestRange); 上面的例子復(fù)制了8個(gè)單元格的內(nèi)容。如果給Copy函數(shù)傳遞一個(gè)空參數(shù),則該區(qū)域的數(shù)據(jù)被復(fù)制到剪貼板,以后可以用Paste方法粘貼到別的位置。 var WS: _Worksheet; …… Excel.Range['A1', 'B4'].Copy(EmptyParam); //在一個(gè)工作表中復(fù)制數(shù)據(jù)到剪貼板 WS := Excel.Activesheet as _Worksheet; //改變活動(dòng)工作表 WS.Range['C1', 'D4'].Select; WS.Paste(EmptyParam, EmptyParam, lcid); //把剪貼板中的內(nèi)容粘貼到新的工作表中 格式設(shè)置選擇Excel作為報(bào)表服務(wù)器主要是因?yàn)樗鼜?qiáng)大的格式化能力。我們首先把標(biāo)題“通訊錄”進(jìn)行單元格合并,居中顯示,然后修改字體為18磅的“隸書(shū)”,粗體: with wkSheet.Range['A1','D1'],Font do begin Merge(True); //合并單元格 HorizontalAlignment:= xlCenter; Size:=18; Name:='隸書(shū)'; FontStyle:=Bold; end; 如果單元格內(nèi)容較長(zhǎng),將有部分內(nèi)容無(wú)法顯示,通常的做法是雙擊選定區(qū)域右側(cè)的邊線是各列的寬度自動(dòng)適應(yīng)內(nèi)容的長(zhǎng)度。在Delphi中通過(guò)AutoFit方法也可實(shí)現(xiàn)自適應(yīng)的列寬行高,需要注意的是該方法僅能用于整行整列,否則會(huì)提示OLE方法拒絕執(zhí)行的錯(cuò)誤: wkSheet.Columns.EntireColumn.AutoFit; 中式報(bào)表通常需要上下封頂?shù)谋砀窬€,可以使用Borders集合屬性。要注意,VBA中的集合對(duì)象通常都有一個(gè)缺省的Item屬性,Delphi中是不能省略的。Weight屬性用于定義表格線的粗細(xì): 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; 頁(yè)面設(shè)置與打印頁(yè)面設(shè)置是通過(guò)工作表的PageSetUp對(duì)象屬性設(shè)置的。Excel VBA中預(yù)設(shè)了40余種紙張常量,需要注意的是某些打印機(jī)只支持其中的一部分紙張類型。屬性O(shè)rientation用于控制打印的方向,常量landscape = 2表示橫向打印。布爾屬性CenterHorizontally和CenterVertically用于確定打印的內(nèi)容是否在水平和垂直方向上居中。 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; 打印報(bào)表可以調(diào)用工作表的PrintOut方法,VBA定義的該方法共有8個(gè)可選參數(shù),前兩個(gè)用于規(guī)定起止頁(yè),第三格式打印的份數(shù),不過(guò)在Delphi中為其在最后增加了一個(gè)LCID參數(shù),而且該參數(shù)不能使用EmptyParam。類似地,打印預(yù)覽方法PrintPreview在VBA中沒(méi)有參數(shù),而在Delphi中調(diào)用需要兩個(gè)參數(shù)。 // wkBook.PrintPreview(True,LCID); //for previewing wkSheet.PrintOut(EmptyParam,EmptyParam,1, EmptyParam,EmptyParam,EmptyParam, EmptyParam,EmptyParam,LCID); 命名區(qū)域與宏如果報(bào)表的格式比較復(fù)雜,為特定的表格區(qū)域命名然后按名引用是一種比較好的方法。Names是WorkBook的一個(gè)集合對(duì)象屬性,它有一個(gè)的Add方法可以完成這項(xiàng)工作。 Var Aname : Excel2000.Name; …… Aname := wkBook.Names.Add('通訊錄','=Sheet1!$A$3:$D$7', EmptyParam, EmptyParam, EmptyParam,EmptyParam,EmptyParam,EmptyParam, EmptyParam,EmptyParam,EmptyParam); 其中Add函數(shù)的第一個(gè)參數(shù)是定義的名稱,第二個(gè)參數(shù)是名稱所表示的單元格區(qū)域。要注意區(qū)域名稱的類型必須使用限定符,如果使用類型庫(kù)(D4),則限定符為Excel_TLB。此外,命名的區(qū)域應(yīng)使用絕對(duì)引用方式,即加上“$”符號(hào)。一旦命名了一個(gè)區(qū)域,就可以使用這個(gè)名稱來(lái)引用它,下面的一行代碼使通訊錄內(nèi)容以粗體顯示: AName.RefersToRange.Font.Bold:=True; 不過(guò)最令人驚喜的也許是你能夠在Delphi中動(dòng)態(tài)地修改Excel宏程序!下面的代碼為我們的工作簿創(chuàng)建了一個(gè)宏,在關(guān)閉工作簿時(shí)記錄上一次訪問(wèn)的時(shí)間: var LineNo: integer; CM: CodeModule; sDate:String; begin CM := WkBook.VBProject.VBComponents.Item('ThisWorkbook').Codemodule; LineNo := CM.CreateEventProc('BeforeClose', 'Workbook'); SDate:='上次訪問(wèn)日期:'+DateToStr(Date()); CM.InsertLines(LineNo + 1, ' Range("B2").Value = "'+sDate+'"'); End; 修改宏需要在前面的uses一節(jié)加上一個(gè)單元:VBIDE2000,如果使用類型庫(kù)則相應(yīng)的單元為VBIDE_TLB。這段代碼的關(guān)鍵是CodeModule對(duì)象,遺憾的是在Excel VBA help文中找不到該對(duì)象的蹤跡,只能去檢索MSDN了。 Delphi4及以前的版本 Delphi4沒(méi)有提供TExcelApplication對(duì)象,需要引入類型庫(kù)使用OLE自動(dòng)化技術(shù),Excel97的類型庫(kù)是Excel8.olb。這兩種方法的主要區(qū)別在于與服務(wù)器程序建立連接的方法,下面是通過(guò)類型庫(kù)控制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); //嘗試捕獲運(yùn)行中的程序?qū)嵗?if (Result = MK_E_UNAVAILABLE) then Excel := CoApplication.Create //啟動(dòng)新的程序?qū)嵗?else begin {檢查GetActiveObject方法調(diào)用過(guò)程中的錯(cuò)誤} OleCheck(Result); OleCheck(Unknown.QueryInterface(_Application, Excel)); end; …… //進(jìn)行數(shù)據(jù)處理 Excel.Visible[LCID] := True; // Excel.DisplayAlerts[LCID] := False; //顯示提示對(duì)話框 Excel.Quit; End; 這里沒(méi)有采用通常的try…except結(jié)構(gòu),是因?yàn)槔馓幚頇C(jī)制要進(jìn)行復(fù)雜的OLE檢查,降低了except部分的執(zhí)行速度。要注意,不同的Delphi版本生成的伴隨函數(shù)CoApplication和一些常量名可能不同,應(yīng)查看相應(yīng)的類型庫(kù)。在調(diào)用Quit方法之前,一定要釋放程序中創(chuàng)建的所有工作簿和工作表變量,否則Excel可能駐留在內(nèi)存中運(yùn)行(可以按下Ctrl+Alt+Del查看)。調(diào)用GetActiveObject捕獲程序?qū)嵗€有一個(gè)小問(wèn)題,如果Excel處于最小化運(yùn)行狀態(tài),可能出現(xiàn)只顯示程序主框架而用戶區(qū)不可見(jiàn)的情況。此外,如果不希望引入類型庫(kù),還可以采用滯后綁定的方法,不過(guò)速度要慢許多。下面的例子聲明了一個(gè)Variant變量來(lái)代表Excel應(yīng)用程序: var Excel: Variant; …… try Excel := GetActiveOleObject('Excel.Application'); except Excel := CreateOleObject('Excel.Application'); end; Excel.Visible := True; 采用滯后綁定時(shí),編譯器不對(duì)調(diào)用的Excel對(duì)象方法進(jìn)行檢查,而把這些工作交給服務(wù)器程序在執(zhí)行時(shí)完成,這樣VBA所設(shè)置的大量默認(rèn)參數(shù)(經(jīng)常有十幾個(gè))就發(fā)揮了應(yīng)有的作用,因此這種方法有一個(gè)意料不到的好處——代碼簡(jiǎn)潔: 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; 除了運(yùn)行速度慢以外,如果要使用類型庫(kù)中定義的常量,就只能自己動(dòng)手了: const xlWBATWorksheet = -4167; …… XLApp.Workbooks.Add(xlWBatWorkSheet); 最后不要忘記關(guān)閉Excel之后釋放變量: Excel := Unassigned; 以下是本文例子中所用的源代碼,在Delphi6+MSOffice2000下通過(guò)。 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:='隸書(shū)'; 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:='上次訪問(wèn)日期:'+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. Delphi與Excel的親密接觸 [王安鵬(anpengwang@263.net) 2002/4/14] Delphi作為一個(gè)出色的RAD,強(qiáng)大的數(shù)據(jù)庫(kù)功能是其最重要的特色之一,但是操縱困難的QuickReport控件常常不能滿足數(shù)據(jù)庫(kù)報(bào)表的需要。如果你的報(bào)表非常復(fù)雜,或者要求靈活地改變格式,那么使用Excel作為報(bào)表服務(wù)器是一個(gè)不錯(cuò)的選擇。Delphi從版本5開(kāi)始提供的Excel組件極大地簡(jiǎn)化了OLE自動(dòng)化技術(shù)的應(yīng)用。不過(guò)缺漏多多的幫助文件一直是Delphi最令人詬病的地方,這些新組件也不例外,本文試圖對(duì)此作一較詳細(xì)地介紹。 Excel的對(duì)象模型是一個(gè)樹(shù)狀的層次結(jié)構(gòu),根是應(yīng)用程序本身,工作簿W(wǎng)orkBook是根對(duì)象的屬性對(duì)象,本文主要討論的用于數(shù)據(jù)交換的WorkSheet則是工作簿的屬性對(duì)象,詳情參閱MSOffice提供的Excel VBA幫助文件。在Delphi中控制Excel首先要與服務(wù)器程序建立連接,打開(kāi)工作簿,然后與目標(biāo)工作表交換數(shù)據(jù),最后斷開(kāi)連接。 打開(kāi)Excel工作簿我們的例子從一個(gè)帶有TStringGrid(當(dāng)然要填上一些數(shù)據(jù))和兩個(gè)按鈕的主窗體開(kāi)始,從控制面板的Servers頁(yè)簽中拖一個(gè)TExcelApplication控件放到窗體上。首先把ConnectKind設(shè)為ckRunningOrNew,表示如果能夠檢測(cè)到運(yùn)行的Excel實(shí)例則與其建立聯(lián)系,否則啟動(dòng)Excel。另外,如果希望程序一運(yùn)行即與服務(wù)器程序建立聯(lián)系,可以把AutoConnect屬性設(shè)為True。與Excel建立聯(lián)系只要一條語(yǔ)句就可以了: Excel . Connect; 也許你已經(jīng)注意到Servers頁(yè)簽上還有其他幾個(gè)Excel控件,這些控件通過(guò)ConnectTo方法可以與前面的Excel聯(lián)系在一起: ExcelWorkbook1.ConnectTo(Excel . ActiveWorkbook); ExcelWorksheet1.ConnectTo(Excel . ActiveSheet as _Worksheet); ExcelWorksheet2.ConnectTo(Excel . Worksheets.Item['Sheet2'] as _Worksheet); 要注意,使用ConnectTo方法前必須先打開(kāi)相應(yīng)的工作簿或工作表,另外這些控件在多數(shù)情況下并不會(huì)帶來(lái)額外的便利,因此最好只使用一個(gè)TExcelApplication。一旦與Excel服務(wù)器建立聯(lián)系,就可以創(chuàng)建新的工作簿: var wkBook : _WorkBook; LCID : Integer; ... LCID := GetUserDefaultLCID(); wkBook := Excel.Workbooks.Add(EmptyParam, LCID); Add函數(shù)的第一個(gè)參數(shù)用于定義新建工作簿所使用的模板,可以使用xlWBATChart、xlWBATExcel4IntlMacroSheet、 xlWBATExcel4MacroSheet或者xlWBATWorksheet常量,也可以是已有的xls文件名。這里的EmptyParam是Variants單元與定義的變量,表示使用默認(rèn)的通用模板創(chuàng)建新工作簿。如果打開(kāi)已有的xls文檔,則應(yīng)把要打開(kāi)的文件名作為第一個(gè)參數(shù)傳遞給Open函數(shù): wkBook:=Excel.WorkBooks.Open(edtDesFile.text,EmptyParam,EmptyParam, EmptyParam,EmptyParam,EmptyParam,EmptyParam, EmptyParam,EmptyParam,EmptyParam,EmptyParam, EmptyParam,EmptyParam,LCID); 要知道,所有的數(shù)據(jù)操作主要是針對(duì)活動(dòng)工作表而言的,下面的語(yǔ)句使用一個(gè)_WorkSheet變量代表當(dāng)前的活動(dòng)單元格。如果知道工作表的名稱,其中的索引號(hào)可以用工作表名代替: wkSheet:=wkBook.Sheets[1] as _WorkSheet; 完成數(shù)據(jù)交換后需要保存工作簿: Excel.ActiveWorkBook.SaveAs ('MyOutput', EmptyParam,EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, LCID); 或者: Excel.ActiveWorkBook.Save(LCID); 最后要關(guān)閉工作簿并斷開(kāi)與Excel的連接: wkBook.Close(True, SaveAsName, EmptyParam, LCID); //Excel.Quit; Excel.Disconnect; 這里的Close方法包含有保存的功能,第一個(gè)參數(shù)說(shuō)明在關(guān)閉工作簿之前是否保存所做的修改,第二個(gè)參數(shù)給出要保存的文件名,第三個(gè)參數(shù)用于多位作者處理文檔的情況。第二行要求終止Excel的運(yùn)行。 與工作表交換數(shù)據(jù)輸入數(shù)據(jù)是對(duì)活動(dòng)工作表的某個(gè)單元格或區(qū)域進(jìn)行的,Range與cells都是工作表的對(duì)象屬性。Cells是單元格的集合,如果沒(méi)有指定具體位置可以代表整個(gè)工作表的所有單元格,但一般使用它是為了引用某個(gè)具體的單元格,比如WS.Cells.Item[1,1]就表示最左上角的單元格A1,注意在VBA中Item是Cells的默認(rèn)屬性可以省略,但在Delphi中就沒(méi)有這種便利了。為單元格賦值要引用其Value屬性,不言而喻,該屬性是一個(gè)Variant變量,例如: wkSheet.Cells.Item[1, 1].Value := '通訊錄'; 當(dāng)然你也可以為單元格指定公式: var AFormula:String; …… AFormula:='=Rand()'; wkSheet.Range['F3','G6'].Value:=AFormula; 上面的方法非常直接簡(jiǎn)單,但是速度非常慢,不適合作大型報(bào)表。那么能不能把所有的數(shù)據(jù)依次傳遞給Excel呢?我們可以使用Range,這個(gè)對(duì)象代表工作表中的一個(gè)區(qū)域,象我們用鼠標(biāo)拖出的那樣,一般是一個(gè)矩形區(qū)域,只要給定其左上角和右下角單元格的位置就可以了,如Range[‘C3’,’J42’]。這里還有一個(gè)小問(wèn)題,因?yàn)槿绻麛?shù)據(jù)超出26列(比如有100列)或者需要在運(yùn)行中確定目標(biāo)區(qū)域范圍的話,使用字符名稱標(biāo)記單元格就比較麻煩。回想一下,既然“C3”是單元格的標(biāo)記,那么我們當(dāng)然也可以使用Cells,比如Range[Cells.Item[1,1], Cells.Item[100,100]]。可以想象,Range的值應(yīng)該是數(shù)組,但是絕對(duì)不能用Delphi中的Array給它賦值!要記住,在Delphi中,Excel對(duì)象的值總是Variant類型的。 var Datas : Variant; Ir, ic: Integer; …… Datas:= varArrayCreate([1,ir,1,ic],varVariant); //這里創(chuàng)建100*100的動(dòng)態(tài)數(shù)組 …… //這里為數(shù)組元素賦值 with wkSheet do Range[cells.Item[3,1],cells.Item[ir+2,ic]].Value:=Datas; 要注意,工作表與Range都有Cells屬性,為了明確起見(jiàn),這里使用了with語(yǔ)句。此外,Range是有方向性的,用VarArrayCreate建立的一維數(shù)組只能賦給單行的Range,如果要為單列的Range定義值,必須使用二維數(shù)組,比如: Datas:=VarArrayCreate([1,100,1,1], varVariant);//創(chuàng)建100*1的動(dòng)態(tài)數(shù)組。順便提一下,Cells.Item[]實(shí)際上返回的也是Range對(duì)象。從工作表中取回?cái)?shù)據(jù)基本上是寫(xiě)數(shù)據(jù)的逆過(guò)程,稍微需要注意的是如何確定工作表的數(shù)據(jù)范圍: var ir, ic : Integer; …… wkSheet.Cells.SpecialCells(xlCellTypeLastCell,EmptyParam).Activate; ir := Excel.ActiveCell.Row; ic := Excel.ActiveCell.Column; 這里巧妙地利用特殊單元格函數(shù)SpecialCells取得包含數(shù)據(jù)的最后一個(gè)單元格。 數(shù)據(jù)編輯下面是數(shù)據(jù)編輯的兩個(gè)例子。 var DestRange: OleVariant; begin DestRange := Excel.Range['C1', 'D4']; Excel.Range['A1', 'B4'].Copy(DestRange); 上面的例子復(fù)制了8個(gè)單元格的內(nèi)容。如果給Copy函數(shù)傳遞一個(gè)空參數(shù),則該區(qū)域的數(shù)據(jù)被復(fù)制到剪貼板,以后可以用Paste方法粘貼到別的位置。 var WS: _Worksheet; …… Excel.Range['A1', 'B4'].Copy(EmptyParam); //在一個(gè)工作表中復(fù)制數(shù)據(jù)到剪貼板 WS := Excel.Activesheet as _Worksheet; //改變活動(dòng)工作表 WS.Range['C1', 'D4'].Select; WS.Paste(EmptyParam, EmptyParam, lcid); //把剪貼板中的內(nèi)容粘貼到新的工作表中 格式設(shè)置選擇Excel作為報(bào)表服務(wù)器主要是因?yàn)樗鼜?qiáng)大的格式化能力。我們首先把標(biāo)題“通訊錄”進(jìn)行單元格合并,居中顯示,然后修改字體為18磅的“隸書(shū)”,粗體: with wkSheet.Range['A1','D1'],Font do begin Merge(True); //合并單元格 HorizontalAlignment:= xlCenter; Size:=18; Name:='隸書(shū)'; FontStyle:=Bold; end; 如果單元格內(nèi)容較長(zhǎng),將有部分內(nèi)容無(wú)法顯示,通常的做法是雙擊選定區(qū)域右側(cè)的邊線是各列的寬度自動(dòng)適應(yīng)內(nèi)容的長(zhǎng)度。在Delphi中通過(guò)AutoFit方法也可實(shí)現(xiàn)自適應(yīng)的列寬行高,需要注意的是該方法僅能用于整行整列,否則會(huì)提示OLE方法拒絕執(zhí)行的錯(cuò)誤: wkSheet.Columns.EntireColumn.AutoFit; 中式報(bào)表通常需要上下封頂?shù)谋砀窬€,可以使用Borders集合屬性。要注意,VBA中的集合對(duì)象通常都有一個(gè)缺省的Item屬性,Delphi中是不能省略的。Weight屬性用于定義表格線的粗細(xì): 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; 頁(yè)面設(shè)置與打印頁(yè)面設(shè)置是通過(guò)工作表的PageSetUp對(duì)象屬性設(shè)置的。Excel VBA中預(yù)設(shè)了40余種紙張常量,需要注意的是某些打印機(jī)只支持其中的一部分紙張類型。屬性O(shè)rientation用于控制打印的方向,常量landscape = 2表示橫向打印。布爾屬性CenterHorizontally和CenterVertically用于確定打印的內(nèi)容是否在水平和垂直方向上居中。 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; 打印報(bào)表可以調(diào)用工作表的PrintOut方法,VBA定義的該方法共有8個(gè)可選參數(shù),前兩個(gè)用于規(guī)定起止頁(yè),第三格式打印的份數(shù),不過(guò)在Delphi中為其在最后增加了一個(gè)LCID參數(shù),而且該參數(shù)不能使用EmptyParam。類似地,打印預(yù)覽方法PrintPreview在VBA中沒(méi)有參數(shù),而在Delphi中調(diào)用需要兩個(gè)參數(shù)。 // wkBook.PrintPreview(True,LCID); //for previewing wkSheet.PrintOut(EmptyParam,EmptyParam,1, EmptyParam,EmptyParam,EmptyParam, EmptyParam,EmptyParam,LCID); 命名區(qū)域與宏如果報(bào)表的格式比較復(fù)雜,為特定的表格區(qū)域命名然后按名引用是一種比較好的方法。Names是WorkBook的一個(gè)集合對(duì)象屬性,它有一個(gè)的Add方法可以完成這項(xiàng)工作。 Var Aname : Excel2000.Name; …… Aname := wkBook.Names.Add('通訊錄','=Sheet1!$A$3:$D$7', EmptyParam, EmptyParam, EmptyParam,EmptyParam,EmptyParam,EmptyParam, EmptyParam,EmptyParam,EmptyParam); 其中Add函數(shù)的第一個(gè)參數(shù)是定義的名稱,第二個(gè)參數(shù)是名稱所表示的單元格區(qū)域。要注意區(qū)域名稱的類型必須使用限定符,如果使用類型庫(kù)(D4),則限定符為Excel_TLB。此外,命名的區(qū)域應(yīng)使用絕對(duì)引用方式,即加上“$”符號(hào)。一旦命名了一個(gè)區(qū)域,就可以使用這個(gè)名稱來(lái)引用它,下面的一行代碼使通訊錄內(nèi)容以粗體顯示: AName.RefersToRange.Font.Bold:=True; 不過(guò)最令人驚喜的也許是你能夠在Delphi中動(dòng)態(tài)地修改Excel宏程序!下面的代碼為我們的工作簿創(chuàng)建了一個(gè)宏,在關(guān)閉工作簿時(shí)記錄上一次訪問(wèn)的時(shí)間: var LineNo: integer; CM: CodeModule; sDate:String; begin CM := WkBook.VBProject.VBComponents.Item('ThisWorkbook').Codemodule; LineNo := CM.CreateEventProc('BeforeClose', 'Workbook'); SDate:='上次訪問(wèn)日期:'+DateToStr(Date()); CM.InsertLines(LineNo + 1, ' Range("B2").Value = "'+sDate+'"'); End; 修改宏需要在前面的uses一節(jié)加上一個(gè)單元:VBIDE2000,如果使用類型庫(kù)則相應(yīng)的單元為VBIDE_TLB。這段代碼的關(guān)鍵是CodeModule對(duì)象,遺憾的是在Excel VBA help文中找不到該對(duì)象的蹤跡,只能去檢索MSDN了。 Delphi4及以前的版本 Delphi4沒(méi)有提供TExcelApplication對(duì)象,需要引入類型庫(kù)使用OLE自動(dòng)化技術(shù),Excel97的類型庫(kù)是Excel8.olb。這兩種方法的主要區(qū)別在于與服務(wù)器程序建立連接的方法,下面是通過(guò)類型庫(kù)控制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); //嘗試捕獲運(yùn)行中的程序?qū)嵗?if (Result = MK_E_UNAVAILABLE) then Excel := CoApplication.Create //啟動(dòng)新的程序?qū)嵗?else begin {檢查GetActiveObject方法調(diào)用過(guò)程中的錯(cuò)誤} OleCheck(Result); OleCheck(Unknown.QueryInterface(_Application, Excel)); end; …… //進(jìn)行數(shù)據(jù)處理 Excel.Visible[LCID] := True; // Excel.DisplayAlerts[LCID] := False; //顯示提示對(duì)話框 Excel.Quit; End; 這里沒(méi)有采用通常的try…except結(jié)構(gòu),是因?yàn)槔馓幚頇C(jī)制要進(jìn)行復(fù)雜的OLE檢查,降低了except部分的執(zhí)行速度。要注意,不同的Delphi版本生成的伴隨函數(shù)CoApplication和一些常量名可能不同,應(yīng)查看相應(yīng)的類型庫(kù)。在調(diào)用Quit方法之前,一定要釋放程序中創(chuàng)建的所有工作簿和工作表變量,否則Excel可能駐留在內(nèi)存中運(yùn)行(可以按下Ctrl+Alt+Del查看)。調(diào)用GetActiveObject捕獲程序?qū)嵗€有一個(gè)小問(wèn)題,如果Excel處于最小化運(yùn)行狀態(tài),可能出現(xiàn)只顯示程序主框架而用戶區(qū)不可見(jiàn)的情況。此外,如果不希望引入類型庫(kù),還可以采用滯后綁定的方法,不過(guò)速度要慢許多。下面的例子聲明了一個(gè)Variant變量來(lái)代表Excel應(yīng)用程序: var Excel: Variant; …… try Excel := GetActiveOleObject('Excel.Application'); except Excel := CreateOleObject('Excel.Application'); end; Excel.Visible := True; 采用滯后綁定時(shí),編譯器不對(duì)調(diào)用的Excel對(duì)象方法進(jìn)行檢查,而把這些工作交給服務(wù)器程序在執(zhí)行時(shí)完成,這樣VBA所設(shè)置的大量默認(rèn)參數(shù)(經(jīng)常有十幾個(gè))就發(fā)揮了應(yīng)有的作用,因此這種方法有一個(gè)意料不到的好處——代碼簡(jiǎn)潔: 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; 除了運(yùn)行速度慢以外,如果要使用類型庫(kù)中定義的常量,就只能自己動(dòng)手了: const xlWBATWorksheet = -4167; …… XLApp.Workbooks.Add(xlWBatWorkSheet); 最后不要忘記關(guān)閉Excel之后釋放變量: Excel := Unassigned; 以下是本文例子中所用的源代碼,在Delphi6+MSOffice2000下通過(guò)。 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:='隸書(shū)'; 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:='上次訪問(wèn)日期:'+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.

上一篇:在Delphi中捕獲控制臺(tái)程序的輸出

下一篇:獲取其他線程的光標(biāo)句柄[DELPHI]

發(fā)表評(píng)論 共有條評(píng)論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
學(xué)習(xí)交流
熱門圖片

新聞熱點(diǎn)

疑難解答

圖片精選

網(wǎng)友關(guān)注

主站蜘蛛池模板: 欧美亚洲伦理 | 欧美视频在线免费 | 日韩视频在线观看中文字幕 | 黄色a视频 | 超碰人操 | 国产区精品在线 | 91高清视频在线观看 | 国产成人精品在线 | 97网站| 黄免费看 | 日日骚 | 亚洲精品三级 | 久久精品免费 | 日日爱夜夜爽 | 成人午夜精品一区二区三区 | 国产成人在线视频 | 在线一区视频 | 亚洲一级淫片 | 伊人精品久久久 | 九一在线观看 | 亚洲一区二区免费在线观看 | 一级片国产 | 日韩中文字幕视频在线观看 | 亚洲色图偷拍视频 | 精品成人在线 | 男女免费在线观看视频 | 中文成人在线 | 欧美一级在线 | 久久精品这里只有精品 | 久久久国产精品入口麻豆 | 欧美精品成人一区二区三区四区 | 欧美午夜视频在线观看 | 黄页网站免费观看 | 成人免费av | 日韩欧美国产一区二区三区 | 久久久日韩精品一区二区三区 | 男人的天堂久久 | 欧美精品亚洲精品 | 成人免费视频国产免费麻豆 | 超碰在线播| 成人欧美一区二区三区色青冈 |