LearnVSXNow!-#7 創建我們第一個工具集-完成這個示例

LearnVSXNow!-#7 創建我們第一個工具集-完成這個示例

     在上一篇文章中,我們創建了一個例子:我們爲一個空的package添加了一個菜單命令,並且在這個過程中瞭解了Visual Studio Command Table文件的作用和用法。

     在這篇文章中,我們繼續這個例子,手動爲它添加一個工具窗。

爲項目添加工具窗

     我們將創建如下圖所示的工具窗:

image

     這個工具窗的功能非常簡單:在FirstArgEditSecondArgEdit文本框裏輸入數字,在OperatorCombo下拉框裏選擇運算符(+、-、*或%) ,點擊Calculate按鈕後,運算結果顯示在ResultEdit文本框中。

     爲了在StartupToolset示例中創建我們的工具窗,我們需要做下面的工作:

  1. 設計工具窗的界面
  2. 實現工具窗的功能
  3. 設置工具窗需要的資源
  4. 創建ToolWindowPane類,以便將這個工具窗嵌入到IDE中
  5. 將工具窗和package關聯起來
  6. 編寫顯示工具窗的代碼

     我們曾在第4篇中爲package添加過工具窗。正如我們在第4篇看到的那樣,爲了創建一個工具窗,我們至少需要兩個類。第一個類是一個WinForm用戶控件,它是工具窗的界面;第二個類繼承自ToolWindowPane,通過它可以把工具窗的界面嵌入到Visual Studio IDE中。

第一步:設計用戶界面

     在StartupToolset項目裏,添加一個名爲CalculationControl的用戶控件。把相應的控件從Toolbox中拖到該用戶控件上,並且按照上圖中給出的名字來命名各控件。設置ResultEdit控件的Anchor屬性爲[Top,Left,Right];設置OperationCombo控件的DropDownStyle屬性爲DropDownList,並給它的Items屬性添加“+”, “-”, “*”, “/”, “%”五個選項。

第二步:實現工具窗的功能

     實現一個工具窗的功能可以有很多種方式(設計模式)。特別是對於複雜的功能,我們可以創建一些互相協作的類來共同完成這些功能,我們也可以爲VSPackage創建服務,這樣我們的package和其他的package可以共用這些服務。 

     但是,在這篇文章裏我們採用最簡單的方式:直接在用戶控件裏添加實現功能的代碼。

     爲CalculationControl用戶控件的Load事件和CalculateButton按鈕的Click事件添加事件處理方法,如下所示:

using System;
using System.Windows.Forms;
  
namespace MyCompany.StartupToolset
{
  public partial class CalculationControl : UserControl
  {
    public CalculationControl()
    {
      InitializeComponent();
    }
  
    private void CalculationControl_Load(object sender, EventArgs e)
    {
      OperatorCombo.SelectedIndex = 0;
      FirstArgEdit.Text = "0";
    }
  
    private void CalculateButton_Click(object sender, EventArgs e)
    {
      try
      {
        int firstArg = Int32.Parse(FirstArgEdit.Text);
        int secondArg = Int32.Parse(SecondArgEdit.Text);
        int result = 0;
        switch (OperatorCombo.Text)
        {
          case "+":
            result = firstArg + secondArg;
            break;
          case "-":
            result = firstArg - secondArg;
            break;
          case "*":
            result = firstArg * secondArg;
            break;
          case "/":
            result = firstArg / secondArg;
            break;
          case "%":
            result = firstArg % secondArg;
            break;
        }
        ResultEdit.Text = result.ToString();
      }
      catch (SystemException)
      {
        ResultEdit.Text = "#Error";
      }
    }
  }
}

     我想代碼就不用解釋了吧。

第三步:設置資源

     當我們的工具窗顯示的時候,Visual Studio IDE會在這個工具窗的窗口標籤那裏顯示一個圖片。例如,當我們的工具窗和Solution Explorer顯示在一起的時候,你可以在窗口標籤那裏看到這個圖片:

