VS簡單注釋插件——VS插件開發續


前些時候,我寫過一篇《VS版權信息插件——初試VS插件開發小記》分享過一個用於添加注釋信息的插件,但那個插件有幾個問題:

  1. 不能添加塊注釋(/**/),只能用//來注釋(見舊文最后處的遺留問題)
  2. 添加的注釋,如果按Ctrl+Z只能一行一行的刪除(而非期望的整塊刪除)
  3. 只有一個模板,不能對多種文件進行注釋(比如模板是針對c#的,那就當然不能對xml文件注釋,因為注釋符號不同)
  4. 不能在發布到微軟的擴展庫里(不能通過VS擴展管理器來安裝)

對於以上1、2兩點,最后找到問題的根源,是因為之前的代碼是這么寫的:

    TextSelection selectedText = _vs.ActiveDocument.Selection as TextSelection; //獲取選擇的文本對象

    string copyInfo = AddInHelper.Read();   //讀取版權配置信息

    copyInfo = copyInfo.Replace("@time", DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss"));//替換時間點位符

    selectedText.Text = copyInfo;   //覆蓋選擇文本

注意最后一句,直接用注釋內容來替換選中文本的話,VS處理過程實際是一行一行來添加的,這也是為什么按Ctrl+Z是一行行的刪除了,同理塊注釋(/**/)格式錯亂的原因也是如此。

第3點算是新需求,至於第4點我開始以為注冊個賬號,發布到微軟庫里就行了呢,誰知道微軟不支持.AddIn插件的發布,官方建議使用的是.vsix格式的來發布插件。那么接下來,我又研究了下vsix是什么個東西,怎么個開發方法,於是便有了本文。

關於VSIX

關於VSIX開發,網上中文的介紹不是很多(我沒找到幾個),我在博客園看到有 Visual Studio 擴展包(.vsix)制作VS2010 Extension實踐 和 明年我18歲的這個翻譯系列(這個系列還是比較好的,我基本上是從這里扒到對VSIX開發的一些認識,有興趣的可以看看)。

由於對COM開發沒有經驗,一個小小的插件開發,苦哥我還是走了不少苦路。對於AddIn里簡簡單單的DTE對象,在VSIX里我網上找了好久才知道怎么獲得,此步着實浪費了不少時間。最終在一個開源的插件源碼里扒到(哪個插件我忘了),要像下面這樣獲得:

    var dte = (DTE)Package.GetGlobalService(typeof(DTE));

還有一處浪費了不少時間的就是多語言的支持(我是有強迫症的人,中文界面上因為安裝個插件出現幾個英文菜單,哥是很不爽的,同理英文界面上出現幾個中文,哥也不能忍受),關於這點我在以上幾篇博文里都沒扒到, google到的幾篇英文博文貌似也沒講到,最后在msdn上扒了半天,一點一點滴照着改,最終也算是完成了(下文將詳細作個介紹)。

由於本人也沒有研究太多,僅限於本插件的開發范圍,所以對VSIX的具體介紹就此處省略1G字節了……

項目結構

還是老套路,給大家簡單介紹下項目的結構,方便大家拿到源碼后快速理清代碼。下面是項目結構截圖(注意開發VSIX擴展要安裝Visual Studio SDK):

初看上去文件雖然多,但其中半數是模板生成的,下面簡單介紹一下:

  • Common目錄下是一些輔助類(配置文件讀寫、xml序列化與反序列化等)
  • Core目錄下是添加注釋的邏輯
  • Models目錄下是注釋對象實體
  • Options目錄下是用戶控件(用於 工具|選項 窗口)
  • Resources目錄下是一些資源文件
  • zh-CN目錄和其他同樣顏色框出的都是本地化要用的資源文件(下文會詳細說明)
  • SimpleAnnotationPackage.cs是整個擴展的入口,是一個插件與VS連接的地方
  • SimpleAnnotation.vsct是定義菜單的xml文件
  • Source.extensio.vsixmanifest是用來描述插件信息的
  • Guids.cs封裝一些guid(貌似在COM編輯世界里,用GUID來作為組件ID ?)
  • PkgCmdID.cs定義菜單命令的ID

遺留問題的解決

關於1、2兩點遺留問題,既然找到了問題的根源,那也有找到了解決方案。其實只要使用TextSelection類型的Insert方法來插入內容就可以了,像下面這樣(代碼參見Core目錄下的AnnotationCore.cs文件):

selectedText.Insert(annotation.Replace("@time", DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss")));

關於第3點多種文件格式多種模板的支持,其實是在我使用上一個插件的過程中想到要加的功能,因為cs文件的注釋模板明顯不能用在xml文件里。這個功能實現也不復雜,只要用key/value的形式,用key來保存支持的文件擴展名,用value來保存對應的注釋內容就行了。

Setting文件的問題

為什么要說setting文件,因為開始我是用setting文件來保存配置好的注釋模板,而不是用config或xml,因為后兩者都要自己解析,處理起來遠沒有setting文件的強類型化用起來爽(關於setting文件,不太了解的可以參考一下園子里的這篇博文)。但是一個很奇怪的事情是,每次保存好配置,再次打開VS后都沒了。。。

研究半天不得解決,最終換成用xml來保存。不知道是我使用的問題,還是在插件開發中就不能用setting文件,還請知道的園友指點!

XML序列化的問題

當我把配置方案從setting換成xml后,又出現一個小問題,開始我是用Dictionary<string,string>來保存注釋模板,結果Dictionary不支持直接XML序列化,網上查了查解決方案,感覺太麻煩了。所以寫了個Annotation類型:

    public sealed class Annotation
    {

        private string _fileExtension;

        /// <summary>
        /// 支持的文件擴展名
        /// </summary>
        public string FileExtension
        {
            get { return _fileExtension; }
            set { _fileExtension = value; }
        }

        private string _content;

        /// <summary>
        /// 注釋內容
        /// </summary>
        public string Content
        {
            get { return _content; }
            set { _content = value; }
        }
}

然后用一個List來保存配置模板,這樣序列化的問題就解決了,可是當我打開生成的xml文件時(文件保存在%User%\AppData\Local\SimpleAnnotation目錄下),發現Content(注釋內容節點)被編碼了:

雖然不影響功能,但非傳說中的可見即可得,看上去非常不直觀。所以我想到了CData節點,本以為只要在Content屬性上加上某個特性(Attribute)標記一下就行了呢,一查才知道,微軟這次這么不給力,竟然沒有提供這樣的特性。然后找到的解決方案,都是自定義一個CData類型,實現IXmlSerializable,但我感覺這樣的解決方案甚是丑陋,不過在通過了解這種解決方案的時候,想到一種“更好的”辦法,其實只要將string類型包裝成XmlCDataSection類型給序列化器應該就行了。所以將Annotation類改成了下面的樣子,一個string類型的Content屬性用來在程序里操作_content字段,一個XmlCDataSection類型的CdataContent屬性用來提供給XML序列化器,這樣就解決了string到CData節點的序列化問題,也沒有增加任何類,感覺還算比較滿意(要是能不把CDataContent屬性暴露出來就更完美了,可惜貌似辦不到)。

    [Serializable]
    public sealed class Annotation
    {

        private string _fileExtension;

        /// <summary>
        /// 支持的文件擴展名
        /// </summary>
        public string FileExtension
        {
            get { return _fileExtension; }
            set { _fileExtension = value; }
        }

        private string _content;

        /// <summary>
        /// 注釋內容
        /// </summary>
        [XmlIgnore]
        public string Content
        {
            get { return _content; }
            set { _content = value; }
        }

        /// <summary>
        /// 內部序列化使用
        /// </summary>
        [XmlElement("Content")]
        public XmlCDataSection CDataContent
        {
            get
            {
                return new XmlDocument().CreateCDataSection(_content);
            }
            set
            {
                _content = value.InnerText;
            }
        }
    }

本地化總結

前文也提到了我為什么要本地化,不是為了給外國人用,是哥有強迫症(T_T),也同時是為了練習一下傳說中的資源文件(之前一直沒用過)。在此希望我這篇博文能對國內VSIX本地化這塊欠缺的內容作一點補充。

1、本地化擴展管理器里的內容

這里是一個插件第一次向人展現自己的地方,這里的本地化是通過在項目根目錄下,新建zh-CN目錄(其他語言同種做法),然后增加一個名為Extension.vsixlangpack的xml文件,內容如下:

<?xml version="1.0" encoding="utf-8"?>
<VsixLanguagePack Version="1.0.0" xmlns="http://schemas.microsoft.com/developer/vsx-schema-lp/2010">
  <LocalizedName>Simple Annotation</LocalizedName>
  <LocalizedDescription>Simple Annotation(簡單注釋)是一個用於讓添加注釋變得更簡單的VS插件。</LocalizedDescription>
  <MoreInfoUrl></MoreInfoUrl>
</VsixLanguagePack>

同時要將屬性Include in VSIX改為true,還有一點要注意,就是你的source.extension.vsixmanifest里一定不要與這里設置重復了,不然不會使用這里的配置。

我開始將這里的Locale設置成中國,結果就是按上面怎么配置都不顯示中文!這塊內容是從msdn扒到的,有興趣的童鞋也可自行研究。

2、本地化幫助|關於里的內容

我們先看一段代碼(代碼在SimpleAnnotationPackage.cs文件里),

    [PackageRegistration(UseManagedResourcesOnly = true)]
    [InstalledProductRegistration("#110", "#112", "1.0", IconResourceID = 400)]     //幫助|關於 注冊
    [ProvideMenuResource("Menus.ctmenu", 1)]    //菜單 注冊
    [Guid(GuidList.guidSimpleAnnotationPkgString)]
    [ProvideOptionPage(typeof(GeneralOptionPage), "SimpleAnnotation", "General", 201, 202, true)]   //工具|選項 注冊
    public sealed class SimpleAnnotationPackage : Package

以上代碼是VS的擴展開發模板自動生成的,這里可以看到所謂的擴展就是自定義的Package,上面加了一大堆特性,其中幾個需要注意的我已經加了注釋。

幫助|關於里的內容,是通過InstalledProductRegistration特性來注冊的(說法可能有問題,應該不影響理解),其構造方法如下:

    public InstalledProductRegistrationAttribute(string productName, string productDetails, string productId);

因此我們代碼中的”#110”便是productName,”#112”便是productDetails,這里的”#110”和”#112”就是資源文件VSPackage.resx里資源的名稱:

到這步,我很自然地想到了,新增一個資源文件VSPackage.zh-CN.resx,但我只猜對了開頭,沒有猜對結尾。雖然是要增加這么個資源文件,但並不是增加個這個文件就能本地化了。

最后還是要苦扒msdn ,首先要將默認的資源文件VSPackage.resx改名為VSPackage.en-US.resx(一定要改啊,擦),然后要將AssemblyInfo.cs里應對的地方改成:

    [assembly: NeutralResourcesLanguage("en-US"(此處是默認語言), ltimateResourceFallbackLocation.Satellite)]

最后要卸載項目,鼠標右鍵編輯項目文件,找到包含EmbeddedResource 的ItemGroup,將內容改成下面這樣(msdn上這里代碼貼錯了,害我搞了半天沒成功):

    <EmbeddedResource Include="VSPackage.en-US.resx">
      <MergeWithCTO>true</MergeWithCTO>
      <LogicalName>VSPackage.en-US.Resources</LogicalName>
    </EmbeddedResource>
    <EmbeddedResource Include="VSPackage.zh-CN.resx">
      <MergeWithCTO>true</MergeWithCTO>
      <LogicalName>VSPackage.zh-CN.Resources</LogicalName>
    </EmbeddedResource>

上面代碼貼的是我的配置,相信再增加其他語言的支持,聰明的你肯定知道怎么處理了^_^。

3、本地化配置頁里的菜單名稱

這里的左邊菜單本地化方式同關於頁里一樣,只要在對應的VSPackage資源文件里增加201和202的菜單名稱就可以了:

右邊的菜單本地化就方便多了,因為這里是一個用戶控件,使用的資源文件是我添加的Resources.resx文件

在用戶控件初始化后,用資源來設置界面顯示(代碼在Options目錄下GeneralOptionControl.cs里):

        public GeneralOptionControl()
        {
            InitializeComponent();

            btnSave.Text = Resources.Save;
            btnRemove.Text = Resources.Remove;
            btnClear.Text = Resources.Clear;
            lblSetFileExtension.Text = Resources.lblSetFileExtension;
            lblSetAnnotation.Text = Resources.lblSetAnnotation;
            lblRemark.Text = Resources.lblRemark;
        }

這里我們只要添加相應的Resources.zh-CN.resx就可以實現界面的中文化了。不過還有一點要注意一下,必須還要增加一個Resources.en-US.resx的資源(即使我這里它的內容與Resources.resx完全相同)。

4、本地化工具欄的菜單

最后一處就是工具菜單里的本地化了,這里非常簡單,因為菜單是通過SimpleAnnotation.vsct文件來注冊的,只要增加一個帶language的<strings>節點就行了(並不需要按msdn上面那樣做)如下: 這里有錯誤,實際上還是要按CSDN的方式處理才會生效。。。

      <Button guid="guidSimpleAnnotationCmdSet" id="SimpleAnnotationCommandId" priority="0x0100" type="Button">
        <Parent guid="guidSimpleAnnotationCmdSet" id="MyMenuGroup" />
        <!--圖標-->
        <Icon guid="guidImages" id="bmpPic1" />
        <Strings>
          <CommandName>SimpleAnnotationCommandId</CommandName>
          <ButtonText>Insert Annotation</ButtonText>
        </Strings>
        <Strings language="zh-CN">
          <CommandName>SimpleAnnotationCommandId</CommandName>
          <ButtonText>插入注釋信息</ButtonText>
        </Strings>
      </Button>

最后!如果你在本地化過程中總是不成功,要注意一下下面幾處:

  • Extension.vsixlangpack文件的Include in VSIX屬性是否設置成true了
  • 所有的資源文件,生成操作是否設置成“嵌入的資源”了
  • AssemblyInfo里是否設置[assembly: NeutralResourcesLanguage("en-US"(此處是默認語言), UltimateResourceFallbackLocation.Satellite)]
  • source.extension.vsixmanifest里的locale是否設置正確了

發布時的問題

就在我將插件提交到微軟官網的時候(只有發布了才能在擴展管理器找到),又出現了一個新問題,實踐真是步履維艱!

老是報“VSIX中缺少圖標”,最后自己慢慢摸索,發現必須要將source.extension.vsixmanifest里的圖標都設置了,而且這兩個圖片必須在項目根目錄下(開始我並不是放在根目錄下),

同時圖片的Include in VSIX屬性要設置成true

發布后,有個說明內容是要求自定義的,我上傳后並沒有及時做這塊工作,第二天看時就成下面這樣子了(T_T),所以提醒大家在發布插件時,一定要及時地把說明信息提供完整。

悲劇啊,必須要發郵件與網站管理員聯系,我就用我四級433的英文水平,發了封郵件過去,慶幸得是他們應該看懂了,過了一天,給我解鎖了(這里是插件在微軟擴展庫里的地址)。

PS:由於本人VS是中文版的,希望哪位用英文版VS的園友,告訴我在英文版VS下是不是以上都正確顯示成英文了。。。

總結與源碼

這個插件的開發,遠比我相像中遇到的問題要多,不過因此收獲也多。同時深刻體會到,動手去實踐是多么的重要啊,有些東西你不實踐,只憑空相像,永遠不知道難點(或問題)在哪里。

精力有限,暫時未做批量添加注釋的功能,后期有時間再補上來吧,有興趣的童鞋也歡迎在此基礎上完成更多的功能。同時博文及源碼不足之處,歡迎園友們指出!感謝!

另:關於插件的獲得方式

  • 在VS擴展管理器里查找simple annotation即可
  • 下載源碼,編譯安裝(記得要先安裝VS SDK噢)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM