重慶諾懷軟件有限公司 軟件開發(fā), app開發(fā), 微信開發(fā), 小程序開發(fā) - Excel
http://www.smt18.com/blogtag/excel
zh-hans
-
Excel 插件開發(fā)經驗分享
http://www.smt18.com/blog/excel-%E6%8F%92%E4%BB%B6%E5%BC%80%E5%8F%91%E7%BB%8F%E9%AA%8C%E5%88%86%E4%BA%AB
<div class="field field-name-body field-type-text-with-summary field-label-hidden"><div class="field-items"><div class="field-item even" property="content:encoded"><h1> </h1>
<h1><strong>1. </strong><strong>這個文檔寫的是些什么?</strong></h1>
<p> </p>
<p><em>這個文檔分享了我在Excel插件開發(fā)中所遇到的問題和對應的解決方案。希望對要進行Excel插件開發(fā)或者想自己寫插件增加效率的朋友有所幫助。</em></p>
<p> </p>
<p>首先描述一下我的Excel開發(fā)遇到的場景:</p>
<p>我需要開發(fā)一個Excel的插件可以從后臺Web Service獲取數據然后顯示到Excel中,那么就存在一個問題:Web Service的數據是通過什么方式寫到Excel中呢?答案是Function!通過函數的方式寫入Excel中。請看下面這幅圖:</p>
<p><img src="/sites/default/files/clip_image001.png" /></p>
<p>首先我會在C3 – C8 輸入年份 2005 - 2010,這時候我需要在D3 - D8 使用函數 <strong>=GetPriceByYear()</strong>,參數就是C3 – C8的年份,然后從Web Service獲取相應的數據寫入其中??吹竭@里你可能會說用VBA就好了?。縑BA也可以調用Web Service啊,問什么這么麻煩用插件?</p>
<p> </p>
<h1><strong>2. </strong><strong>為什么要使用插件這個東西?</strong></h1>
<p> </p>
<p>下面我來說一下為什么要用插件。</p>
<p>之前這個功能的確是用VBA實現了的,但是VBA是和Excel綁定著的,換句話說你用VBA寫了這個功能保存到Excel中,只有保存的文檔能用這個函數功能,如果你用Excel新開一個文檔,那你之前寫的VBA用不到新的空白文檔里面去。這就造成了麻煩。</p>
<p>那如果用插件呢?任何一個用文檔不管是Excel新建的,還是從別的地方拷貝過來的文檔,只要你的Excel安裝了插件,并且插件啟用了,那么就可以在任何一個文檔使用這個功能。是不是比之前用VBA的方式更通用更方便呢?有.NET強大的類庫支持,插件的功能可以寫得更強大更實用。</p>
<p>那么就開始動手開發(fā)插件吧!等等,好像需要先調查一下網上有沒有什么好的方案,存不存在什么問題。</p>
<p> </p>
<h1><strong>3. </strong><strong>用什么開發(fā)插件?</strong></h1>
<p> </p>
<p>.NET程序員傷不起,開發(fā)環(huán)境當然首選Visual Studio了,接下來就有兩種選擇:</p>
<p>一種是<strong>VSTO</strong><strong>(Visual Studio Tools for Office</strong><strong>)</strong></p>
<p><img src="/sites/default/files/clip_image003.jpg" /></p>
<p>還有一種就是:<strong>Shared Add-in</strong></p>
<p><img src="/sites/default/files/clip_image005.jpg" /></p>
<p>這兩種有什么異同呢?相同的點不用說了,都可以開發(fā)Excel Add-in,不同的就體現在Shared這個詞上,VSTO開發(fā)的插件是針對某一個Office產品的,也就是Excel,Word,Outlook需要用VSTO開發(fā)三個插件才能在這三個產品中使用,而Shard Add-in只需要開發(fā)一個,就可以在這三個產品上使用,很強大是吧?所以根據你的需求選擇最適合你的一種方式開發(fā)就好了。</p>
<p>如果你想深入了解他們之間的區(qū)別可以去這里看下:</p>
<p><a href="http://social.msdn.microsoft.com/Forums/vstudio/en-US/3f97705a-6052-4296-a10a-bfa3a39ab4e7/shared-addin-vs-vsto-addin-whats-the-difference-betweenhow-can-i-tell-if-im-developing?forum=vsto">Shared Add-in vs. VSTO Add-in: What's the difference between/how can I tell if I'm developing?</a></p>
<p>這時你可能興奮得馬上開始打開VS創(chuàng)建一個項目試試,試試歸試試,你回頭看到周圍電腦大家用的操作系統和Office都不一樣的時候,你突然開始思考了。。。</p>
<p> </p>
<h1><strong>4.</strong><strong>插件在Office 2000 – 2013</strong><strong>上都能用嗎?</strong></h1>
<p> </p>
<p>答案是否定的,首先看Wiki 上的VSTO怎么說:</p>
<p>The developer editions of Office have been discontinued after Office XP and VSTO is available for Office 2003 and later versions only.</p>
<p>很遺憾Office 2000 還有 Office XP不被支持了,其實現在沒看到用這兩個版本的了,最低版本也就是 2003了,但是看到這句話我心有余悸,微軟的這種拋棄式做法,那估計想同時兼容2003-2013也懸了,因為你看下Office2003和Office2007的界面差距有多大你就清楚了。</p>
<p>結果正如我所料,請看下圖:</p>
<p><img src="/sites/default/files/clip_image007.jpg" /></p>
<p>當我看到這個圖我凌亂了~哦難道要一個Office版本開發(fā)一個?</p>
<p>經過我的不懈Google。。。和虛擬機中的不懈測試。。。</p>
<p>終于得出了結論:</p>
<ul>
<li><strong>如果開發(fā)Office 2003</strong><strong>的Add-in </strong><strong>使用 VS2008 + .NET 3.5 + VSTO 3.0</strong></li>
<li><strong>如果開發(fā)Office 2007</strong><strong>以上的Add-in</strong><strong>使用(VS2010</strong><strong>或者VS2012 </strong><strong>)+ .NET 4.0 + VSTO 4.0</strong></li>
</ul>
<h1> </h1>
<h1><strong>5. </strong><strong>那我寫個小Demo</strong><strong>發(fā)布一下試試?</strong></h1>
<p> </p>
<p>“開發(fā)機上運行完美,客戶機上運行不起來!”</p>
<p> </p>
<p>這個是很多人發(fā)布插件到客戶機安裝后得出的結論!是這么多人都SB嗎?腫么可能?是因為發(fā)布程序的陷阱太多了,換句話說,官方給了你發(fā)布程序的20個步驟,你看了之后說:你當我沒發(fā)布過程序嗎?傻啊,我就5步搞定!當你說出這話你就會說上面那句很多人都說的話了。。。</p>
<p>因為你的確需要做20步,所以發(fā)布的確很讓人頭疼,不過你想想發(fā)布成功之后插件啟動了之后。。。多么美好啊!</p>
<ul>
<li>如果你開發(fā)的是Office 2003的插件那么請按照下面的步驟發(fā)布:</li>
</ul>
<p><a href="http://msdn.microsoft.com/en-us/library/bb332051(v=office.12).aspx">Deploying Visual Studio 2005 Tools for the Office System SE Solutions Using Windows Installer (Part 1 of 2)</a></p>
<p><a href="http://msdn.microsoft.com/en-us/library/bb332052(v=office.12).aspx">Deploying Visual Studio 2005 Tools for the Office System SE Solutions Using Windows Installer (Part 2 of 2)</a></p>
<p>做完Part 1之后是Part 2,都要按步驟做哦!</p>
<p>另外給一個寫的不錯中文文章:</p>
<p><a href="http://blog.csdn.net/v_jzho/article/details/1954454">如何簡單部署用VSTO SE 2005開發(fā)的Excel,Word插件</a></p>
<p> </p>
<ul>
<li>如果你開發(fā)的是Office 2007以上的插件,請看這里的步驟:</li>
</ul>
<p><a href="http://www.cnblogs.com/brooks-dotnet/archive/2011/11/04/2236609.html">http://www.cnblogs.com/brooks-dotnet/archive/2011/11/04/2236609.html</a></p>
<p> </p>
<h1><strong>6. Office 2007</strong><strong>有問題怎么辦?</strong></h1>
<p> </p>
<p>開發(fā)和發(fā)布都沒有什么問題,應為 Office 2007 版本以上的插件發(fā)布安裝都比 2003 不知道方便了多少,但是如果你發(fā)現調試沒問題,發(fā)布后也能成功安裝但是就是運行出錯,首先確認你的系統是不是 windows XP,因為 Windows 7 以上不會出現這種情況,windows 7 是自帶了 .NET Framework 2.0 的,但是XP沒有自帶,我一直奇怪用 .NET Framework 4.0 開發(fā)的插件為什么還要用 2.0 的 Framework?不過就是因為這個問題弄了我好久!</p>
<p>所以記住讓你額客戶端無論如何都裝一個Framework 2.0比較保險一點。</p>
<p> </p>
<h1><strong>7. </strong><strong>怎么讓函數參數列表自動提示?</strong></h1>
<p> </p>
<p>一開始寫了個模塊定義了一些函數,結果在Excel里面用的時候只是提示函數名參數都不提供,給其他人使用鬼曉得輸入什么參數???</p>
<p><img alt="I=sho I" src="/sites/default/files/clip_image008.png" /></p>
<p><img alt="I=ShowMeJheParameter(" src="/sites/default/files/clip_image009.png" /></p>
<p>完全看不到參數提示,你說我該輸入什么?</p>
<p>這時候如果按 Shift + Ctrl + A,下面就是見證奇跡的時刻!</p>
<p><img alt="I=ShowMeTheParameter(IDIrName,Year,Month)" src="/sites/default/files/clip_image010.png" /></p>
<p>參數列表出來了,這時候選擇參數去關聯Excel里面的單元格就可以了</p>
<p><img alt="I=ShowMeTheparameter(C6,D6,E6,F4. . . .? ?.I I I :. . . .. ." src="/sites/default/files/clip_image011.png" /></p>
<p> </p>
<h1><strong>8. C# </strong><strong>如何動態(tài)調用Web Service</strong><strong>?</strong></h1>
<p> </p>
<p>查了網上很多文章都不靠譜,自己寫了一個,支持 Soapheader。</p>
<pre class="brush: csharp">
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</param>
/// <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>
/// <param name="methodName">Method Name</param>
/// <param name="args">Arguments</param>
/// <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>
/// <param name="methodName">Method Name</param>
/// <param name="soapHeader">Soap Header</param>
/// <param name="args">Arguments</param>
/// <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>
/// <param name="className">Class Name</param>
/// <param name="methodName">Method Name</param>
/// <param name="soapHeader">Soap Header</param>
/// <param name="args">Arguments</param>
/// <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</param>
public SoapHeader(string className)
{
this.ClassName = className;
this.Properties = new Dictionary<string, object>();
}
/// <summary>
/// SoapHeader
/// </summary>
/// <param name="className">Soap Header Class Name</param>
/// <param name="properties">Soap Header Properties</param>
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>
/// <param name="value">Soap Property Value</param>
public void AddProperty(string name, object value)
{
if (this.Properties == null)
{
this.Properties = new Dictionary<string, object>();
}
Properties.Add(name, value);
}
}
}
}
</pre>
<p> </p>
<h1><em><strong>9. </strong></em><em><strong>待續(xù)</strong></em></h1>
</div></div></div><div id="comment-wrapper-nid-472"></div><div class="field field-name-field-nuova-blogtag field-type-taxonomy-term-reference field-label-above"><div class="field-label">諾懷博客標簽: </div><div class="field-items"><div class="field-item even"><a href="/blogtag/excel" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">Excel</a></div><div class="field-item odd"><a href="/blogtag/%E5%BC%80%E5%8F%A3%E5%8D%8F%E8%AE%AE" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">開口協議</a></div><div class="field-item even"><a href="/blogtag/web-service" typeof="skos:Concept" property="rdfs:label skos:prefLabel" datatype="">Web Service</a></div></div></div>
Fri, 13 Dec 2013 06:19:12 +0000
Yee
472 at http://www.smt18.com
http://www.smt18.com/blog/excel-%E6%8F%92%E4%BB%B6%E5%BC%80%E5%8F%91%E7%BB%8F%E9%AA%8C%E5%88%86%E4%BA%AB#comments