image

     不能把圖片直接傳給工具窗,必須利用圖片資源:在初始化工具窗的時候,我們只能傳遞資源的標識。另外,由於這些資源標識是由VS IDE來處理的,所以這個圖片必須放在VSPackage.resx文件中。

     爲了給工具窗添加“clock”圖片,我們可以把這個圖片文件添加到VSPackage.resx文件中,並用一個數字作爲該圖片資源的ID,在這裏我們用300作爲這個圖片資源的ID。 (譯者注:如果不知道怎樣做bmp資源,可以從以前的示例SimpleToolWindow的Resources目錄下拷貝一個bmp文件過來)

     另外,我們自己的代碼(不是IDE)也有可能用到一些資源,這些資源最好放在Resource.resx文件中,因爲Visual Studio已經自動地幫我們創建了一個Resources類了,並且以靜態屬性的方式來表示放在該文件中的資源。

     在Resources.resx文件中,添加如下的字符串資源,我們在後面會用到它們:

資源名 資源值
ToolWindowTitle

Calculate Tool Windows

CanNotCreateWindow

Cannot create tool window.

第四步:創建ToolWindowPane

     負責工具窗界面的用戶控件並不知道如何嵌入到VS IDE中。嵌入到IDE中的窗口對象(工具窗是其中一種)會包含很多由IDE提供的特性:例如它們可以停靠、浮動或者固定。IDE通過Windows frame和Window pane來提供這些特性。爲了使我們的用戶控件也有這些特性,它必須嵌入到一個Window pane裏。

     所以,把用戶控件嵌入到IDE的關鍵,是去創建一個Window pane的類,這個類繼承自ToolWindowPane,ToolWindowPane實現了IVsWindowPane接口。IDE利用這個接口來爲工具窗提供上述特性。

     在StartupToolset項目裏,添加一個CalculationToolWindow.cs 文件,並且把下面的代碼複製到這個文件裏:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.Shell;
using System.Runtime.InteropServices;
using System.Windows.Forms;
 
namespace Company.StartupToolset
{
    [Guid("4B1BBBA2-9D83-45a4-8899-E7CB0296D27F")]
    public class CalculationToolWindow : ToolWindowPane
    {
        private CalculationControl control;
 
        public CalculationToolWindow()
            : base(null)
        {
            Caption = Resources.ToolWindowTitle;
            BitmapResourceID = 300;
            BitmapIndex = 0;
            control = new CalculationControl();
        }
 
        override public IWin32Window Window
        {
            get { return control; }
        }
    }
}

     工具窗類以COM類的形式被IDE調用,所以我們需要爲它指定一個GUID。在這個類的上面添加GuidAttribute,並指定一個guid。

     用戶控件CalculationControl的實例通過私有字段control來嵌入到tool window pane中。在這個類的構造函數裏,我們創建了一個CalculationControl控件的實例,並利用Window屬性來返回該控件實例的Win32句柄。

     現在讓我們看一下構造函數的代碼:

public CalculationToolWindow()
    : base(null)
{
    Caption = Resources.ToolWindowTitle;
    BitmapResourceID = 300;
    BitmapIndex = 0;
    control = new CalculationControl();
}

     在上面這個構造函數裏,我們用資源來設置工具窗的標題和圖片。Caption是一個字符串類型的屬性,所以我們可以給它一個字符串常量。但是在這裏我用了和VSPackage嚮導一樣的方式:通過在Resources.resx文件中指定的值來給Caption賦值。

     工具窗的圖片是根據BitmapResourceIDBitmapIndex這兩個屬性來決定的。第一個必須是一個整型的ID,這個ID值就是我們在VSPackage.resx文件中添加的圖片資源的ID。IDE會把這個圖片看作一個位圖條(bitmap  strip),BitmapIndex屬性則指定了工具窗的圖片在這個位圖條中的索引。

     這個構造函數沒有參數,但是基類裏的構造函數需要一個IServiceProvider類型的參數。由於我們並不需要這個參數,所以只需要傳一個null過去就行了。當然,如果我們需要在工具窗中調用service,我們可以給它傳一個IServiceProvider的實例。

     我之所以提到這個,是因爲VS 2008 SDK的文檔誤導我們說:“這個參數值不能是null(在Visual Basic裏是Nothing),否則這個工具窗將不能加到vs殼裏”。這是不正確的說法,你如果運行起來我們這個例子的話,你會看到我們的工具窗照樣可以加到IDE裏。

第五步:讓我們的package知道這個的工具窗

     我們的工具窗本身並不是一個獨立的對象,它必須和package捆綁起來:包括何時或如何顯示工具窗的邏輯,甚至可能包括一些交互邏輯和服務。

     我們可以利用ProvideToolWindowAttribute來把工具窗和package關聯起來,並且把工具窗的類型(在這裏是CalculationToolWindow)作爲參數傳遞給這個attribute:

