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。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 | 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 { /// <summary> /// Call WebService Dynamically (support SoapHeader) /// </summary> public class WebServiceHelper { /// <summary> /// Get WebService Class Name /// </summary> /// <param name="wsUrl">WebService URL /// <returns>WebService Class Name</returns> private static string GetWsClassName( string wsUrl) { string [] parts = wsUrl.Split( '/' ); string [] pps = parts[parts.Length - 1].Split( '.' ); return pps[0]; } /// <summary> /// Call WebService Without SoapHeader /// </summary> /// <param name="wsUrl">WebService URL /// <param name="methodName">Method Name /// <param name="args">Arguments /// <returns>Method Result</returns> public static object InvokeWebService( string wsUrl, string methodName, object [] args) { return InvokeWebService(wsUrl, null , methodName, null , args); } /// <summary> /// Call WebService With SoapHeader /// </summary> /// <param name="wsUrl">WebService URL /// <param name="methodName">Method Name /// <param name="soapHeader">Soap Header /// <param name="args">Arguments /// <returns>Method Result</returns> public static object InvokeWebService( string wsUrl, string methodName, SoapHeader soapHeader, object [] args) { return InvokeWebService(wsUrl, null , methodName, soapHeader, args); } /// <summary> /// Call WebService /// </summary> /// <param name="wsUrl">WebService URL /// <param name="className">Class Name /// <param name="methodName">Method Name /// <param name="soapHeader">Soap Header /// <param name="args">Arguments /// <returns>Method Result</returns> 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< string , object = "" > 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); } /// <summary> /// Soap Header /// </summary> public class SoapHeader { /// <summary> /// SoapHeader /// </summary> public SoapHeader() { this .Properties = new Dictionary< string , object = "" >(); } /// <summary> /// SoapHeader /// </summary> /// <param name="className">Soap Header Class Name public SoapHeader( string className) { this .ClassName = className; this .Properties = new Dictionary< string , object = "" >(); } /// <summary> /// SoapHeader /// </summary> /// <param name="className">Soap Header Class Name /// <param name="properties">Soap Header Properties public SoapHeader( string className, Dictionary< string , object = "" > properties) { this .ClassName = className; this .Properties = properties; } /// <summary> /// Soap Header Class Name /// </summary> public string ClassName; /// <summary> /// Soap Header Properties /// </summary> public Dictionary< string , object = "" > Properties; /// <summary> /// Add a Property to SoapHeader /// </summary> /// <param name="name">Soap Property Name /// <param name="value">Soap Property Value public void AddProperty( string name, object value) { if ( this .Properties == null ) { this .Properties = new Dictionary< string , object = "" >(); } Properties.Add(name, value); } } } } </ string ,></ string ,></ string ,></ string ,></ string ,></ string ,> |