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的插件那么请按照下面的步骤发布:
做完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里面用的时候只是提示函数名参数都不提供,给其他人使用鬼晓得输入什么参数啊?
完全看不到参数提示,你说我该输入什么?
这时候如果按 Shift + Ctrl + A,下面就是见证奇迹的时刻!
参数列表出来了,这时候选择参数去关联Excel里面的单元格就可以了
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 (KeyValuePairproperty 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, Dictionaryproperties) { this.ClassName = className; this.Properties = properties; } /// /// Soap Header Class Name /// public string ClassName; ////// Soap Header Properties /// public DictionaryProperties; /// /// 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); } } } }