...
[ProvideToolWindow(typeof(CalculationToolWindow))] 
public sealed class StartupToolsetPackage : Package{...}
...

     一個工具窗不僅能被所在的VSPackage調用,也能被其他的VSPackage調用。在前面的文章中(第5篇),我提到了一個按需加載package的模型。當其他的package調用這個package的工具窗的時候,該package纔會被加載(前提是這個package在這之前沒有被用到,否則早就被加載了)。這是通過和菜單命令類似的註冊機制來實現的。regpkg.exe命令根據ProvideToolWindowAttribute去註冊我們的工具窗,並且把它和對應的package關聯起來。當其他的package試圖對我們的工具窗做任何操作時,IDE就會加載我們的package(除非它已經被加載進來了)。

第六步:顯示這個工具窗

     在第四篇中我們看過顯示工具窗的代碼,在這裏我們採用類似的方式來顯示CalculationToolWindow,我們把這段代碼放在菜單命令的事件處理方法裏(這個事件處理方法我們已經在上一篇中創建了,但在當時只是用來顯示一個消息框):

...
public sealed class StartupToolsetPackage : Package
{
  ...
  private void ShowCalculateToolCallback(object sender, EventArgs e)
  {
    ToolWindowPane window = FindToolWindow(typeof(CalculationToolWindow), 0, true); 
    if ((null == window) || (null == window.Frame))
    {
      throw new NotSupportedException(Resources.CanNotCreateWindow);
    }
    IVsWindowFrame windowFrame = (IVsWindowFrame)window.Frame;
    Microsoft.VisualStudio.ErrorHandler.ThrowOnFailure(windowFrame.Show());
  }
  ...
}

     提醒一下你它是如何工作的:關鍵是FindToolWindow方法,該方法負責查找ID爲0的CalculationToolWindow的實例。如果沒有找到,就會創建一個新的;通過調用工具窗的Frame屬性的Show方法,就可以顯示這個工具窗。

     就這樣,我們的工具窗可以通過點擊相應的菜單項來顯示出來了。

我們需要的工具

     如果我問你,在你開發的時候最想要的是什麼類型的工具,我猜排在前5的一定是“日誌”。利用日誌,調試和修復程序就容易的多。所以在這篇文章剩下的部分裏,我們將爲這個示例添加簡單的日誌功能。

 

爲VSPackage添加日誌

     有很多方式可以爲程序添加日誌,例如,我們可以把文本消息發送到控制檯,或發送到Trace或Debug output、Windows事件查看器甚至Windows調試日誌。另外,Visual Studio也提供了一些其他的可選方案:

  1. Visual Studio有一個被稱爲活動日誌(activity log)的的xml文件。我們可以把日誌信息記錄在這個文件裏。對於記錄重要的信息來說,活動日誌非常重要。
  2. 另外,Visual Studio有一個輸出窗口(output window),我們也可以把信息記錄在這裏。爲了把我們的日誌信息和其他的信息區分開來,我們可以在output window中創建自己的pane(例如版本控制工具或其他package創建的pane)。

     在這篇文章中我們會在代碼中加入這樣的日誌功能:當點擊我們的工具窗的Calculate按鈕時,我們把參數、操作符和計算結果記錄到日誌中。

