如何用C#開發Excel擴展插件


文章出處    http://support.microsoft.com/?kbid=302901

毫無疑問,微軟公司的Excel是一個非常成功的應用軟件。它不僅本身功能強,而且還允許用戶根據自己需要進行再開發以擴展其功能。傳統上,我們可以用C/C++語言或者VBA對Excel進行擴展性開發。C/C++功能強大,但是開發工作比較復雜;VBA使用起來非常方便,但是功能有限。.NET的推出為我們提供了一種能夠結合這兩者長處的再開發手段。基於.NET的解決方案可以利用所有.NET開發平台提供的強大功能,例如多線程編程、分布式計算等。這都是VBA難以做到的,事實上即使使用C/C++,我們也很難實現這些功能。不僅如此,我們還可以充分利用Visual Studio的整體開發環境,例如版本控制之類的功能。這些功能對於團隊開發非常重要,但是在VBA開發環境下則非常困難。

本文將以C#為例介紹對Excel進行再開發的方法和技巧。我們通常有三種方法來對Excel進行再開發:

一是擴展插件(AddIn)。這是開發Excel用戶自定義函數的首選。

二是VSTO(Visual Studio Tool for Office)。這是開發Excel內嵌功能的首選(例如按鈕、菜單等等)。

三是RTD(Real Time Data)。這是開發Excel實時數據應用程序的首選。

盡管它們針對的用途不同,但是開發的基本手段和過程是非常類似的。在技術細節上,這三種方法主要的不同是需要實施不同的接口。在本文中,我們將主要介紹利用C#開發Excel擴展插件的方法。讀者可以依次類推在需要的時候開發其它兩類的程序。

首先,創建一個C#庫程序(library)並指定這個類程序庫“控件可見”(Com Visible)。這通常是通過項目屬性、Assembly屬性來指定的,如下圖所示。(注意不同版本的Visual Studio可能對話框的樣子不同)。

 

 

為了方便起見,我們還可以讓程序自動注冊成控件。這可以通過項目屬性、編譯屬性來指定,如下圖所示。如果我們不指定這個屬性的話,或者我們需要在其它計算機上使用這個程序的話,我們可以使用.NET平台自帶的RegAsm.exe來手工進行注冊。

 

其次,我們的項目需要添加以下兩個引用:

Ø         (.NET)Extensibility

Ø         (COM)Microsoft Office Excel Object Library(選用最新版本)

然后,輸入以下程序。程序中的注釋已經對各部分的作用作了適當說明。注意,這個類程序並不是Excel用戶自定義函數本身,而是包含了所有需要的基本架構函數。有了這個基類以后,我們就可以編寫一個簡單的子類僅僅包含我們需要的Excel用戶自定義函數。

 

 

using System;

using System.Runtime.InteropServices;

using Extensibility;

using MyWin32 = Microsoft.Win32;

using MsExcel = Microsoft.Office.Interop.Excel;

 

namespace MyExcelAddIn

{

    ///

 

    /// Excel用戶自定義函數的基類,包含了所有必需的基礎架構類函數。它的子類則可以專注於用戶自定義函數的編寫。

    ///

 

    public class ExcelUDFBase : IDTExtensibility2

    {

        ///

 

        /// 這個函數將在我們的AddIn被注冊時調用。

        ///

 

        ///

        [ComRegisterFunctionAttribute]

        public static void RegisterFunction(Type type_)

        {

            MyWin32.Registry.ClassesRoot.CreateSubKey(GetSubKeyName(type_,"Programmable"));

 

            // 以下這兩個函數調用不是必須的。但是省略它們的話,每次我們從EXCEL中引用或取掉這個AddIn時,EXCEL都會提示找不到mscoree.dll,問你

            // 是否要刪掉這個AddIn。當然我們應該回答“不”。

            MyWin32.RegistryKey key = MyWin32.Registry.ClassesRoot.OpenSubKey(GetSubKeyName(type_, "InprocServer32"), true);

 

            key.SetValue(""

String.Format("{0}\\mscoree.dll", System.Environment.SystemDirectory)

, MyWin32.RegistryValueKind.String

);

        }

 

        ///

 

        /// 這個函數將在我們的AddIn被注銷時調用。

        ///

 

        ///

        [ComUnregisterFunctionAttribute]

        public static void UnregisterFunction(Type type_)

        {

            MyWin32.Registry.ClassesRoot.DeleteSubKey(GetSubKeyName(type_,"Programmable"));

        }

 

        ///

 

        /// 工具函數。

        ///

 

        ///

        ///

        ///

        private static string GetSubKeyName(Type type_, String sub_key_ame_)

        {

            return String.Format("CLSID\\{{{0}}}\\{1}", type_.GUID.ToString().ToUpper(), sub_key_ame_);

        } 

 

        #region IDTExtensibility2 Members

 

        ///

 

        /// 這個變量將指向Excel應用程序。

        ///

 

        protected MsExcel.Application MyExcelAppInstance { get; private set; }

 

        ///

 

        /// 這個變量將指向我們的AddIn。

        ///

 

        protected object MyAddInInstance { get; private set; }

 

        ///

 

        /// 這個函數和以下四個函數都是為了實施IDTExtensibility2接口。實施IDTExtensibility2接口並不是必須的。但是如果我們不實施這個接口的

/// 話,我們將無法得到,從而無法編寫Volatile函數。

        ///

 

        ///

        public void OnAddInsUpdate(ref Array custom)

        {

        }

 

        ///

 

        ///

 

        ///

        public void OnBeginShutdown(ref Array custom)

        {

        }

 

        ///

 

        ///

 

        ///

        ///

        ///

        ///

        public void OnConnection(object Application, ext_ConnectMode ConnectMode, objectAddInInst, ref Array custom)

