Excel開發(fā)過程中有時候會遇到各種奇怪的問題,下面就列出一些本人在開發(fā)中遇到的一些比較典型的問題,并給出了解決方法,希望對大家有所幫助。
在開發(fā)機器上,有時可能會裝有多個版本的.NET運行時,有時候也可能裝有多個版本的Visual Studio,本人的開發(fā)機器上就安裝了3個版本的Visual Studio,在最新的Visual Studio 2013出來之后,就迫不及待的把工程文件遷移到2013上來了,但是在設置了啟動程序之后,調試的時候,斷點根本不能命中,也調試不進去。即使使用Attached to process方式也不行。
該問題是由于在安裝有多個.NET版本的機器上,由于某些原因,會導致Excel不知道為Com Add-in加載哪個版本的Framework導致的。注:這里僅針對是用.NET技術開發(fā)Shared Add-in插件的情況,由于VSTO 中的安裝部署文件.vsto中注明了程序集的版本,所以不存在該問題。
解決方法是新建一個名為EXCEL.EXE.config的配置文件,放到和EXCEL.EXE同級的目錄下,配置如下,在startup節(jié)點下新建supprotedRuntime節(jié)點,指定運行時的版本號,即可解決該問題:
在Shared Add-in插件開發(fā)的初期,在測試機器上測試的時候,有時會由于代碼錯誤,導致Excel直接崩潰掉的問題。在開發(fā)機器上,我們只需要將在該配置文件中,將runtime節(jié)點下的legacyUnhandledExceptionPolicy的Enable屬性設置為true,這樣,Excel在出現(xiàn)問題的時候就會彈出提示框,有助于幫助查找錯誤出現(xiàn)的原因。
這其實也是相較于VSTO,Shared Add-in應用程序的缺點之一,VSTO的一個插件是加載到各自的App Domain中的,出現(xiàn)問題不會影響其他的插件。
由于需要兼容不同版本的Excel,但是隨著版本的變化,Excel暴漏出來的API方法的參數(shù)也會發(fā)生變化。有時候采用直接調用的方式在遇到版本不同時會拋出異常,這種COM類型的異常一般很難處理和恢復。在有些時候,通過采用“晚綁定”的方式,采用反射調用方法就可以解決這一問題。
以條件著色功能為例。在使用插件獲取并輸出數(shù)據(jù)的時候,通常需要根據(jù)條件對單元格進行著色,比如,輸出股票的漲跌和漲跌幅的時候,人們一般習慣將漲的標注為紅色,跌的標注為綠色。Excel中對Range進行單元格條件可以使用FormatCoondition對象,該對象有如下方法,以添加漲跌幅的條件著色為例,代碼如下。
object missing = Type.Missing;FormatCondition conditionFall = (FormatCondition)tmpRange.FormatConditions.Add(XlFormatConditionType.xlCellValue, XlFormatConditionOperator.xlLess, "=0", missing);conditionFall.Font.ColorIndex = 10;//GreenFormatCondition conditionRise = (FormatCondition)tmpRange.FormatConditions.Add(XlFormatConditionType.xlCellValue, XlFormatConditionOperator.xlGreater, "=0", missing);conditionRise.Font.ColorIndex = 3;//Green
該方法在Excel07及以上版本中沒有問題,但是在03下就會拋出異常。由于個版本的API中,參數(shù)個數(shù)不一樣。所以采用反射的方法,將參數(shù)以數(shù)組的方式傳入即可解決該問題。
下面這個方法封裝了FoormationCoondition的Add方法。
/// <summary>///兼容Excel 2003-Excel2010的代碼條件著色,如果直接使用FormatConditions.Add方法,則不能兼容///詳情可以參考:http://social.msdn.microsoft.com/Forums/en-US/vsto/thread/8a91d154-f766-427a-963c-16dfa39e154a///使用方法如: tempRange.AddConditionValue(XlFormatConditionOperator.xlLess, "=0");/// </summary>/// <param name="R">待進行條件著色的Range區(qū)域</param>/// <param name="ConditionOperator">找色條件,如,大于,小于</param>/// <param name="Formula">具體的值</param>/// <returns></returns>public static FormatCondition AddConditionValue(Range R, XlFormatConditionOperator ConditionOperator, string Formula){ return (FormatCondition)R.FormatConditions.GetType().InvokeMember("Add", BindingFlags.InvokeMethod, null, (object)R.FormatConditions, new object[] { XlFormatConditionType.xlCellValue, ConditionOperator, Formula });}
使用該方法,上面的代碼改寫為:
FormatCondition conditionFall = AddConditionValue(tempRange, XlFormatConditionOperator.xlLess, "=0");conditionFall.Font.ColorIndex = 10;//GreenFormatCondition conditionRise = AddConditionValue(tempRange, XlFormatConditionOperator.xlGreater, "=0");conditionRise.Font.ColorIndex = 3;//Red
Excel中遇到同一方法由于不同API版本參數(shù)不同導致出現(xiàn)問題,都可以采取上面的采用反射方法調用來解決問題。
一般地通常的插件都會開發(fā)相應的自定義函數(shù) (UDF),除了使用C#類庫編寫自定義函數(shù),大部分都是將自定義函數(shù)寫在Excel的.xla或者xll文件中,有時候也會使用自定義函數(shù)編寫一些標準的模板供用戶調用。當這些模板中包含有自定義函數(shù),當用戶將該模板保存到其他位置之后,再次打開,這些單元格中的函數(shù)就會包含.xla或者.xll的路徑。比如"C:\Program Files\Installation folder\MyUDFs.xla!MyUDF"等,原因是Excel在內部會包含自定義函數(shù)文件的完整路徑。
這會導致一些問題,比如說,改自定義函數(shù)不能夠正確的執(zhí)行,不能求值等等。
就解決方法就是在vba中,在SheetOpen事件中,將所有包含函數(shù)的路徑進行重新定向到系統(tǒng)指標的自定義函數(shù)類庫的目錄下即可。VBA代碼如下,主要的方法為_Workbook.ChangeLink 方法,VBA的代碼如下。將該部分代碼寫到vba類庫中即可。
Private Sub ExcelApp_WorkbookOpen(ByVal Wb As Workbook) ReLink WbEnd SubPublic Sub ReLink(ByVal oBook As Workbook) If IsEmpty(oBook.LinkSources(xlLinkTypeExcelLinks)) Then Exit Sub Dim lk As Variant For Each lk In oBook.LinkSources(xlLinkTypeExcelLinks) If lk Like "*" & ThisWorkbook.Name And lk <> ThisWorkbook.FullName Then oBook.ChangeLink lk, ThisWorkbook.FullName, xlLinkTypeExcelLinks End If Next lkEnd Sub
還是UDF加載的問題,如果是采用.xla或者xll承載自定義函數(shù)的話,在插件啟動的時候需要加載改文件到Excel中,這樣Excel才能夠使用這里面的自定義函數(shù),并在Excel 2007及以上版本中會給出函數(shù)的智能提示。
一般地,在Shared Add-ins中加載自定義函數(shù)我們可以在OnConnection方法中,調用如下方法:
MSExcel.AddIn excelFunc = null;try{ excelFunc = applicationObject.AddIns.Add(“your .xla file path“, true);}catch (System.IO.IOException exception){ LogHelper.Error("IOException:未發(fā)現(xiàn)xla文件在所設置路徑", exception);}catch (Exception exception){ LogHelper.Error(exception);}
在大多數(shù)情況下,如果用戶直接打開Excel應用程序,插件加載就會執(zhí)行OnConnect方法,就會執(zhí)行加載.xla函數(shù)的邏輯。
但是,更多的情況是,用戶會直接雙擊一個之前保存過的,包含有我們的.xla文件中的自定義函數(shù)的excel文件,通過這種方式打開excel,插件的加載.xla在某些情況下回出現(xiàn)加載不了的情況。出現(xiàn)該問題的原因在于,我們再加載.xla文件的時候,必須要確保當前Excel中有一個空白的Sheet頁處于打開狀態(tài)。在用戶通過雙擊現(xiàn)有的excel文件的時候,在文檔打開之前,并沒有可用的Sheet頁處于打開狀態(tài),這是我們的插件加載.xla文件的時候就會出現(xiàn)加載不上的問題。
解決方法就是在OnConnect中調用RegisterOpenEvent方法注冊WorkbookActivate事件,在該事件回調方法中去注冊.xla文件。這樣在文件被打開的時候就可以加載.xla或者.xll函數(shù)了,在加載完函數(shù)之后,我們需要注銷該事件以防止重復加載。如下
private void RegisterOpenEvent(){ applicationObject.WorkbookActivate += new MSExcel.AppEvents_WorkbookActivateEventHandler(applicationObject_WorkbookActivate);}/// <summary>/// This active evevt was designed to be trigged only once to load xla file/// </summary>/// <param name="Wb"></param>void applicationObject_WorkbookActivate(MSExcel.Workbook Wb){ MSExcel.AddIn excelFunc = null; try { excelFunc = applicationObject.AddIns.Add(RuntimeHelper.MapRootPath(EmOfficeCore.Helper.PathHelper.Instance.EMFuncPath), true); } catch (System.IO.IOException exception) { LogHelper.Error("IOException:未發(fā)現(xiàn)xla文件在所設置路徑", exception); } catch (Exception exception) { LogHelper.Error(exception); } if (excelFunc.Installed == true) { applicationObject.WorkbookActivate -= new MSExcel.AppEvents_WorkbookActivateEventHandler(applicationObject_WorkbookActivate); } else { applicationObject.WorkbookActivate -= new MSExcel.AppEvents_WorkbookActivateEventHandler(applicationObject_WorkbookActivate); excelFunc.Installed = true; }}
這樣就能解決加載不上的問題。
在使用Shared Add-ins開發(fā)插件的時候,通常如果資源釋放處理不好,會存在關閉Excel后,任務管理器中的Excel.exe這個進程沒有關閉。這是由于我們在.NET中引用了非托管代碼 ,但是有沒有手動釋放,導致資源沒有被回收導致的。該問題在StackOverflow上有很詳細的討論,按照msdn上的解決方法。我們只需要在OnDisConnection 方法中對資源進行釋放即可:
/// <summary>/// Implements the OnDisconnection method of the IDTExtensibility2 interface./// Receives notification that the Add-in is being unloaded./// </summary>/// <param term='disconnectMode'>/// Describes how the Add-in is being unloaded./// </param>/// <param term='custom'>/// Array of parameters that are host application specific./// </param>/// <seealso class='IDTExtensibility2' />public void OnDisconnection(Extensibility.ext_DisconnectMode disconnectMode, ref System.Array custom){ try { GC.Collect(); Marshal.FinalReleaseComObject(applicationObject); GC.Collect(); } catch (Exception ex) { LogHelper.Error(ex); }}
在應用程序開發(fā)中,通常我們需要在Winform中導出到Excel或者對Excel文件進行讀寫,如果您選擇使用Excel API的話(當然更好的方式是直接使用OpenXML或者相似的技術直接生成Excel文件),通常的做法是直接創(chuàng)建一個Excel實例:
Microsoft.Office.Interop.Excel.Application m_objExcel = new Microsoft.Office.Interop.Excel.Application();
但是這種方式過于簡單粗暴, 他相當于直接在后臺創(chuàng)建一個Excel的實例。當您導出多次的時候會創(chuàng)建多個這樣的應用程序,并且在關閉程序的時候很可能導致這些資源不能釋放,最明顯的是您會發(fā)現(xiàn)在資源管理器中會出現(xiàn)很多個Excel的進程沒有被關閉。
還有一個場景就是插件的自動升級。 通常我們的插件需要升級,如果發(fā)現(xiàn)新的版本,則回去下載,然后對現(xiàn)有的dll進行覆蓋。通常,改升級程序時一個獨立于excel的插件,覆蓋之前通常需要關閉當前打開的Excel才能進行覆蓋。在檢測到升級并下載完新的升級包時,需要提示用戶關閉Excel,點擊確定的時候,一般的做法是強制終止任務管理器中的Excel進行,再執(zhí)行覆蓋,這樣做的問題在于:Excel會認為插件導致了Excel的意外關閉,在下次打開的時候,會直接提示改插件存在嚴重問題,是否禁用。通常一般的用戶會去點擊禁用。這樣顯然不好。
打開或者獲取Office產(chǎn)品實例的方法有很9種,比較友好的方式是,如果當前已經(jīng)有Excel的實例在運行,則不要去new新的實例,而是通過一些方法直接從現(xiàn)有的實例中去獲取和創(chuàng)建,比如可以通過Marshal.GetActiveObject方法傳入想要獲取實例的ProgID即可。對于上面的方法,更友好的版本是:
public Microsoft.Office.Interop.Excel.Application StartExcel(){ Microsoft.Office.Interop.Excel.Application instance = null; try { instance = (Microsoft.Office.Interop.Excel.Application)System.Runtime.InteropServices.Marshal.GetActiveObject("Excel.Application"); } catch (System.Runtime.InteropServices.COMException ex) { instance = new Microsoft.Office.Interop.Excel.Application(); } return instance;}
該方法首先嘗試使用GetActiveObject從當前活動的Excel實例獲取實例,如果沒有活動的Excel,則再新建一個Excel實例。這樣就避免了肯能的資源浪費。
在升級程序中,我們可以直接調用該方法返回的實例,然后調用它的Quit方法就可以。
使用Shared Com Add-in創(chuàng)建的程序在Office 2003上有可能安裝不上, 該問題在前一篇有關Excel安裝部署中 提到過,需要安裝兩個針對Office 2003的補丁。這篇文章Deploying a Shared COM add-in for Office 2003 (Visual Studio 2008 SP1) [and how to work around a known issue which causes the add-in to fail to load if KB908002 is not installed.] 有詳細介紹該補丁如何在部署時打包及安裝,這里就不在贅述了。
聯(lián)系客服