文章出处 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平台提供的强大功能根据需要来开发更为复杂的用户自定义函数。