关注微信 欢迎致电:400-8310-286

你在这里

Excel 插件开发经验分享

诺怀博客标签: 

 

1. 这个文档写的是些什么?

 

这个文档分享了我在Excel插件开发中所遇到的问题和对应的解决方案。希望对要进行Excel插件开发或者想自己写插件增加效率的朋友有所帮助。

 

首先描述一下我的Excel开发遇到的场景:

我需要开发一个Excel的插件可以从后台Web Service获取数据然后显示到Excel中,那么就存在一个问题:Web Service的数据是通过什么方式写到Excel中呢?答案是Function!通过函数的方式写入Excel中。请看下面这幅图:

首先我会在C3 – C8 输入年份 2005 - 2010,这时候我需要在D3 - D8 使用函数 =GetPriceByYear(),参数就是C3 – C8的年份,然后从Web Service获取相应的数据写入其中。看到这里你可能会说用VBA就好了啊?VBA也可以调用Web Service啊,问什么这么麻烦用插件?

 

2. 为什么要使用插件这个东西?

 

下面我来说一下为什么要用插件。

之前这个功能的确是用VBA实现了的,但是VBA是和Excel绑定着的,换句话说你用VBA写了这个功能保存到Excel中,只有保存的文档能用这个函数功能,如果你用Excel新开一个文档,那你之前写的VBA用不到新的空白文档里面去。这就造成了麻烦。

那如果用插件呢?任何一个用文档不管是Excel新建的,还是从别的地方拷贝过来的文档,只要你的Excel安装了插件,并且插件启用了,那么就可以在任何一个文档使用这个功能。是不是比之前用VBA的方式更通用更方便呢?有.NET强大的类库支持,插件的功能可以写得更强大更实用。

那么就开始动手开发插件吧!等等,好像需要先调查一下网上有没有什么好的方案,存不存在什么问题。

 

3. 用什么开发插件?

 

.NET程序员伤不起,开发环境当然首选Visual Studio了,接下来就有两种选择:

一种是VSTO(Visual Studio Tools for Office

还有一种就是:Shared Add-in

这两种有什么异同呢?相同的点不用说了,都可以开发Excel Add-in,不同的就体现在Shared这个词上,VSTO开发的插件是针对某一个Office产品的,也就是Excel,Word,Outlook需要用VSTO开发三个插件才能在这三个产品中使用,而Shard Add-in只需要开发一个,就可以在这三个产品上使用,很强大是吧?所以根据你的需求选择最适合你的一种方式开发就好了。

如果你想深入了解他们之间的区别可以去这里看下:

Shared Add-in vs. VSTO Add-in: What's the difference between/how can I tell if I'm developing?

这时你可能兴奋得马上开始打开VS创建一个项目试试,试试归试试,你回头看到周围电脑大家用的操作系统和Office都不一样的时候,你突然开始思考了。。。

 

4.插件在Office 2000 – 2013上都能用吗?

 

答案是否定的,首先看Wiki 上的VSTO怎么说:

The developer editions of Office have been discontinued after Office XP and VSTO is available for Office 2003 and later versions only.

很遗憾Office 2000 还有 Office XP不被支持了,其实现在没看到用这两个版本的了,最低版本也就是 2003了,但是看到这句话我心有余悸,微软的这种抛弃式做法,那估计想同时兼容2003-2013也悬了,因为你看下Office2003和Office2007的界面差距有多大你就清楚了。

结果正如我所料,请看下图:

当我看到这个图我凌乱了~哦难道要一个Office版本开发一个?

经过我的不懈Google。。。和虚拟机中的不懈测试。。。

终于得出了结论:

  • 如果开发Office 2003的Add-in 使用 VS2008 + .NET 3.5 + VSTO 3.0
  • 如果开发Office 2007以上的Add-in使用(VS2010或者VS2012 )+ .NET 4.0 + VSTO 4.0

 

5. 那我写个小Demo发布一下试试?

 

“开发机上运行完美,客户机上运行不起来!”

 

这个是很多人发布插件到客户机安装后得出的结论!是这么多人都SB吗?肿么可能?是因为发布程序的陷阱太多了,换句话说,官方给了你发布程序的20个步骤,你看了之后说:你当我没发布过程序吗?傻啊,我就5步搞定!当你说出这话你就会说上面那句很多人都说的话了。。。

因为你的确需要做20步,所以发布的确很让人头疼,不过你想想发布成功之后插件启动了之后。。。多么美好啊!

  • 如果你开发的是Office 2003的插件那么请按照下面的步骤发布:

Deploying Visual Studio 2005 Tools for the Office System SE Solutions Using Windows Installer (Part 1 of 2)

Deploying Visual Studio 2005 Tools for the Office System SE Solutions Using Windows Installer (Part 2 of 2)

做完Part 1之后是Part 2,都要按步骤做哦!

另外给一个写的不错中文文章:

如何简单部署用VSTO SE 2005开发的Excel,Word插件

 

  • 如果你开发的是Office 2007以上的插件,请看这里的步骤:

http://www.cnblogs.com/brooks-dotnet/archive/2011/11/04/2236609.html

 

6. Office 2007有问题怎么办?

 

开发和发布都没有什么问题,应为 Office 2007 版本以上的插件发布安装都比 2003 不知道方便了多少,但是如果你发现调试没问题,发布后也能成功安装但是就是运行出错,首先确认你的系统是不是 windows XP,因为 Windows 7 以上不会出现这种情况,windows 7 是自带了 .NET Framework 2.0 的,但是XP没有自带,我一直奇怪用 .NET Framework 4.0 开发的插件为什么还要用 2.0 的 Framework?不过就是因为这个问题弄了我好久!

所以记住让你额客户端无论如何都装一个Framework 2.0比较保险一点。

 

7. 怎么让函数参数列表自动提示?

 

一开始写了个模块定义了一些函数,结果在Excel里面用的时候只是提示函数名参数都不提供,给其他人使用鬼晓得输入什么参数啊?

I=sho I

I=ShowMeJheParameter(

完全看不到参数提示,你说我该输入什么?

这时候如果按 Shift + Ctrl + A,下面就是见证奇迹的时刻!

I=ShowMeTheParameter(IDIrName,Year,Month)

参数列表出来了,这时候选择参数去关联Excel里面的单元格就可以了

I=ShowMeTheparameter(C6,D6,E6,F4. . . .? ?.I I I :. . . .. .

 

8. C# 如何动态调用Web Service

 

查了网上很多文章都不靠谱,自己写了一个,支持 Soapheader。


using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.IO;
using System.Web.Services.Description;
using System.CodeDom;
using Microsoft.CSharp;
using System.CodeDom.Compiler;
using System.Reflection;

namespace ExcelAddIn
{
    /// 
    /// Call WebService Dynamically (support SoapHeader)
    /// 
    public class WebServiceHelper
    {
        ///    
        /// Get WebService Class Name
        ///    
        /// WebService URL   
        /// WebService Class Name   
        private static string GetWsClassName(string wsUrl)
        {
            string[] parts = wsUrl.Split('/');
            string[] pps = parts[parts.Length - 1].Split('.');
            return pps[0];
        }

        ///    
        /// Call WebService Without SoapHeader
        ///    
        /// WebService URL   
        /// Method Name   
        /// Arguments   
        /// Method Result   
        public static object InvokeWebService(string wsUrl, string methodName, object[] args)
        {
            return InvokeWebService(wsUrl, null, methodName, null, args);
        }

        ///    
        /// Call WebService With SoapHeader
        ///    
        /// WebService URL   
        /// Method Name   
        /// Soap Header   
        /// Arguments   
        /// Method Result
        public static object InvokeWebService(string wsUrl, string methodName, SoapHeader soapHeader, object[] args)
        {
            return InvokeWebService(wsUrl, null, methodName, soapHeader, args);
        }

        ///    
        /// Call WebService
        ///    
        /// WebService URL   
        /// Class Name   
        /// Method Name   
        /// Soap Header
        /// Arguments   
        /// Method Result   
        public static object InvokeWebService(string wsUrl, string className, string methodName, SoapHeader soapHeader, object[] args)
        {
            string @namespace = "";
            if ((className == null) || (className == ""))
            {
                className = GetWsClassName(wsUrl);
            }

            WebClient wc = new WebClient();
            Stream stream = wc.OpenRead(wsUrl + "?wsdl");
            ServiceDescription sd = ServiceDescription.Read(stream);
            ServiceDescriptionImporter sdi = new ServiceDescriptionImporter();
            sdi.AddServiceDescription(sd, "", "");
            CodeNamespace cn = new CodeNamespace(@namespace);

            CodeCompileUnit ccu = new CodeCompileUnit();
            ccu.Namespaces.Add(cn);
            sdi.Import(cn, ccu);
            CSharpCodeProvider csc = new CSharpCodeProvider();
            ICodeCompiler icc = csc.CreateCompiler();

            CompilerParameters cplist = new CompilerParameters();
            cplist.GenerateExecutable = false;
            cplist.GenerateInMemory = true;
            cplist.ReferencedAssemblies.Add("System.dll");
            cplist.ReferencedAssemblies.Add("System.XML.dll");
            cplist.ReferencedAssemblies.Add("System.Web.Services.dll");
            cplist.ReferencedAssemblies.Add("System.Data.dll");

            CompilerResults cr = icc.CompileAssemblyFromDom(cplist, ccu);
            if (true == cr.Errors.HasErrors)
            {
                System.Text.StringBuilder sb = new System.Text.StringBuilder();
                foreach (System.CodeDom.Compiler.CompilerError ce in cr.Errors)
                {
                    sb.Append(ce.ToString());
                    sb.Append(System.Environment.NewLine);
                }
                throw new Exception(sb.ToString());
            }

            System.Reflection.Assembly assembly = cr.CompiledAssembly;
            Type t = assembly.GetType(@namespace + "." + className, true, true);

            FieldInfo[] arry = t.GetFields();

            FieldInfo client = null;
            object clientkey = null;
            if (soapHeader != null)
            {
                client = t.GetField(soapHeader.ClassName + "Value");
                Type typeClient = assembly.GetType(@namespace + "." + soapHeader.ClassName);
                clientkey = Activator.CreateInstance(typeClient);
                foreach (KeyValuePair property in soapHeader.Properties)
                {
                    typeClient.GetField(property.Key).SetValue(clientkey, property.Value);
                }
            }

            object obj = Activator.CreateInstance(t);

            if (soapHeader != null)
            {
                client.SetValue(obj, clientkey);
            }

            System.Reflection.MethodInfo mi = t.GetMethod(methodName);

            return mi.Invoke(obj, args);
        }

        /// 
        /// Soap Header
        /// 
        public class SoapHeader
        {
            /// 
            /// SoapHeader
            /// 
            public SoapHeader()
            {
                this.Properties = new Dictionary();
            }

            /// 
            /// SoapHeader
            /// 
            /// Soap Header Class Name
            public SoapHeader(string className)
            {
                this.ClassName = className;
                this.Properties = new Dictionary();
            }

            /// 
            /// SoapHeader
            /// 
            /// Soap Header Class Name
            /// Soap Header Properties
            public SoapHeader(string className, Dictionary properties)
            {
                this.ClassName = className;
                this.Properties = properties;
            }

            /// 
            /// Soap Header Class Name
            /// 
            public string ClassName;

            /// 
            /// Soap Header Properties
            /// 
            public Dictionary Properties;

            /// 
            /// Add a Property to SoapHeader
            /// 
            /// Soap Property Name
            /// Soap Property Value
            public void AddProperty(string name, object value)
            {
                if (this.Properties == null)
                {
                    this.Properties = new Dictionary();
                }
                Properties.Add(name, value);
            }
        }
    }
}

 

9. 待续

备案/许可证编号为:渝ICP备14000366号-1