注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

hurt0759的个人主页

人生常态--跋涉.人生暂态--歇息.

 
 
 

日志

 
 

C#开发扩展插件  

2011-04-26 08:51:06|  分类: IT |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

毫无疑问,微软公司的Excel是一个非常成功的应用软件。它不仅本身功能强,而且还允许用户根据自己需要进行再开发以扩展其功能。传统上,我们可以用C/C++语言或者VBA对Excel进行扩展性开发。C/C++功能强大,但是开发工作比较复杂;VBA使用起来非常方便,但是功能有限。.NET的推出为我们提供了一种能够结合这两者长处的再开发手段。基于.NET的解决方案可以利用所有.NET开发平台提供的强大功能,例如多线程编程、分布式计算等。这都是VBA难以做到的,事实上即使使用C/C++,我们也很难实现这些功能。不仅如此,我们还可以充分利用Visual Studio的整体开发环境,例如版本控制之类的功能。这些功能对于团队开发非常重要,但是在VBA开发环境下则非常困难。

本文将以

毫无疑问,微软公司的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可能对话框的样子不同)。

C开发扩展插件 - michael - hurt0759的个人主页 

为了方便起见,我们还可以让程序自动注册成控件。这可以通过项目属性、编译属性来指定,如下图所示。如果我们不指定这个属性的话,或者我们需要在其它计算机上使用这个程序的话,我们可以使用.NET平台自带的RegAsm.exe来手工进行注册。

C开发扩展插件 - michael - hurt0759的个人主页

其次,我们的项目需要添加以下两个引用:

?         (.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, object AddInInst, 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()函数了,如下图所示:

C开发扩展插件 - michael - hurt0759的个人主页 

一个常见的可能问题是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开发扩展插件 - michael - hurt0759的个人主页 

另外一个常用技巧是函数参数的缺省值。我们知道C#一般不支持缺省参数,但是在这里我们却可以使用缺省参数。

        ///

        /// 使用缺省参数只要使用Optional这个属性就可以了。

        ///

        ///

        ///

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

        {

            try

            {

                return Name;

            }

            catch (Exception err_)

            {

                return err_.ToString();

            }

     }

 

一个例子如下图所示:

C开发扩展插件 - michael - hurt0759的个人主页

至此,我们已经介绍了利用C#开发Excel控件的基本方法和一些基本技巧。由此出发,我们就可以利用.NET平台提供的强大功能根据需要来开发更为复杂的用户自定义函数。

为例介绍对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可能对话框的样子不同)。

C开发扩展插件 - michael - hurt0759的个人主页 

为了方便起见,我们还可以让程序自动注册成控件。这可以通过项目属性、编译属性来指定,如下图所示。如果我们不指定这个属性的话,或者我们需要在其它计算机上使用这个程序的话,我们可以使用.NET平台自带的RegAsm.exe来手工进行注册。

C开发扩展插件 - michael - hurt0759的个人主页

其次,我们的项目需要添加以下两个引用:

?         (.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, object AddInInst, 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()函数了,如下图所示:

C开发扩展插件 - michael - hurt0759的个人主页 

一个常见的可能问题是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开发扩展插件 - michael - hurt0759的个人主页 

另外一个常用技巧是函数参数的缺省值。我们知道C#一般不支持缺省参数,但是在这里我们却可以使用缺省参数。

        ///

        /// 使用缺省参数只要使用Optional这个属性就可以了。

        ///

        ///

        ///

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

        {

            try

            {

                return Name;

            }

            catch (Exception err_)

            {

                return err_.ToString();

            }

     }

 

一个例子如下图所示:

C开发扩展插件 - michael - hurt0759的个人主页

至此,我们已经介绍了利用C#开发Excel控件的基本方法和一些基本技巧。由此出发,我们就可以利用.NET平台提供的强大功能根据需要来开发更为复杂的用户自定义函数。

  评论这张
 
阅读(1844)| 评论(0)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017