        {

            MyExcelAppInstance = (MsExcel.Application)Application;

 

            MyAddInInstance = AddInInst;

        }

 

        ///

 

        ///

 

        ///

        ///

        public void OnDisconnection(ext_DisconnectMode RemoveMode, ref Array custom)

        {

        }

 

        ///

 

        ///

 

        ///

        public void OnStartupComplete(ref Array custom)

        {

        }

        #endregion

    }

}

 

最后我們可以輸入以下程序來創建了我們第一個用戶自定義函數。(當然,另寫一個類不是必須的,我們也可以把這個函數直接寫在ExcelUDFBase類里。)

 

using System;

using System.Runtime.InteropServices;

using MsExcel = Microsoft.Office.Interop.Excel;

 

namespace MyExcelAddIn

{

   ///

 

/// 這個類包括我們真正需要的用戶自定義函數。它的兩個類屬性是必須的。但是每個這樣的類應該具有唯一的GUID值。GUID可以用Visual Studio自己生成。

/// 我們只要選擇菜單中的工具(Tools),然后選擇GUID選項就可以了。

    ///

   

 

[ClassInterface(ClassInterfaceType.AutoDual), ComVisible(true)]

    [Guid("348E9F3A-96E4-42da-A5B9-FAD52E7744BB")]

     public class MyFunctions :  ExcelUDFBase

    {

        ///

 

        /// 這是個簡單的樣例函數。它的邏輯非常簡單,無需過多解釋。需要指出的是這個函數的返回值是object以及其內部的try . . . catch程序結構。

        /// 當程序出錯的時候,這樣的結構可以使我們的用戶自定義函數返回合適的錯誤信息。

        ///

 

        ///

        ///

        ///

        public object MyDivid(double Value1, double Value2)

        {

            try

            {

                return Value1 / Value2;

            }

            catch (Exception err_)

            {

                return err_.ToString();

            }

        }

}

}

 

編譯好之后,我們就可以啟動Excel。先從菜單上選擇“工具”(Tools)、“插件”(AddIn),然后點擊“自動插件”(Automation)按鈕。然后在跳出的對話框中選擇MyExcelAddIn.MyFunctions。如果一切順利的話,我們就可以使用我們剛才編寫的MyDivid()函數了,如下圖所示:

 

 

一個常見的可能問題是Excel和.NET版本不兼容。例如如果你使用的是Office 2002版而程序是用Visual Studio 2005版開發的。這時候,你就需要告訴Excel加載合適的.NET運行環境。這其實很簡單,只要創建一個內容如下的文本文件,命名為excel.exe.config,並把它保存在excel.exe同一個目錄下就可以了。

 

xml version=”1.0”?>
<configuration>
  <startup>
    <supportedRuntime version=”v2.0.50727”/>
  startup>
configuration>

 

至此,我們可以看到用C#開發Excel控件基本上和開發其它應用程序一樣簡單。我們唯一需要添加的就是在文章一開頭ExcelUDFBase類里定義的幾個函數,而這幾個函數完全可以照抄。事實上,如果我們只要編寫類似於MyDivid()這樣的函數的話,我們甚至無需實施IDTExtensibility2接口,即只需要RegisterFunction()和UnregisterFunction()這兩個函數就可以了。實施IDTExtensibility2接口的主要目的是為了獲得指向Excel和AddIn的兩個變量。有了這兩個變量,我們就可以做一些比較高級開發工作。其中最簡單但是最重要的大概是編寫所謂的易變函數(Volatile)。熟悉Excel用戶自定義函數的讀者應該知道非易變函數只要輸入沒有變化,Excel就不會重新調用該函數。而易變函數則沒有這個限制。用C#編寫易變函數其實很容易,只要在相應的函數里首先調用MyExcelAppInstance.Volatile(Type.Missing)就可以了,如下所示:

 

        ///

 

        /// 這個函數是一個易變(Volatile)函數的例子。我們只要在相應函數的開頭加上MyExcelAppInstance.Volatile(Type.Missing)就可以

        ///

 

        ///

        public double MyRand()

        {

            MyExcelAppInstance.Volatile(Type.Missing);

 

            return m_random.NextDouble();

        }

 

        ///

 

        ///

        ///

 

        private Random m_random = new Random();

C/C++開發過Excel用戶自定義函數的讀者一定知道內存管理是個大問題,甚至傳遞一個字符串也需要考慮怎么釋放內存。但是用C#則沒有這個問題,甚至C#會為我們自動進行類型轉換。請見下面這個例子:

 

        ///

 

        ///

        ///

 

        ///

        ///

        ///

        ///

        public object ShowInfo(String Name, DateTime When, MsExcel.Range Dummy)

        {

            try

            {

                return String.Format("{0} selected {2} cells at {1}.", Name, When, Dummy.Cells.Count);

            }

            catch (Exception err_)

            {

                return err_.ToString();

            }

        }

}

 

一個例子如下圖所示:

 

 

另外一個常用技巧是函數參數的缺省值。我們知道C#一般不支持缺省參數,但是在這里我們卻可以使用缺省參數。

        ///

 

        /// 使用缺省參數只要使用Optional這個屬性就可以了。

        ///

 

        ///

        ///

        public object MyEcho([Optional, DefaultParameterValue("Buddy")]String Name)

        {

            try

            {

                return Name;

            }

            catch (Exception err_)

            {

                return err_.ToString();

            }

     }

 

一個例子如下圖所示:

 

 

至此,我們已經介紹了利用C#開發Excel控件的基本方法和一些基本技巧。由此出發,我們就可以利用.NET平台提供的強大功能根據需要來開發更為復雜的用戶自定義函數。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM