LearnVSXNow!-#3 創建一個帶有簡單命令的Package

LearnVSXNow!-#3 創建一個帶有簡單命令的Package

    爲了演示如何給我們的package增加功能,本篇將創建一個帶有簡單菜單(命令)的VS Package。和上一篇一樣,我們新建一個Visual Studio Integration Package類型的項目,這一次我們把它命名爲SimpleCommand。當項目嚮導出現後,我們選擇C#做爲開發語言,並利用嚮導爲我們的程序集自動生成一個key文件。在VSPackage Information頁面,我們輸入如下內容:

image

    在下一步,爲了創建一個簡單的菜單命令,我們選中Menu Command:

image

     當轉到下一步的時候,嚮導會要求我們填寫菜單的顯示文本和菜單的標識,請參考下圖填寫: 

image

     在嚮導的最後一步我們可以建立集成測試項目和單元測試項目,請勾掉這兩個選項並且點擊Finish按鈕。嚮導會在幾秒鐘內幫我們創建項目的源文件。 

     編譯並運行SimpleCommand項目。當Visual Studio實驗室運行後,你可以在工具菜單下發現我們的package的菜單命令:

image

     點擊菜單My First Command,可以看到一個消息框。證明這個package已經正常運行了。 

image

 

源文件分析(What is inside?)

     關掉VS實驗室,讓我們查看一下源文件。我們可以發現,與上一篇的EmptyPackage相比,這一次多了兩個文件:PkgCmdID.csSimpleCommand.vsct。在文件PkgCmdID.cs中定義了菜單“My First Command”的標識符:

   1: //在英文原文中,命名空間是MyCompany.SimpleToolWindow
   2: namespace MyCompany.SimpleCommand
   3: {
   4:   static class PkgCmdIDList
   5:   {
   6:     public const uint cmdidMyFirstCommand = 0x100;
   7:   };
   8: }

     標識符的名字是我們在項目嚮導那裏填入的,標識符的值0x100是由嚮導自動生成的。

     第二個文件SimpleCommand.vsct似乎更“令人興奮”,因爲它包含XML內容,這更符合我們進行界面定義的習慣。出於可讀性的考慮,我去掉了源文件中的註釋,該文件內容如下:

   1: <?xml version="1.0" encoding="utf-8"?>
   2: <CommandTable xmlns="http://schemas.microsoft.com/VisualStudio/2005-10-18/CommandTable" xmlns:xs="http://www.w3.org/2001/XMLSchema">
   3:   <Extern href="stdidcmd.h" mce_href="stdidcmd.h"/>
   4:   <Extern href="vsshlids.h" mce_href="vsshlids.h"/>
   5:   <Extern href="msobtnid.h" mce_href="msobtnid.h"/>  
   6:  
   7:   <Commands package="guidSimpleCommandPkg">
   8:     <Groups>
   9:       <Group guid="guidSimpleCommandCmdSet" id="MyMenuGroup" priority="0x0600">
  10:         <Parent guid="guidSHLMainMenu" id="IDM_VS_MENU_TOOLS"/>
  11:       </Group>
  12:     </Groups>  
  13:     <Buttons>
  14:       <Button guid="guidSimpleCommandCmdSet" id="cmdidMyFirstCommand"
  15:         priority="0x0100" type="Button">
  16:         <Parent guid="guidSimpleCommandCmdSet" id="MyMenuGroup" />
  17:         <Icon guid="guidImages" id="bmpPic1" />
  18:         <Strings>
  19:           <CommandName>cmdidMyFirstCommand</CommandName>
  20:           <ButtonText>My First Command</ButtonText>
  21:         </Strings>
  22:       </Button>
  23:     </Buttons>
  24:  
  25:     <Bitmaps>
  26:       <Bitmap guid="guidImages" href="Resources\Images_32bit.bmp" mce_href="Resources\Images_32bit.bmp" usedList="bmpPic1, bmpPic2, bmpPicSearch, bmpPicX, bmpPicArrows"/>
  27:     </Bitmaps>
  28:   </Commands>
  29:   
  30:   <Symbols>
  31:     <GuidSymbol name="guidSimpleCommandPkg" value="{2291da24-92e5-4ea4-bdb7-72a9b5ac7d59}" />
  32:     <GuidSymbol name="guidSimpleCommandCmdSet" value="{a982b107-4ad4-437e-b2bc-cdf2708aa376}">
  33:       <IDSymbol name="MyMenuGroup" value="0x1020" />
  34:       <IDSymbol name="cmdidMyFirstCommand" value="0x0100" />
  35:     </GuidSymbol>
  36:     <GuidSymbol name="guidImages" value="{5c3faf04-8190-48c4-a6e9-71f04f1848e5}" >
  37:       <IDSymbol name="bmpPic1" value="1" />
  38:       <IDSymbol name="bmpPic2" value="2" />
  39:       <IDSymbol name="bmpPicSearch" value="3" />
  40:       <IDSymbol name="bmpPicX" value="4" />
  41:       <IDSymbol name="bmpPicArrows" value="5" />
  42:     </GuidSymbol>
  43:   </Symbols>
  44: </CommandTable>

     .vsct文件是Visual Studio 2008 SDK裏的一種新的XML格式,vsct代表Visual Studio的命令表(Command Table),Visual Studio利用vsct文件的定義爲我們的package的命令創建用戶界面。如果你查看這個文件的屬性的話,你會發現該文件的Build Action是VSCTCompile。在package編譯過程中,vsct文件會被編譯成二進制的資源,並以1000作爲資源ID添加到VSPackage.resx資源文件中。當regpkg.exe去註冊我們的package的時候,vsct文件代表的資源也會註冊到Visual Studio中。而當VS實驗室啓動的時候,VS只需要去讀取已註冊的資源以便更新VS的界面(例如顯示菜單或工具欄項),而不需要加載我們的package。

     我認爲現在還不是深入研究vsct文件的時候,但是我想告訴你一些重要的東西:通過vsct文件,我們可以學習到VS是怎樣架構的,以及它是怎樣組織這麼多的功能和用戶界面的。
     - 命令(動作)和觸發命令的用戶界面是分開的。同一個命令可以被不同的菜單或工具欄調用。
     - 多個命令可以分組,利用分組,可以簡單的合併到已存在的菜單中。
     — 元素是可標識的符號,而不是常量。這樣就不容易出錯:標識符的名字是唯一的,VSCT編譯器會檢測輸入錯誤。