什麼是活動日誌(activity log)?

     在啓動Visual Studio時,添加/log開關即可以啓動Visual Studio的活動日誌模式。在這種模式下,寫在所謂的VS活動日誌裏的信息最終被保存在一個xml文件裏,我們可以查看這個xml文件的內容,以便用於測試、驗證、或解決問題。

     如果在啓動Visual Studio的時候沒有加/log開關,發送到活動日誌的信息就不會記錄在這個xml文件裏。

     每一次你通過/log開關啓動VS,上一次記錄的ActivityLog.Xml日誌文件就會被覆蓋。這個文件位於你的用戶配置(user profile)目錄的Microsoft\VisualStudio\<Hive>\UserSettings子目錄中。<Hive>取決於你運行的Visual Studio的版本(例如如果是VS 2008的話,<Hive>是9.0),如果你另外加了/rootsuffix開關的話,表明是VS的Experimental hive版本。所以,如果你是用vs 2008 sdk來開發package的話,<Hive>通常是9.0Exp。還有,一定要注意你的用戶配置文件夾(user profile folder)的路徑是由很多因素決定的(例如你的登錄用戶名、配置類型、操作系統等等)。

     例如,如果你的系統是Windows Vista,你的用戶名是jsmith,並且你有一個漫遊配置文件(roaming profile),你可以在這個目錄下找到活動日誌文件:C:\Users\jsmith\AppData\Roaming\Microsoft\VisualStudio\9.0Exp\UserSettings

     Visual Studio也會在同一個目錄下生成一個樣式表文件(ActivityLog.xsl),所以如果用IE打開活動日誌文件(ActivityLog.Xml)的話,會根據樣式表文件定義的格式來以列表的形式展現日誌。

     活動日誌文件會經常被重寫,所以——根據我的經驗——你可以在開着VS的時候查看這個文件。(譯者注:本人認爲關閉VS後再看這個文件內容也未嘗不可,因爲在VS不關閉的情況下ActivityLog.xml無法在IE下正常顯示,只能用記事本之類的文件看。原文作者的意思應該是如果你在VS做了一個操作,可以在不關閉VS的情況下立刻用記事本之類的程序查看這個文件,以便檢查這段操作記錄下來的日誌。)當你關閉了VS之後,樣式表文件纔會更新到這個目錄下。如果你在打開VS之前或開着VS的時候刪除了這個文件,那隻能等VS關了之後才能重新得到這個文件。

使用Visual Studio活動日誌(activity log)

     你可以把活動日誌當作一個表格。當你用他來記錄一條消息的時候,會在活動日誌表格裏新增一行記錄。每行記錄包括如下的列:

列名 描述
Record ID

     標識每條日誌的順序號。IVsActivityLog服務會自動創建這個ID。

Type

     表示消息的類型,是__ACTIVITYLOG_ENTRYTYPE枚舉的文本值。該枚舉有三個選項:

  • ALE_ERROR
  • ALE_WARNING
  • ALE_INFORMATION

Description

     日誌的描述,由開發人員自定義。

GUID

     和這條日誌相關的對象的GUID,是一個可選項。可以是任何值(例如一個CLSID、一個命令ID或一個package的ID等等)

Hr

     和日誌相關的HRESULT,是一個可選項。通常在爲了記錄一個COM方法的返回值時使用。

Source

     標識消息的來源。可以是package的名字,或者是開發者認爲可以用來作爲來源標識的任意字符串。

Time

     記錄某條日誌的時間,是由活動日誌來決定的,開發人員不能設置它的值。

Path

     和日誌相關的文件路徑。如果用默認的樣式表來顯示活動日誌的話,這一列的內容會合併到Description列中。

     如果你想使用活動日誌的話,必須要通過GetService方法來得到IVsActivityLog接口的實例。可以調用這個接口提供的一些方法來把消息記錄到活動日誌中。這些方法在被調用的時候,會往不同的列中寫數據。每個方法都必須指定日誌的類型,來源和日誌描述,並且會爲該日誌自動創建一個Record ID,例如LogEntry方法和LogEntryGuidHr方法,但LogEntryGuidHr方法還會爲該條日誌添加GUID和Hr,而LogEntry則不會。

     讓我們看一下在代碼裏怎樣把信息記錄到活動日誌裏。在下面的代碼段中,我們利用LogEntry方法記錄了一條簡單的信息。在這段代碼中,我們添加了一段簡單的邏輯:如果計算兩個數的運算結果失敗的話(例如除數爲0),將會記錄一條類型爲error的日誌;否則記錄一條類型爲information的日誌。在CalculationButton_Click方法中,去調用LogCalculation方法:

private void CalculateButton_Click(object sender, EventArgs e)
{
  try
  {
    int firstArg = Int32.Parse(FirstArgEdit.Text);
    int secondArg = Int32.Parse(SecondArgEdit.Text);
    int result = 0;
    switch (OperatorCombo.Text)
    {
      case "+":
        result = firstArg + secondArg;
        break;
      ... // --- Omitted for clarity
    }
    ResultEdit.Text = result.ToString();
  }
  catch (SystemException)
  {
    ResultEdit.Text = "#Error";
  }
 
  //調用LogCalculation方法來記錄日誌
  LogCalculation(FirstArgEdit.Text, SecondArgEdit.Text, OperatorCombo.Text, ResultEdit.Text);
}
 

    當然,LogCalculation方法還沒有定義,下面是該私有方法的定義:

