程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> Visual Studio 2008可擴展性開發(四):添加新的命令

Visual Studio 2008可擴展性開發(四):添加新的命令

編輯:關於.NET

前言

在之前的兩篇隨筆中,我介紹了Add-In的運行機制,這樣對Add-In的事件、生命周期 、與VS如何交互可以有個基本的了解了。現在是時候看看如何在VS中完成一些操作,這才 是Add-In開發的目的所在。

一般的,Add-In應當提供一些界面元素,這樣用戶可以進行某些操作,比如在主菜單 內添加一個菜單項,或者在編輯器的上下文菜單內添加一個菜單項,在本文中就來看看如 何實現這些。

關於命令(Command)

考慮一個極為常見的場景:在編寫代碼的過程中,選中一段文本,點擊Edit->Copy (或工具欄按鈕)或者按下Ctrl+C,我們可以把選中文本拷貝到剪貼板,這個過程的背後 發生了什麼?

是什麼完成的拷貝操作呢?答案是命令。可以認為命令就是某個特定的功能,如Copy 、Paste、Cut等等。VS本身就內置大量的命令(有數千個之多),而上面說到的菜單項、 工具欄或快捷鍵則執行了這些命令。通過Tools->Options菜單可以查看命令列表:

圖1:VS的命令列表

值得注意的是,執行命令並非必須通過界面元素,比如在命令窗口中:

圖2:在命令窗口執行命令

這樣也可以執行同樣的命令。現在我們知道存在多種方式來執行命令,即菜單、工具 欄、快捷鍵或命令窗口,這裡統稱為觸發者。在VS中,觸發者與命令是分離開來的:用戶 通過觸發者來執行命令,而命令負責檢查自身的狀態(名稱、是否可見、是否可用等等) 並執行。這意味著,命令可以對應一個或多個菜單項,也可以不對應任何菜單項。

另一方面,對於同一個命令,比如Edit.Copy,仍然可能有不同的情況。在文本編輯器 內和在解決方案管理器內的Edit.Copy命令執行內容並不相同。這裡有一個命令目標 (Command Target)的概念,VS將命令轉向給了命令目標,而命令目標按自己的實現來執 行該命令。總結下來就是:

對象 職責 觸發者 提供一種方式供用戶使用 命令 一個邏輯實體,檢查自身的狀態,可以執行命令,也可以轉向一個 命令目標 命令目標 根據傳遞過來的命令,按自己的實現來執行它

關於命令欄(CommandBar)

命令是個“乖孩子”,首先是我們讓它做什麼它就做什麼,第二就是它不會單獨出門 ,它會跟其它同伴待在一塊兒,它們都被放在命令欄中。比如主菜單中File下的那些菜單 項。

既然這樣,如果希望向已經存在的命令欄內添加命令,就得首先找到命令欄才能添加 。現在來看一個例子,向Tools菜單中添加一個菜單項。

添加一個新命令

使用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這兩個方法
    try
    {
        // 向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,這裡通過名稱從DET2.CommandBars 集合中進行查找。往下可以看到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來查看所有的命令和命令欄,方便以後的開發。

命令的執行

現在命令和菜單項是添加了,當用戶點擊菜單時,如何處理它呢?要實現 IDTCommandTarget接口,一旦實現了它,我們的Connect類就成為一個合格的命令目標 (Command Target)了。確切地說,當用戶點擊菜單時,VS要執行它的Exec方法,Exec方 法有如下幾個參數:

C# Code - Method Signature

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來說,這意味著出錯——不可能有其它的地方可以執行 我們自定義的命令

命令的狀態

有的命令並不總是可用,比如我們開發了一個Add-In,它專門針對於文本編輯器,如 果沒有任何文件打開,它就不應該是可用的。這裡要使用IDTCommandTarget接口的另一個 方法QueryStatus,它的參數有:

CmdName:命令的全稱

NeededText:當前來說,該參數的值只會是vsCommandStatusTextWantedNone。但是在 開發Add-In的時候,強烈建議要對此進行檢查,因為VS還保留了其它的可能值作將來之用 (可以參考向導生成的代碼)

StatusOption:這是最重要的參數(ref),我們要給它賦值,告訴VS傳入的這個命令 是否支持、是否可用或者是否可見,這幾種情況可以同時存在,此時對它們可以使用“或 ”操作。

CommandText:此參數當前VS還沒有使用,不要給它賦值

好了,了解了命令的概念,也知道如何添加、執行命令了,剩下的就是實現命令的功 能了。這裡要新建一個窗體,添加一個TreeView來顯示命令欄和命令:

圖3:CommandBarViewer窗體

要查看該窗體的具體代碼,可以在文章末尾處下載代碼。

現在只要在Connect類中稍作修改顯示窗體:

C# Code - 執行命令public void QueryStatus(string commandName, vsCommandStatusTextWanted neededText,
    ref vsCommandStatus status, ref object commandText)
{
    if (neededText == vsCommandStatusTextWanted.vsCommandStatusTextWantedNone)
    {
        if (commandName == "NEnhancer.Connect.CommandViewer")
        {
            status = (vsCommandStatus) vsCommandStatus.vsCommandStatusSupported | vsCommandStatus.vsCommandStatusEnabled;
            return;
        }
    }
}

