(翻譯)LearnVSXNow! #12- 「VsxLibrary」 和「HowToPackage」
- 2019 年 10 月 5 日
- 筆記
前面的11篇文章涉及到了在VSX開發中最重要的知識,利用這些知識,我們已經可以開始開發VSPackage了。但是,還有很多重要的主題我們並沒有涉及到(例如Package Load Key、部署和安裝、屬性頁,自定義編輯器、項目樹,文檔窗口等等)。
通過前面這些文章的讀者回饋來看,現在是進行下一個主題的時候了。但在這之前,讓我先對比一下軟體開發和潛水運動…
不僅僅我的昵稱叫作DeepDiver,我本身也是一個海底潛水的愛好者。在匈牙利,我們只有能見度非常低的湖泊,對於潛水新手來說,在這些湖裡面潛水可不是什麼有趣的事情,但如果跟著潛水教練的話,即使是新手也會在能見度非常低的湖泊里找到樂趣。如果在能見度非常高的大海里潛水的話,即便沒有教練跟著,你也會覺得自己能夠潛的非常棒,但是一旦回到了湖裡,你就會失去剛剛建立起來的自信。擺脫這一困境的方法是多學和多練,直到你變成一個潛水教練或者潛水高手。
我現在已經是一個潛水教練了,我們經常和其他人一起練習,並學習了很多理論基礎(物理學和生理學)和實踐技能(常規任務、自救、應急操作等等)。我們有一個」夥伴系統」:當潛水的時候,夥伴之間要相互幫助。現在,不管是在匈牙利的冰冷的湖裡,還是在紅海里,我都能潛的很自如。如果你非要問我更喜歡哪一個,我會告訴你這兩個我都喜歡:喜歡紅海的壯觀,喜歡湖泊的冷靜和挑戰。
我為什麼要和你們說這個呢?因為我感覺我自己在.NET編程方面是一個專家,但在VSX開發中卻只是一個新手,但我希望自己兩個都能精通,所以我還要做很多學習和練習。我猜如果有個「夥伴系統」的話,事情會變得簡單很多,所以,如果你想的話,成為我的夥伴吧!
在這篇文章里我準備繼續我們的系列。這一次我們創建一個新的package,這個package用於放置「How To」示例,但我並不是簡單的添加示例,我還會把一些公用的程式碼抽取出來,變成可重用的託管程式碼,從而簡化VSX的開發。
創建VsxLibrary和HowToPackage項目
在第10篇中,我創建了一個叫VsxToolset的類庫項目,那個時候我想著這個東西可以作為將來開發VSX的真正工具集(甚至框架)的很好的基礎。開發工具集有下面幾個原則:
工具集里的類型必須減少噪音。我希望能夠以更簡單的方式訪問VS IDE底層的COM互操作類型和方法。我會減少程式碼行數,加強類型安全,並能夠利用託管程式碼的強大威力。例如第10篇中關於ActivityLog的處理。
COM類型轉換成.NET的類型。VS IDE的對象模型是成熟的,但它是用COM技術實現的,由於COM技術和.NET有很大不同,所以對.NET開發人員來說會很不習慣。我想把VS IDE底層的service和類型轉換成.NET的實現方式,這樣.NET的很多特性和C#(甚至3.0)都可以用了。例如第10篇文章里關於OutputWindow和OutputWindowPane的處理。
在可能的地方採用聲明式的方法。有很多地方都可以用聲明式的開發風格。.在這些地方,可以用NET提供的屬性(Attribute)、反射和元數據等技術把命令式的的程式碼轉換成聲明式的程式碼。例如第10篇文章中OutputPaneDefinition類上面就聲明了很多屬性(Attribute)。
持續不斷的文檔。我喜歡能夠使軟體開發變得簡單和有效率的框架,但是很多框架都沒有很好的文檔說明,需要花費很長的時間才能搞清楚框架怎麼用。我可不想我這個工具集也這樣,所以我打算在這個工具集的開發過程中,遵循下面的原則:
- 寫好程式碼注釋
- 為每個特性編寫示例程式碼
- 寫相關的文章來描述清楚特性的用法
我現在把VsxTools這個類庫重命名為VsxLibrary了,並且同時創建了一個名為HowToPackage的項目,目的是在這個項目里可以演示以後的文章中涉及到的VSX開發方面的內容。這兩個項目已經放到了CodePlex網站的LearnVSXNow項目上面了。
在這篇文章里,我們來做一下VsxLibrary和HowToPackage的簡單的概述。
Solution文件的結構
下載了源程式碼之後,你會看到如下的目錄結構:
目錄 | 內容 |
|---|---|
PackageStartupSamples | 第2篇到第11文章里的示例程式碼,solution文件是 PackageStartupSamples.sln。我一般情況下不會在這個下面增加新的示例,當然如果有必要的話(例如新版本的VS SDK出來了,或者原來的例子有bug),我還是會做些更新的。 |
DiveDeeper.VsxLibrary | 這個目錄是VsxLibrary(原來叫作VsxTools)的主目錄。這個solution文件下只包含這個類庫和它的單元測試項目。 |
DiveDeeper.HowToPackage | HowToPackage用於演示一些例子。這個solution文件里包含了Package項目和單元測試項目,同時也把VsxLibrary項目添加了進來。 |
創建初始程式碼
用VSPackage嚮導創建了HowToPackage項目之後,我添加了一個簡單的菜單和工具窗。我不太喜歡嚮導生成的類和常數的名字,所以我用重構工具改了一些名字。另外,我也刪了嚮導生成的大部分的注釋。
創建了VsxLibrary項目之後,我打算根據VS IDE中服務的類型來組織我的目錄。例如我把Output Window相關的程式碼放到了OutputWindow目錄下,把MessageBox相關的功能放到了VsUIShell目錄下。
VsxLibrary概覽
我說過VsxLibrary是從原來的VsxTools的基礎上創建的,在後面的文章里我會繼續向這個類庫里添加新功能,但現在我先給你展示一下這個類庫里已有的功能。
Utility類
VsxLibrary會在任何可能的地方使用聲明式的程式碼風格。使用聲明式風格的關鍵是使用attribute。所以我創建了一些attribute的抽象類型:BoolAttribute、StringAttribute、Int32Attribute和UInt32Attribute。它們只有一個Value屬性,這個屬性的類型是和這幾個attribute的名字相對應的,例如BoolAttribute的定義如下:
public abstract class BoolAttribute: Attribute{ private readonly bool _Value; protected BoolAttribute(bool value) { _Value = value; } public bool Value { get { return _Value; } }}所有其他的attribute類繼承上面這些相應的基類。如你所知,System.Attribute是不能用泛型的,所以我們不得不為每種attribute定義它的基類。
通過繼承這些基類,只有一個屬性的attribute(很多attitude都只有一個屬性)就可以用很少的程式碼行來定義了:
[AttributeUsage(AttributeTargets.Class)]public sealed class PaneNameAttribute: StringAttribute{ public PaneNameAttribute(string value) : base(value) { }} [AttributeUsage(AttributeTargets.Class)]public sealed class InitiallyVisibleAttribute: BoolAttribute{ public InitiallyVisibleAttribute(bool value): base(value) { }}隨著VsxLibrary的不斷開發,其他的attribute基類也會添加進來。
另外一個utility類是VsxConverter靜態類。在.NET基礎類庫和VS shell的互操作(interop)類之間,有很多含義一樣但實現方式不同的常數或者枚舉。例如在System.Windows.Forms命名空間下,有DialogResult這個枚舉,相應的,在VS shell的互操作類里,用1到7來分別表示這個枚舉值。再比如,Windows forms里有一個MessageBoxButtons的枚舉,在VS Shell里,相應的有OLEMSGBUTTON這個枚舉。
我認為.net開發人員比較喜歡.NET基礎類庫里的類型(枚舉、常數等等),所以我創建了VsxConverter靜態類,這個類負責在基礎類型和VS Shell類型之間做轉換。目前它已經有了幾個方法了(會越來越多的),例如:
public static OLEMSGBUTTON ConvertToOleMsgButton(MessageBoxButtons buttons){ switch (buttons) { case MessageBoxButtons.AbortRetryIgnore: return OLEMSGBUTTON.OLEMSGBUTTON_ABORTRETRYIGNORE; case MessageBoxButtons.OKCancel: return OLEMSGBUTTON.OLEMSGBUTTON_OKCANCEL; case MessageBoxButtons.RetryCancel: return OLEMSGBUTTON.OLEMSGBUTTON_RETRYCANCEL; case MessageBoxButtons.YesNo: return OLEMSGBUTTON.OLEMSGBUTTON_YESNO; case MessageBoxButtons.YesNoCancel: return OLEMSGBUTTON.OLEMSGBUTTON_YESNOCANCEL; default: return OLEMSGBUTTON.OLEMSGBUTTON_OK; }}我知道隨著VsxLibrary的開發,會有越來越多的utility類型,一旦我添加了這些類,我會在相應的文章里介紹它們。
封裝SVsUIShell服務
有很多服務提供了很多方法,例如SVsUIShell服務。我們可以在VsxLibrary類庫里去封裝它們。另外,SVsUIShell的某些方法,例如FindToolWindow和CreateToolWindow,已經在MPF里封裝了(可以通過Package類來訪問它們)。
當我們創建了一個帶有菜單的package之後(例如第3篇里講到的),它同時創建了用於顯示一個消息框的程式碼:
IVsUIShell uiShell = (IVsUIShell)GetService(typeof(SVsUIShell));Guid clsid = Guid.Empty;int result;Microsoft.VisualStudio.ErrorHandler.ThrowOnFailure( uiShell.ShowMessageBox( 0, ref clsid, "SimpleCommand", string.Format(CultureInfo.CurrentCulture, "Inside {0}.MenuItemCallback()", this.ToString()), string.Empty, 0, OLEMSGBUTTON.OLEMSGBUTTON_OK, OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST, OLEMSGICON.OLEMSGICON_INFO, 0, // false out result));這段程式碼很簡單,但也很煩雜。所以為了讓它更簡單,我封裝了VsUIShell。封裝後,如果想顯示消息框的話,只需要用下面簡單的程式碼就行了:
VsUIShell.ShowMessageBox( string.Format(CultureInfo.CurrentCulture, "Inside {0}.MenuItemCallback()", this), "SimpleCommand);我是用非常簡單的方式封裝ShowMessageBox方法的。在VsUIShell類內部,我添加了一個叫作ShowMessageBoxInternal的私有方法,這個方法接受的是.NET基礎類型,而不是VS Shell的類型:
private static DialogResult ShowMessageBoxInternal(string title, string message, string helpFile, uint helpTopic, MessageBoxButtons buttons, MessageBoxDefaultButton defButton, MessageBoxIcon icon, bool sysAlert){ Guid clsid = Guid.Empty; int result; ErrorHandler.ThrowOnFailure(UIShell.ShowMessageBox( 0, ref clsid, title, message, helpFile, helpTopic, VsxConverter.ConvertToOleMsgButton(buttons), VsxConverter.ConvertToOleMsgDefButton(defButton), VsxConverter.ConvertToOleMsgIcon(icon), sysAlert ? 1 : 0, out result)); return VsxConverter.Win32ResultToDialogResult(result);}然後我創建了一些ShowMessageBox 的重載方法,就像MessageBox.Show系列方法那樣。
總結
在這篇文章里,為了演示VSX開發,我創建了一個叫作HowToPackage的solution,並且打算在後面的文章里不斷的擴展它。我在第9篇和第10篇文章里說過,如果能把VS Shell里的類型轉換成.NET風格,並擁有CLR(例如元數據、attribute、泛型等等)和C#(例如擴展方法、LINQ等等)的特性,VSX開發就會變的簡單很多。所以,在創建HowToPackage的同時,我也創建了一個很小的框架,叫作VsxLibrary。
在下一篇文章里,我們將繼續探討VSX的開發。