多年來 COM 對象一直是 Windows 編程的基礎(chǔ),,然而隨著技術(shù)的進步和發(fā)展,微軟推出了更佳出色的.NET,。.NET Framework 提供了一個稱為公共語言運行庫的運行時環(huán)境(CLR),,它的托管執(zhí)行過程,自動的內(nèi)存管理,,以及在版本的控制上都較COM技術(shù)有很大的提高,。可以預(yù)見的是,,.NET 平臺應(yīng)用程序?qū)⒆罱K取代那些用 COM 開發(fā)的應(yīng)用程序,。但不可避免的是,在向.NET過渡時,,我們還是需要繼續(xù)使用現(xiàn)有的COM對象的,。CLR不管所用的編程語言是什么,所有.NET 應(yīng)用程序都共享一組公共類型,,這些公共類型允許對象互操作,。COM 對象的參數(shù)和返回值使用的數(shù)據(jù)類型有時會與托管代碼中的有所不同。“互操作性封送處理”是一個打包過程,,在將參數(shù)和返回值移動到 COM 對象或從 COM 對象移出時,,此過程將這些參數(shù)和返回值打包為等價的數(shù)據(jù)類型。 公共語言運行庫通過名為運行庫可調(diào)用包裝 (Runtime Callable Wrapper,,RCW) 的代理來公開 COM 對象,,如圖所示。雖然 RCW 在 .NET 客戶端看來是普通的對象,,但它的主要功能是封送在 .NET 客戶端和 COM 對象之間傳遞的調(diào)用,。同時.NET提供Interop 程序集,它用作托管和非托管代碼之間的橋梁,,將 COM 對象成員映射為等價的 .NET 托管成員,。
l .NET中如何引用COM組件?
方法一,,通過IDE來生成PIA: 首先,,工程添加引用,選擇COM選項卡,,選擇Excel Object Library xx.0(xx為版本號,,不同版本的Office,,生成的PIA的版本也不同)。如下圖所示:
這個引用過程就是RCW的打包過程,,.NET自動創(chuàng)建 PIA,。當然,你也可以通過.NET提供的工具Tlbimp.exe手動創(chuàng)建PIA,。 方法二,,手動生成PIA: 首先,啟動.NET Framework 2003工具中的控制臺:
然后找到當前操作系統(tǒng) 中安裝的EXCEL.EXE的位置,,輸 入:
結(jié)果就會在指定目錄里生成Excel.dll,。當然,你還可以指定生成PIA的命名空間名稱和程序集名,。
生成之后的dll是經(jīng)過包裝后的.NET的程序集,,可以直接引用。(很明顯,,是使用IDE來得方便,,但有當要使用一個未在Windows上注冊的COM組件時,就要使用到這個手動工具),。 引用之后就可以通過IDE的Object Browser來查看COM組件里提供的對象和方法了: 當然,,由于語法的不同,.NET上不同語言封裝之后的COM對象也稍稍有點不同,,比如C#和VB.NET,。上圖是C#工程里的Object Browser。 另外,,在ASP.NET應(yīng)用開發(fā)中使用Excel COM組件還需要對該組件進行訪問授權(quán),,因為ASP.NET程序的用戶為ASPNET,而該用戶在默認情況下是無權(quán)訪問COM對象的,。可以使用命令行命令dcomcnfg來對COM對象授權(quán),。 l Excel對象結(jié)構(gòu)(Microsoft Excel object hierarchy) 當啟動Excel應(yīng)用程序的時候,,將會啟動一個Excel Application進程(進程名為:EXCEL.EXE),一個Excel文件相當于Excel Application中的一個Workbook對象,。文件中的一個Sheet相當于Excel Workbook對象中的Worksheet對象,,而Excel單元格,行,,列,,區(qū)域都是一個Range對象。Excel里的主要對象就是Workbook, Worksheet, Range,。具體的類結(jié)構(gòu)如下圖: 你可以通過錄制Excel Macro來了解操作Excel的方法,,比如:賦值,,格式化等操作。如下圖: 然后按Alt+F11進入VBA編輯環(huán)境,,查看代碼,。在.NET中利用Excel COM組件操作Excel 其方法和屬性都跟VBA中的代碼類似,如果是VB.NET有些VBA的代碼甚至可以直接拷貝過來使用,。 l 創(chuàng)建Excel對象 以下代碼演示了怎么在.NET下新建一個Workbook,,添加一個Worksheet,并對其中的單元格賦值,,最后保存在當前程序運行目錄下: [C#] using Excel; _Application xlApp = null; _Workbook xlWorkbook = null; _Worksheet xlWorksheet = null; System.Reflection.Missing oMissing = System.Reflection.Missing.Value; string saveAsPath = ""; try { xlApp = new ApplicationClass(); xlApp.Visible = true; xlWorkbook = xlApp.Workbooks.Add(oMissing); xlWorksheet = xlWorkbook.Worksheets.Add(oMissing, oMissing, 1, oMissing) as _Worksheet; xlWorksheet.Name = "NewWorksheet"; xlWorksheet.Cells[1, 1] = "Topic: "; xlWorksheet.Cells[1, 2] = ".Net Interop Excel Demo"; saveAsPath = System.Windows.Forms.Application.StartupPath + "http://" + xlWorkbook.Name; xlWorkbook.SaveAs(saveAsPath, oMissing, oMissing, oMissing, oMissing, oMissing, Excel.XlSaveAsAccessMode.xlShared, oMissing, oMissing, oMissing, oMissing, oMissing); xlApp.Quit(); } catch(Exception ex) { MessageBox.Show(ex.Message); } finally { System.Runtime.InteropServices.Marshal.ReleaseComObject(xlApp); xlApp = null; GC.Collect(); } 在上面的代碼中,,你可能會注意到出現(xiàn)了許多oMissing對象: System.Reflection.Missing oMissing = System.Reflection.Missing.Value; 這是因為有些方法(比如:SaveAs方法)的參數(shù)是可選的,因為使用的是 C#(C#沒有VB/VB.NET中的可選參數(shù)),,必須發(fā)送一個值表明缺少值,。大家可能會認為可以簡單地傳遞null,但是方法要求使用引用傳遞參數(shù)(VB/VB.NET中的ByRef,,這些方法最初是由VB實現(xiàn)的),,因此無法使用null表示缺省值,而使用了System.Reflection.Missing.Value,。 [VB.NET] Imports Excel Dim xlApp As Excel.Application Dim xlWorkbook As Workbook Dim xlWorksheet As Worksheet Dim saveAsPath As String = "" Try xlApp = New Excel.Application xlApp.Visible = True xlWorkbook = xlApp.Workbooks.Add() xlWorksheet = xlWorkbook.Worksheets.Add() xlWorksheet.Name = "NewWorksheet" xlWorksheet.Cells(1, 1) = "Topic: " xlWorksheet.Cells(1, 2) = ".NET Interop Excel Demo" saveAsPath = System.Windows.Forms.Application.StartupPath + "/" + xlWorkbook.Name xlWorkbook.SaveAs(saveAsPath) xlApp.Quit() Catch ex As Exception MessageBox.Show(ex.Message) Finally If Not xlApp Is Nothing Then System.Runtime.InteropServices.Marshal.ReleaseComObject(xlApp) xlApp = Nothing GC.Collect() End If End Try 通過比較,,可以看到對于Excel PIA的調(diào)用上,還是VB.NET要占便宜, 畢竟是VB/VB.NET一家親,。另外一點,,上面的代碼在最后都調(diào)用了 System.Runtime.InteropServices.Marshal.ReleaseComObject(xlApp) 因為COM對象是非托管對象,雖然當RCW已經(jīng)不在程序范圍之內(nèi),,并且不能再被程序訪問,,但是RCW沒被垃圾回收器回收并銷毀,那么它就沒有真正釋放被其包裝的COM對象,,所以內(nèi)存的釋放也必須另做處理,。另外需要注意的是使用Excel Object Library COM對象不同的Office版本包裝出來的PIA中的方法會有不同,尤其在使用C#進行編程的時候需要注意參數(shù)個數(shù)在不同版本下的變化,。所以最好使用低版本的PIA以保證程序在安裝了不同版本的機器上都能運行,。 通過比較也可以發(fā)現(xiàn),因為VB.NET的可選參數(shù)的語法,,在操作Excel上,,VB.NET的代碼要比C#的代碼更加的簡潔。 l 幾種Excel賦值方法的比較 假設(shè)要將數(shù)據(jù)庫里的以下數(shù)據(jù)導出到Excel中:
No Name Title Department Telephone E-Mail 1 Jossef Goldberg President & CEO Office of the President 555-0100 2 Ashley Larsen Senior VP Sales & Mktg Sales 555-0109 3 Eric Lang Corporate Counsel Operations 555-0110 4 Linda Leste Treasurer Finance 555-0111 5 Ketan Dalal Secretary Finance 555-0112
在本示例中,以“A1”作為開始單元格,。 Delegate Sub SetValueToExcel(ByVal xlWorksheet As Worksheet, ByVal strBeginCell As String, ByVal objDataTable As System.Data.DataTable) ' SetValueToExcelCellByCell button click Private Sub Button4_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button4.Click Me.MakeExcel("SetValueToExcelCellByCell.xls", AddressOf SetValueToExcelCellByCell) End Sub ' SetValueToExcelByClipboard button click Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click Me.MakeExcel("SetValueToExcelByClipboard.xls", AddressOf SetValueToExcelByClipboard) End Sub ' SetValueToExcelByResize button click Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click Me.MakeExcel("SetValueToExcelByResize.xls", AddressOf SetValueToExcelByResize) End Sub Private Sub MakeExcel(ByVal strExcelName As String, ByVal subSetValueToExcel As SetValueToExcel) Dim xlApp As Excel.Application Dim xlWorkbook As Workbook Dim xlWorksheet As Worksheet Dim saveAsPath As String = "" Try xlApp = New Excel.Application xlApp.Visible = True xlWorkbook = xlApp.Workbooks.Add() xlWorksheet = xlWorkbook.Worksheets.Add() xlWorksheet.Name = strExcelName.Replace(".xls", "") ' Call Delegate subSetValueToExcel(xlWorksheet, "A1", Me.objDataTable) Me.FormatTable(xlWorksheet, "A1", Me.objDataTable) saveAsPath = System.Windows.Forms.Application.StartupPath + "/" + strExcelName xlApp.DisplayAlerts = False xlWorkbook.SaveAs(saveAsPath) xlApp.DisplayAlerts = True xlApp.Quit() Catch ex As Exception MessageBox.Show(ex.Message) Finally If Not xlApp Is Nothing Then System.Runtime.InteropServices.Marshal.ReleaseComObject(xlApp) xlApp = Nothing GC.Collect() End If End Try End Sub 1. 利用Offset屬性對Excel單元格賦值,。 在Excel Object Library 的Range對象中提供了一個屬性叫Offset,顧名思義就是根據(jù)該Range進行偏移,,并返回偏移之后的Range對象:
Public Overridable ReadOnly Property Offset(Optional ByVal RowOffset As Object = Nothing, Optional ByVal ColumnOffset As Object = Nothing) As Excel.Range
利用這一屬性,,我們就可以在已知開始單元格的基礎(chǔ)上進行偏移并賦值,而不用同時定位行和列的絕對位置: Private Sub SetValueToExcelCellByCell(ByVal xlWorksheet As Worksheet, ByVal strBeginCell As String, ByVal objDataTable As System.Data.DataTable) ' Output the title For i As Integer = 0 To objDataTable.Columns.Count - 1 xlWorksheet.Range(strBeginCell).Offset(0, i).Value = objDataTable.Columns(i).ColumnName Next ' Output the value For i As Integer = 0 To objDataTable.Rows.Count - 1 For j As Integer = 0 To objDataTable.Columns.Count - 1 xlWorksheet.Range(strBeginCell).Offset(1 + i, j).Value = objDataTable.Rows(i)(j).ToString().Trim() Next Next End Sub 代碼中,,第一個循環(huán)輸出表頭(列名),,因為表頭只占一行,所以行偏移量是0,;第二個循環(huán)輸出DataTable里的數(shù)據(jù),,因為表頭占去第一行,所以行偏移量從1開始,。 此方法相當于遍歷了所有要賦值的單元格,,一一進行賦值操作。也就是說當數(shù)據(jù)量為m*n的情況下,,xlWorksheet.Range(strBeginCell).Offset(1 + i, j).Value被執(zhí)行了m*n次,,跨越托管堆到非托管堆的數(shù)據(jù)轉(zhuǎn)移發(fā)生了m*n次。同時考慮到RCW將COM對象中方法的參數(shù)都包裝成Object,,因此這里還要發(fā)生大量的裝箱操作,,所以當數(shù)據(jù)量非常大的時候,該方法的速度是比較慢的,。 2. 利用系統(tǒng)剪切板進行的賦值操作 這種方法是基于Excel格式的原理而考慮的,,比如:將notepad中的以Tab分隔的數(shù)據(jù)拷貝粘貼到Excel中,你會發(fā)現(xiàn)原來Excel中的列與列是之間Tab符隔開,,行與行之間是回車換行隔開的,。 利用這種格式,我們可以想到,,先將DataTable里的數(shù)據(jù)轉(zhuǎn)化成Tab分隔的數(shù)據(jù),再放到系統(tǒng)剪切板中,,最后粘貼到Excel上就完成上面的賦值操作了,。 Private Sub SetValueToExcelByClipboard(ByVal xlWorksheet As Worksheet, ByVal strBeginCell As String, ByVal objDataTable As System.Data.DataTable) Dim objSB As System.Text.StringBuilder = New System.Text.StringBuilder ' Build the title For i As Integer = 0 To objDataTable.Columns.Count - 1 objSB.Append(objDataTable.Columns(i).ColumnName) If i < objDataTable.Columns.Count - 1 Then objSB.Append(vbTab) End If Next objSB.Append(vbCrLf) ' Build the value For i As Integer = 0 To objDataTable.Rows.Count - 1 For j As Integer = 0 To objDataTable.Columns.Count - 1 objSB.Append(objDataTable.Rows(i)(j).ToString().Trim()) If j < objDataTable.Columns.Count - 1 Then objSB.Append(vbTab) End If Next If i < objDataTable.Rows.Count - 1 Then objSB.Append(vbCrLf) End If Next System.Windows.Forms.Clipboard.SetDataObject(objSB.ToString()) xlWorksheet.Range(strBeginCell).Activate() xlWorksheet.Paste() System.Windows.Forms.Clipboard.SetDataObject("") End Sub 這里的主要操作主要是在組裝StringBuilder上,而系統(tǒng)剪切板利用DDE(Dynamic Data Exchange)的方式轉(zhuǎn)移數(shù)據(jù),,速度還是很快的,。但是,,因為系統(tǒng)剪切板是系統(tǒng)共享資源,所以在多線程的應(yīng)用程序里需要考慮對該共享資源的同步問題,。另外,,在Web應(yīng)用中,因為用戶是ASPNET而不是Administrator所以對Clipboard的訪問是沒有權(quán)限的,。因此該方法也是受限的,。 3. 利用數(shù)組進行賦值操作 因為Range.Value可以接受數(shù)組,并將數(shù)組里的值賦給Range內(nèi)相應(yīng)單元格,。利用這個特點,,我們可以將Range設(shè)定為整個要賦值的范圍,再將DataTable里的數(shù)據(jù)放到一個Object二維數(shù)組中,,讓COM對象自己完成對范圍賦值的過程,。 Private Sub SetValueToExcelByResize(ByVal xlWorksheet As Worksheet, ByVal strBeginCell As String, ByVal objDataTable As System.Data.DataTable) ' The first row is title. Dim objData(objDataTable.Rows.Count, objDataTable.Columns.Count - 1) As Object ' Set the title For i As Integer = 0 To objDataTable.Columns.Count - 1 objData(0, i) = objDataTable.Columns(i).ColumnName Next ' Set the value For i As Integer = 0 To objDataTable.Rows.Count - 1 For j As Integer = 0 To objDataTable.Columns.Count - 1 objData(1 + i, j) = objDataTable.Rows(i)(j).ToString().Trim() Next Next xlWorksheet.Range(strBeginCell).Resize(objData.GetUpperBound(0) + 1, objData.GetUpperBound(1) + 1).Value = objData End Sub 這里用到Range.Resize屬性,這個屬性將已知開始的單元格擴大為要賦值的區(qū)域,。
Public Overridable ReadOnly Property Resize(Optional ByVal RowSize As Object = Nothing, Optional ByVal ColumnSize As Object = Nothing) As Excel.Range
注意:RowSize, ColumnSize必須大于1 這個賦值過程,,跨越托管堆到非托管堆的數(shù)據(jù)轉(zhuǎn)移只有一次,而且沒有大量的裝箱操作,,也不用考慮到系統(tǒng)共享資源的問題,,所以在大數(shù)據(jù)量賦值的時候,應(yīng)該考慮使用該方法,。 最后生成Excel:
l 調(diào)用Excel宏 說到Excel就不能不提到宏,,正是因為能夠使用VBA為Excel進行二次開發(fā)使得Excel成為最好的電子表格工具,這也使得通過.NET操作Excel又多出一種渠道,,我們可以利用Excel中的VBA進行我們的快速開發(fā),。比如:利用宏將Excel轉(zhuǎn)化為PDF格式的文件。 先來看看Excel.Application.Run方法,,Run方法共有30個參數(shù),,第一個是要調(diào)用宏方法的限定名,剩下的是方法的參數(shù),。使用該方法可以調(diào)用Application中的宏,,宏可以寫在.xls或者.xla文件中,通過宏方法限定名來調(diào)用,,宏方法的限定名為: “文件名!模塊名.方法名”(如:PdfConverter.xla!MdlMain.ConvertToPDF) Private Sub ConvertToPDF(ByVal xlApp As Application, ByVal strExcelName As String, ByVal strSheetName As String) Dim strMacroFileName As String = System.Windows.Forms.Application.StartupPath + "/PdfConverter.xla" Dim strMacroMethodName As String = "PdfConverter.xla!MdlMain.ConvertToPDF" Dim strPDFFileName As String = xlApp.Workbooks(strExcelName).Path + "/" + strExcelName.Replace(".xls", "") + ".pdf" xlApp.DisplayAlerts = False xlApp.Workbooks.Open(strMacroFileName) xlApp.Run(strMacroMethodName, strExcelName, strSheetName, strPDFFileName) xlApp.DisplayAlerts = True End Sub 寫在PdfConverter.xla中的VBA代碼: Public Sub ConvertToPDF(ByVal strExcelName As String, ByVal strSheetName As String, ByVal strPDFFileName As String) ' Define the postscript and .pdf file names. Dim strPSFileName As String Dim xlWorksheet As Worksheet Dim objPdfDistiller As PdfDistiller strPSFileName = Left(strPDFFileName, InStrRev(strPDFFileName, "/")) & "tmpPostScript.ps" Application.ActivePrinter = "Adobe PDF on Ne02:" ' Print the Excel ActiveSheet to the postscript file xlWorksheet = Application.Workbooks(strExcelName).Worksheets(strSheetName) xlWorksheet.PrintOut(Copies:=1, preview:=False, ActivePrinter:="Acrobat Distiller", printtofile:=True, Collate:=True, prtofilename:=strPSFileName) ' Convert the postscript file to .pdf objPdfDistiller = New PdfDistiller objPdfDistiller.FileToPDF(strPSFileName, strPDFFileName, "") ' Finally, delete the postscript file Call Kill(strPSFileName) End Sub 調(diào)用之后生成PDF文件:
比如操作Excel,,我們最直接的方法就是利用Excel提供的Excel Object Library COM組件,并將包裝后的程序集叫做“互操作程序集” (Primary Interop Assembly, PIA),。
|
|
來自: 優(yōu)聯(lián)云圖 > 《程序片段》