它是如何工作的?

     現在讓我們看看我們的菜單項“My First Command”是怎樣顯示在Visual Studio中的。我們必須先弄清楚最重要的問題:當我們點擊我們的命令對應的菜單項時,Visual Studio是怎樣調用相應的動作的?答案就在SimpleCommandPackage.cs文件中,所以讓我們來看一下這個文件。

     在上一篇中,我們看到EmptyPackage類有很多Attribute的標記,regpkg.exe用這些Attribute來註冊我們的package。在這一篇裏,SimpleCommandPackage類比EmptyPackage多了一個ProvideMenuResourceAttribute:

   1: ...
   2: [ProvideMenuResource(1000, 1)] 
   3: ...
   4: public sealed class SimpleCommandPackage : Package
   5: {
   6:   ...
   7: }

     ProvideMenuResourceAttribute構造函數有兩個參數,第一個參數是resourceID,這個參數值必須是1000,因爲VSCT編譯器在把vsct文件編譯到VSPackage資源中的時候,默認用1000作爲vsct文件對應的資源ID。第二個參數是versionID,它的取值在資源的緩存機制中非常重要。現在先不要深入研究它的細節了,只需要記住ProvideMenuResourceAttribute允許我們爲package註冊菜單資源。 

    爲了執行一個命令,我們需要做下面的兩步:
     — 我們需要寫一個Command Handler,裏面放置命令執行時的邏輯。
     — 我們必須以某種方式告訴Visual Studio來調用我們的Command Handler。

     在這個例子中,我們的Command Handler將顯示一個消息框。Command Handler本身是一個簡單的私有方法,包含衆所周知的EventHandler的參數。但是,在這個方法體內,卻不是僅僅調用Messagebox.Show這麼簡單:

   1: private void MenuItemCallback(object sender, EventArgs e)
   2: {
   3:   IVsUIShell uiShell = (IVsUIShell)GetService(typeof(SVsUIShell));
   4:   
   5:   Guid clsid = Guid.Empty;
   6:   int result;
   7:  
   8:   Microsoft.VisualStudio.ErrorHandler.ThrowOnFailure(
   9:     uiShell.ShowMessageBox(
  10:       0,
  11:       ref clsid,
  12:       "SimpleCommand",
  13:       string.Format(CultureInfo.CurrentCulture, "Inside {0}.MenuItemCallback()", 
  14:         this.ToString()),
  15:       string.Empty,
  16:       0,
  17:       OLEMSGBUTTON.OLEMSGBUTTON_OK,
  18:       OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST,
  19:       OLEMSGICON.OLEMSGICON_INFO,
  20:       0,        // false
  21:       out result)
  22:     );
  23: }

     爲了使用Visual Studio提供給Add-in和Package的功能,我們必須使用一些service。在我們這個例子中,我們用到了IVsUIShell這個service,它提供了若干方法去實現與界面有關的功能。爲了得到這個service的實例,我們必須調用Package基類的GetService方法,並把SVsUIShell類型作爲參數傳遞給它。

     通過這個service的實例,我們可以調用它的ShowMessageBox方法去彈出一個Visual Studio消息框。這一次我不會解釋ShowMessageBox方法的參數,你只需要知道它會彈出一個帶有“確定”按鈕的消息框就行了(譯者注:我們也可以直接用System.Windows.Forms.MessageBox.Show方法來彈出一個消息框,讀者可以自己試一下) 

     Visual Studio的底層用的是COM技術。所有實現的service都可以被COM的interop類訪問。如你所知,COM沒有提供一個運行時的異常處理機制,它僅僅通過函數的返回值去標識異常。當調用一個COM對象的方法時,我們必須手動的判斷返回值,並根據情況拋出異常。Microsoft.VisualStudio.ErrorHandler類的ThrowOnFailure方法幫助我們做了這項工作。

     所以,MenuItemCallback方法就是這樣執行我們的命令的。不過,我們還得通過某種方式告訴Visual Studio這個方法的存在,這樣當用戶點擊“My First Command”菜單的時候它纔會執行我們的命令。祕訣就在Initialize方法中,當Visual Studio加載我們的Package的時候,會調用Package的Initialize方法:

   1: protected override void Initialize()
   2: {
   3:   Trace.WriteLine (string.Format(CultureInfo.CurrentCulture, 
   4:     "Entering Initialize() of: {0}", this.ToString()));
   5:   base.Initialize();
   6:   
   7:   OleMenuCommandService mcs = 
   8:     GetService(typeof(IMenuCommandService)) as OleMenuCommandService;
   9:   if ( null != mcs )
  10:   {
  11:     CommandID menuCommandID = new CommandID(GuidList.guidSimpleCommandCmdSet, 
  12:       (int)PkgCmdIDList.cmdidMyFirstCommand);
  13:     MenuCommand menuItem = new MenuCommand(MenuItemCallback, menuCommandID );
  14:     mcs.AddCommand( menuItem );
  15:   }
  16: }
  17:  

     我們重寫了Initialize方法,在做任何動作之前,先調用基類的Initialize方法。一個命令可以通過很多方式調用:通過主菜單、通過工具欄或者通過上下文菜單。所有的菜單項都是通過命令ID和相應的命令綁定起來的。在這裏,我們把命令ID和命令處理方法綁定起來。

     通過GetService方法,我們得到了一個OleMenuCommandService的實例,它實現了IMenuCommandService接口,有差不多20個方法和一些屬性。我們利用AddCommand方法把了一個Command handler(在這裏是MenuItemCallback)和一個命令ID(menuCommandId變量)綁定其來。

    當用戶點擊“My First Command”菜單項時,它是與以GuidList.guidSimpleCommandCmdSetPkgCmdIDList.cmdidMyFirstCommand爲標識的命令綁定其來的,而這個命令ID又是和MenuItemCallback關聯的,所以會彈出消息框。

總結

     我們爲package添加了一個簡單的菜單命令。爲了添加這個命令,我們做了如下的事情:  

     — 創建了一個vsct文件去描述資源(菜單項、命令和相關的標識符)。這個文件被VSCT編譯器編譯成二進制的資源,併合併到VSPackage資源中。同時,爲了註冊這個資源,我們爲package添加了一個ProvideMenuResourceAttribute

     — 實現了一個方法去彈出消息框。這個方法利用SVsUIShell和Visual Studio交互,以便彈出消息。

     — 在package初始化的時候,我們添加了相應代碼去把命令和命令處理邏輯綁定在一起。在這裏通過OleMenuCommandService和IDE交互,以便將命令和命令處理邏輯關聯起來。

     下一次,我們將以工具窗口(Tool Window)的形式,爲package添加自己的界面。

 

原文鏈接:http://dotneteers.net/blogs/divedeeper/archive/2008/01/06/LearnVSXNowPart3.aspx

作者:明年我18
出處:http://www.cnblogs.com/default
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文鏈接,否則保留追究法律責任的權利。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章