<b>前言</b>
在之前的兩篇随筆中,我介紹了Add-In的運作機制,這樣對Add-In的事件、生命周期、與VS如何互動可以有個基本的了解了。現在是時候看看如何在VS中完成一些操作,這才是Add-In開發的目的所在。
一般的,Add-In應當提供一些界面元素,這樣使用者可以進行某些操作,比如在主菜單内添加一個菜單項,或者在編輯器的上下文菜單内添加一個菜單項,在本文中就來看看如何實作這些。
<b>關于指令(Command)</b>
考慮一個極為常見的場景:在編寫代碼的過程中,選中一段文本,點選Edit->Copy(或工具欄按鈕)或者按下Ctrl+C,我們可以把選中文本拷貝到剪貼闆,這個過程的背後發生了什麼?
是什麼完成的拷貝操作呢?答案是指令。可以認為指令就是某個特定的功能,如Copy、Paste、Cut等等。VS本身就内置大量的指令(有數千個之多),而上面說到的菜單項、工具欄或快捷鍵則執行了這些指令。通過Tools->Options菜單可以檢視指令清單:

圖1:VS的指令清單
值得注意的是,執行指令并非必須通過界面元素,比如在指令視窗中:
圖2:在指令視窗執行指令
這樣也可以執行同樣的指令。現在我們知道存在多種方式來執行指令,即菜單、工具欄、快捷鍵或指令視窗,這裡統稱為<b>觸發者</b>。在VS中,<b>觸發者與指令是分離開來的</b>:使用者通過觸發者來執行指令,而指令負責檢查自身的狀态(名稱、是否可見、是否可用等等)并執行。這意味着,指令可以對應一個或多個菜單項,也可以不對應任何菜單項。
另一方面,對于同一個指令,比如Edit.Copy,仍然可能有不同的情況。在文本編輯器内和在解決方案管理器内的Edit.Copy指令執行内容并不相同。這裡有一個<b>指令目标</b>(Command Target)的概念,VS将指令轉向給了指令目标,而指令目标按自己的實作來執行該指令。總結下來就是:
<b>對象</b>
<b>職責</b>
觸發者
提供一種方式供使用者使用
指令
一個邏輯實體,檢查自身的狀态,可以執行指令,也可以轉向一個指令目标
指令目标
根據傳遞過來的指令,按自己的實作來執行它
<b></b>
關于指令欄(CommandBar)
指令是個“乖孩子”,首先是我們讓它做什麼它就做什麼,第二就是它不會單獨出門,它會跟其它同伴待在一塊兒,它們都被放在指令欄中。比如主菜單中File下的那些菜單項。
既然這樣,如果希望向已經存在的指令欄内添加指令,就得首先找到指令欄才能添加。現在來看一個例子,向Tools菜單中添加一個菜單項。
<b>添加一個新指令</b>
使用Add-In向導建立一個Add-In,名字定為NEnhancer,意為對VS進行增強:),以後關于Add-In的例子都會放在這裡面。注意要選中向Tools菜單添加一個菜單項。在Connect類的OnConnection方法中可以看到添加菜單項的代碼如下:
C# Code - 向Tools菜單添加新項
try
{
// 如果需要向其它菜單欄添加指令,将Tools改為其它名稱(要使用英文的)
// 關于這些菜單欄的名稱,可以檢視CommandBar.resx檔案
string resourceName;
ResourceManager resourceManager = new ResourceManager("NEnhancer.CommandBar", Assembly.GetExecutingAssembly());
CultureInfo cultureInfo = new CultureInfo(_applicationObject.LocaleID);
if(cultureInfo.TwoLetterISOLanguageName == "zh")
{
System.Globalization.CultureInfo parentCultureInfo = cultureInfo.Parent;
resourceName = String.Concat(parentCultureInfo.Name, "Tools");
}
else
resourceName = String.Concat(cultureInfo.TwoLetterISOLanguageName, "Tools");
toolsMenuName = resourceManager.GetString(resourceName);
}
catch
// 如果沒能找到,就使用英文版本的名稱
toolsMenuName = "Tools";
// 擷取VS的主菜單
CommandBar menuBarCommandBar =
((CommandBars)_applicationObject.CommandBars)["MenuBar"];
// 擷取Tools菜單
CommandBarControl toolsControl = menuBarCommandBar.Controls[toolsMenuName];
CommandBarPopup toolsPopup = (CommandBarPopup)toolsControl;
// 如果需要添加多個菜單項,這個try/catch代碼塊可以多次使用
// 但别忘了更新QueryStatus/Exec這兩個方法
// 向Commands集合添加指令
Command command = commands.AddNamedCommand2(_addInInstance, "NEnhancer", "NEnhancer",
"Executes the command for NEnhancer", true, 59, ref contextGUIDS,
(int)vsCommandStatus.vsCommandStatusSupported+(int)vsCommandStatus.vsCommandStatusEnabled,
(int)vsCommandStyle.vsCommandStylePictAndText, vsCommandControlType.vsCommandControlTypeButton);
// 向菜單中添加新項
if((command != null) && (toolsPopup != null))
command.AddControl(toolsPopup.CommandBar, 1);
catch(System.ArgumentException)
// 如果引發異常,可能是由于同名的指令已經存在了,可以忽略該異常
代碼的功能可以檢視其中的注釋。首先通過資源檔案擷取Tools菜單的名稱(文化相關的),然後是擷取主菜單,這裡用的就是CommandBar,這裡通過名稱從<b>DET2.CommandBars</b>集合中進行查找。往下可以看到Tools菜單是主菜單欄下的一個CommandBarPopup控件,擷取了Tools菜單後就可以給它添加指令了,其關鍵是AddNamedCommand2方法,它的參數資訊為:
C# Code - Method Signature
Command AddNamedCommand2(AddIn AddInInstance, string Name, string ButtonText,
string Tooltip, bool MSOButton, object Bitmap, ref object[] ContextUIGUIDs,
int vsCommandStatusValue, int CommandStyleFlags, vsCommandControlType ControlType);
AddInInstance:用作指令對象的AddIn對象
Name:指令名稱
ButtonText:當指令顯示在菜單或工具欄時的文本
ToolTip:指令的提示資訊
MSOButton:如果是true,表示使用預定義的圖示,否則使用自定義的圖示
Bitmap:如果MSOButton值為true,那麼該參數的值将用于所用預定義圖示的索引;否則用作自定義圖示的Id
ContextUIGUIDs:VS定義了一些GUID值來辨別其狀态的改變。比如如果希望進入debug模式指令可用,可将該參數的值設定為vsContextGuidDebugging
sCommandStatusValue:指令的預設可用狀态
CommandStyleFlags:該參數用于控制指令的外觀,比如隻顯示圖示、隻顯示文本或者都顯示
ControlType:所添加指令對應的UI元素的類型,比如菜單項、組合框等
從本文開頭舉的例子可以看到,對于VS内置的菜單項,它們對應的指令名稱是有一定規律的,即按照菜單的嵌套關系,如Edit.Copy,表示Edit下的Copy項(有空格的話要去掉)。那麼對于我們的Add-In來說,也是有規律的,即Namespace.ClassName.CommandName,這裡就是NEnhancer.Connect.NEnhancer。
指令的名稱應當反應它的意圖,是以這裡把AddNamedCommand2的第二個參數改為CommandViewer。前面提到過,VS中有很多指令,它們按照指令欄來組織,這裡就做一個Add-In來檢視所有的指令和指令欄,友善以後的開發。
<b>指令的執行</b>
現在指令和菜單項是添加了,當使用者點選菜單時,如何處理它呢?要實作IDTCommandTarget接口,一旦實作了它,我們的Connect類就成為一個合格的<b>指令目标</b>(Command Target)了。确切地說,當使用者點選菜單時,VS要執行它的Exec方法,Exec方法有如下幾個參數:
void Exec(string CmdName, vsCommandExecOption ExecuteOption,
ref object VariantIn, ref object VariantOut, ref bool Handled);
CmdName:指令的全稱,Add-In中指令的命名規則是Namespace.ClassName.CommandName
vsCommandExecOption:絕大多數情況下,該參數的值是vsCommandExecOptionDoDefault值,用于通知Add-In按部就班地行事
VariantIn:如果有資料要傳給指令,就使用此參數
VariantOut:與VariantIn相反,此參數用于向調用者傳遞資料
Handler:如果設定為true,VS就知道指令已經執行完畢;如果為false,VS會尋找其它可以執行指令的方法,對Add-In來說,這意味着出錯——不可能有其它的地方可以執行我們自定義的指令
<b>指令的狀态</b>
有的指令并不總是可用,比如我們開發了一個Add-In,它專門針對于文本編輯器,如果沒有任何檔案打開,它就不應該是可用的。這裡要使用IDTCommandTarget接口的另一個方法QueryStatus,它的參數有:
CmdName:指令的全稱
NeededText:目前來說,該參數的值隻會是vsCommandStatusTextWantedNone。但是在開發Add-In的時候,強烈建議要對此進行檢查,因為VS還保留了其它的可能值作将來之用(可以參考向導生成的代碼)
StatusOption:這是最重要的參數(ref),我們要給它指派,告訴VS傳入的這個指令是否支援、是否可用或者是否可見,這幾種情況可以同時存在,此時對它們可以使用“或”操作。
CommandText:此參數目前VS還沒有使用,不要給它指派
好了,了解了指令的概念,也知道如何添加、執行指令了,剩下的就是實作指令的功能了。這裡要建立一個窗體,添加一個TreeView來顯示指令欄和指令:
圖3:CommandBarViewer窗體
要檢視該窗體的具體代碼,可以在文章末尾處下載下傳代碼。
現在隻要在Connect類中稍作修改顯示窗體:
C# Code - 執行指令
通過這個例子,我們可以了解如何添加新的指令并執行它,其中的關鍵方法包括OnConnection、AddNamedCommand2、Exec、QueryStatus。本文前面曾提到過<b>指令的命名規則</b>,我們可以由此獲知某個菜單項對應的指令,這樣就可以在Add-In中使用該指令而不需要再重新發明一次輪子。
<b>執行已有指令</b>
先看下圖。
我們可以關閉目前文檔,也可以關閉其它文檔,就是不能關閉所有文檔。有時候還是需要這個功能的,我想你應該用過Window->Close All Documents菜單項吧?它實作的正是我們所需要的,現在考慮如何使用這個已有的指令。首先得找到這個指令,按前面的規則,我們到Tools-Options裡面去找(見圖1),我們可以試着輸入window.close,這時就可以看到了:Window.CloseAllDocuments。
接下來跟前面例子類似,首先要添加一個指令,前面是把指令添加到了主菜單欄(通過“MenuBar”擷取指令欄),現在要添加到另外一個指令欄:“Easy MDI Document Window”,這名字有些奇怪(我是在所有的CommandBar中搜了好多次才找到的),在剛才添加Tools菜單項的代碼下面添加如下代碼:
C# Code - 添加CloseAllDocuments指令
要使用Window.CloseAllDocuments指令隻要一行代碼:
C# Code - Execute Command
_applicationObject.ExecuteCommand("Window.CloseAllDocuments", string.Empty);
現在菜單項看起來應該是這樣的:
趕緊試一試吧!這裡再提一下,在測試、調試完成之後,如果要釋出Add-In,最簡單的方法是将dll和.AddIn放在[My Documents Path]\Visual Studio 2008\Addins,同時把那個用來測試的.AddIn檔案(如NEnhancer - For Testing.AddIn)移掉。
<b>我們身在何處?</b>
本文首先介紹了指令和指令欄的概念,正是通過指令VS才可以與Add-In進行互動。然後通過兩個例子解釋了如何添加、執行指令,以及如何執行VS内置的指令,接下來我們就有辦法操作VS的方方面面了:解決方案、項目、文檔、代碼等等,敬請期待:-)
<b>參考</b>
《Professional Visual Studio® 2008 Extensibility》
《Working with Microsoft Visual Studio® 2005》
本文轉自一個程式員的自省部落格園部落格,原文連結:http://www.cnblogs.com/anderslly/archive/2009/03/09/vs-addin-add-command.html,如需轉載請自行聯系原作者。