public void Exec(string commandName, vsCommandExecOption executeOption,
    ref object varIn, ref object varOut, ref bool handled)
{
    handled = false;
    if (executeOption == vsCommandExecOption.vsCommandExecOptionDoDefault)
    {
        if (commandName == "NEnhancer.Connect.CommandViewer")
        {
            ShowCmdBarViewer();

            handled = true;
            return;
        }
    }
}

private void ShowCmdBarViewer()
{
    CommandBarViewer viewerForm = new CommandBarViewer();
    viewerForm.DTEObject = _applicationObject;
    viewerForm.ShowDialog();
}

通過這個例子,我們可以了解如何添加新的命令並執行它,其中的關鍵方法包括 OnConnection、AddNamedCommand2、Exec、QueryStatus。本文前面曾提到過命令的命名 規則,我們可以由此獲知某個菜單項對應的命令,這樣就可以在Add-In中使用該命令而不 需要再重新發明一次輪子。

執行已有命令

先看下圖。

我們可以關閉當前文檔,也可以關閉其它文檔,就是不能關閉所有文檔。有時候還是 需要這個功能的,我想你應該用過Window->Close All Documents菜單項吧?它實現的 正是我們所需要的,現在考慮如何使用這個已有的命令。首先得找到這個命令,按前面的 規則,我們到Tools-Options裡面去找(見圖1),我們可以試著輸入window.close,這時 就可以看到了:Window.CloseAllDocuments。

接下來跟前面例子類似,首先要添加一個命令,前面是把命令添加到了主菜單欄(通 過“MenuBar”獲取命令欄),現在要添加到另外一個命令欄:“Easy MDI Document Window”,這名字有些奇怪(我是在所有的CommandBar中搜了好多次才找到的),在剛才 添加Tools菜單項的代碼下面添加如下代碼:

C# Code - 添加CloseAllDocuments命令

// 通過名稱獲取要使用的命令欄
CommandBar mdiDocCommandBar =
    ((CommandBars)_applicationObject.CommandBars)["Easy MDI Document Window"];
// 添加命令
Command closeAllDocsCmd = commands.AddNamedCommand2(_addInInstance, "CloseAllDocuments", "Close All Documents",
        "Close All Documents", false, 0, ref contextGUIDS,
        (int)vsCommandStatus.vsCommandStatusSupported + (int) vsCommandStatus.vsCommandStatusEnabled,
        (int)vsCommandStyle.vsCommandStylePictAndText, vsCommandControlType.vsCommandControlTypeButton);

// 要放在Close All But This菜單項的下面
CommandBarControl closeAllButThisCmd = mdiDocCommandBar.Controls["Close All But This"];
// 設定菜單項的索引,注意索引是從1開始的
int closeAllCmdIndex = (closeAllButThisCmd == null) ? 1 : (closeAllButThisCmd.Index + 1);
closeAllDocsCmd.AddControl(mdiDocCommandBar, closeAllCmdIndex);

要使用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)移掉。

我們身在何處?

本文首先介紹了命令和命令欄的概念,正是通過命令VS才可以與Add-In進行交互。然 後通過兩個例子解釋了如何添加、執行命令,以及如何執行VS內置的命令,接下來我們就 有辦法操作VS的方方面面了:解決方案、項目、文檔、代碼等等,敬請期待:-)

出處:http://anderslly.cnblogs.com

本文配套源碼

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved