前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >(翻译)LearnVSXNow!-#7 创建我们第一个工具集-完成这个示例

(翻译)LearnVSXNow!-#7 创建我们第一个工具集-完成这个示例

作者头像
明年我18
发布2019-09-18 11:45:29
4930
发布2019-09-18 11:45:29
举报
文章被收录于专栏:明年我18明年我18

在上一篇文章中,我们创建了一个例子:我们为一个空的package添加了一个菜单命令,并且在这个过程中了解了Visual Studio Command Table文件的作用和用法。

在这篇文章中,我们继续这个例子,手动为它添加一个工具窗。

为项目添加工具窗

我们将创建如下图所示的工具窗:

image
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事件添加事件处理方法,如下所示:

代码语言:javascript
复制
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
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 文件,并且把下面的代码复制到这个文件里:

代码语言:javascript
复制
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句柄。

现在让我们看一下构造函数的代码:

代码语言:javascript
复制
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:

代码语言:javascript
复制
...[ProvideToolWindow(typeof(CalculationToolWindow))] public sealed class StartupToolsetPackage : Package{...}...

一个工具窗不仅能被所在的VSPackage调用,也能被其他的VSPackage调用。在前面的文章中(第5篇),我提到了一个按需加载package的模型。当其他的package调用这个package的工具窗的时候,该package才会被加载(前提是这个package在这之前没有被用到,否则早就被加载了)。这是通过和菜单命令类似的注册机制来实现的。regpkg.exe命令根据ProvideToolWindowAttribute去注册我们的工具窗,并且把它和对应的package关联起来。当其他的package试图对我们的工具窗做任何操作时,IDE就会加载我们的package(除非它已经被加载进来了)。

第六步:显示这个工具窗

在第四篇中我们看过显示工具窗的代码,在这里我们采用类似的方式来显示CalculationToolWindow,我们把这段代码放在菜单命令的事件处理方法里(这个事件处理方法我们已经在上一篇中创建了,但在当时只是用来显示一个消息框):

代码语言:javascript
复制
...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_ERRORALE_WARNINGALE_INFORMATION

Description

日志的描述,由开发人员自定义。

GUID

和这条日志相关的对象的GUID,是一个可选项。可以是任何值(例如一个CLSID、一个命令ID或一个package的ID等等)

Hr

和日志相关的HRESULT,是一个可选项。通常在为了记录一个COM方法的返回值时使用。

Source

标识消息的来源。可以是package的名字,或者是开发者认为可以用来作为来源标识的任意字符串。

Time

记录某条日志的时间,是由活动日志来决定的,开发人员不能设置它的值。

Path

和日志相关的文件路径。如果用默认的样式表来显示活动日志的话,这一列的内容会合并到Description列中。

  • 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方法:

代码语言:javascript
复制
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方法还没有定义,下面是该私有方法的定义:

代码语言:javascript
复制
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
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方法:

代码语言:javascript
复制
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
image

总结

在这篇文章,我们完成了我们的例子:手动的添加了一个计算器的工具窗。我们的工具窗由两个互相协作的部分组成,其中:用户控件负责用户界面的展现和计算结果这个简单的“业务逻辑”;ToolWindowPane负责把该用户控件以工具窗的形式嵌入到IDE中。然后,我们在上一篇里已经创建好的菜单命令处理方法里,使用相关的代码来把这个工具窗显示出来。

接着,我们创建了我们这个工具集的第一个部分:为它添加了日志功能,可以将我们的工具窗里执行的算式记录下来。为了添加日志功能,我们使用了VS的活动日志和VS的output window两种方式。

VS的活动日志里的内容适合给package的开发者来看(可以用来检查、调试或修复package);VS的output window里的日志内容适合给package的最终用户来看(可以用来了解package正在做什么以及做了什么)。

在下一篇文章中,我们会重构这个例子,抽取一些代码和方法,用于创建我们工具集的新的部分。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2010-03-13 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 为项目添加工具窗
  • 第一步:设计用户界面
  • 第二步:实现工具窗的功能
  • 第三步:设置资源
  • 第四步:创建ToolWindowPane
  • 第五步:让我们的package知道这个的工具窗
  • 第六步:显示这个工具窗
  • 我们需要的工具
  • 为VSPackage添加日志
  • 什么是活动日志(activity log)?
  • 使用Visual Studio活动日志(activity log)
  • 使用output window
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档