private void LogCalculation(string firstArg, string secondArg, 
  string operation, string result)
{
  string message = String.Format("Calculation executed: {0} {1} {2} = {3}",
    firstArg, operation, secondArg, result);
  IVsActivityLog log =
    Package.GetGlobalService(typeof(SVsActivityLog)) as IVsActivityLog;
  if (log == null) return;
  
  log.LogEntry(
    (result == "#Error")
      ?(UInt32) __ACTIVITYLOG_ENTRYTYPE.ALE_ERROR
      : (UInt32)__ACTIVITYLOG_ENTRYTYPE.ALE_INFORMATION,
    "Calculation", message);
}

     可以看出,用活動日誌是非常簡單的。不過要注意,在上面的代碼中,我們用的是Package.GetGlobalService 這個靜態方法來得到service。

使用output window

     活動日誌裏的內容,是給package開發人員調試程序的時候用的。但在很多情況下,我們希望給package的最終用戶顯示一些消息。output window是用來顯示這些消息的理想的地方。

     我想我不必再介紹output window了吧,這就是output window(它通常位於VS IDE的底部):

image

     output window有很多pane(在上圖中顯示的是“生成”這個pane)。當我們向output window中寫信息的時候,我們實際上是向其中一個pane裏寫信息。我們可以用已有的pane,也可以創建自己的pane。在這個例子裏,我們用output window中已有的“General(常規)”這個pane。

     如果我問你怎樣向output window裏寫信息,你一定會回答:“使用一個服務”,沒錯,是這樣的,IVsOutputWindow服務可以幫我們向output window中寫信息。我們可以把SVsOutputWindow類型作爲參數來調用GetService方法,這樣就可以得到IVsOutputWindow接口的實例。這個接口只有3個方法:GetPaneCreatePaneDeletePane。我想這三個方法名已經告訴我們一切了。我們可以用GetPane方法的返回值(是一個IVsOutputWindowPane接口的實例)來向一個pane中寫入信息。

     現在,讓我們修改一下在CalculateButton_Click方法中調用的LogCalculation方法:

private void LogCalculation(string firstArg, string secondArg, string operation, string result)
{
    string message = String.Format("Calculation executed: {0} {1} {2} = {3} ",
      firstArg, operation, secondArg, result);
 
    IVsOutputWindow outWindow =
      Package.GetGlobalService(typeof(SVsOutputWindow)) as IVsOutputWindow;
 
    Guid generalWindowGuid = VSConstants.GUID_OutWindowGeneralPane;
    IVsOutputWindowPane windowPane;
    outWindow.GetPane(ref generalWindowGuid, out windowPane);
    windowPane.OutputString(message);
 
}

     紅色部分是關鍵代碼。爲了向output window裏的其中一個pane中寫入信息,我們必須調用GetPane方法來獲得這個pane的引用。在上面的代碼段中,我們獲得了General pane的引用。每一個pane都是由一個GUID標識的,VSConstants類的靜態字段GUID_BuildOutputWindowPane的值就是General pane的GUID。OutputString方法負責把我們的信息寫入該pane中。

    運行我們的程序,然後在我們的CalculationToolWindow工具窗中試着做幾次算術運算,相應的信息就會顯示在輸出來源爲常規(General)的pane中:

image

總結

     在這篇文章,我們完成了我們的例子:手動的添加了一個計算器的工具窗。我們的工具窗由兩個互相協作的部分組成,其中:用戶控件負責用戶界面的展現和計算結果這個簡單的“業務邏輯”;ToolWindowPane負責把該用戶控件以工具窗的形式嵌入到IDE中。然後,我們在上一篇裏已經創建好的菜單命令處理方法裏,使用相關的代碼來把這個工具窗顯示出來。

     接着,我們創建了我們這個工具集的第一個部分:爲它添加了日誌功能,可以將我們的工具窗裏執行的算式記錄下來。爲了添加日誌功能,我們使用了VS的活動日誌和VS的output window兩種方式。

     VS的活動日誌裏的內容適合給package的開發者來看(可以用來檢查、調試或修復package);VS的output window裏的日誌內容適合給package的最終用戶來看(可以用來了解package正在做什麼以及做了什麼)。

     在下一篇文章中,我們會重構這個例子,抽取一些代碼和方法,用於創建我們工具集的新的部分。

 

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

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