前些時候,我寫過一篇《VS版權信息插件——初試VS插件開發小記》分享過一個用於添加注釋信息的插件,但那個插件有幾個問題:
- 不能添加塊注釋(/**/),只能用//來注釋(見舊文最后處的遺留問題)
- 添加的注釋,如果按Ctrl+Z只能一行一行的刪除(而非期望的整塊刪除)
- 只有一個模板,不能對多種文件進行注釋(比如模板是針對c#的,那就當然不能對xml文件注釋,因為注釋符號不同)
- 不能在發布到微軟的擴展庫里(不能通過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噢)