SSMS2008插件开发(4)--自定义菜单

系统 1667 0
原文: SSMS2008插件开发(4)--自定义菜单

  打开上次的项目MySSMSAddin中的Connect类,发现该类继于了两个接口: IDTExtensibility2 IDTCommandTarget ,关于这两个接口的详细说明,请点击这两个接口转到MSDN。

  IDTExtensibility2接口有2个重要的方法:OnConnection和OnDisconnection。OnConnection表示当(宿主)SSMS加载外接程序的时候调用此接口,可以在此方法中做些初始化的工作,如加载菜单等;OnDisconnection方法表示当SSMS卸载外接程序的时候调用此方法,可以在此方法中做些清理工作。

  OnConnection方法的代码如下:

    		/// <summary>

            /// 实现 IDTExtensibility2 接口的 OnConnection 方法。接收正在加载外接程序的通知。

            /// </summary>

		/// <param term='application'>宿主应用程序的根对象。</param>

		/// <param term='connectMode'>描述外接程序的加载方式。</param>

		/// <param term='addInInst'>表示此外接程序的对象。</param>

		/// <seealso class='IDTExtensibility2' />

		public void OnConnection(object application, ext_ConnectMode connectMode, object addInInst, ref Array custom)

		{

            _applicationObject = (DTE2)ServiceCache.ExtensibilityModel;

			_addInInstance = (AddIn)addInInst;

            if (connectMode == ext_ConnectMode.ext_cm_Startup)

			{

				object []contextGUIDS = new object[] { };

				Commands2 commands = (Commands2)_applicationObject.Commands;

				string toolsMenuName;

                

				try

				{

					string resourceName;

					ResourceManager resourceManager = new ResourceManager("MySSMSAddin.CommandBar", Assembly.GetExecutingAssembly());

					CultureInfo cultureInfo = new CultureInfo(_applicationObject.LocaleID);

					

					if(cultureInfo.TwoLetterISOLanguageName == "zh")

					{

						System.Globalization.CultureInfo parentCultureInfo = cultureInfo.Parent;

						resourceName = String.Concat(parentCultureInfo.Name, "Tools");

					}

					else

					{

						resourceName = String.Concat(cultureInfo.TwoLetterISOLanguageName, "Tools");

					}



					toolsMenuName = resourceManager.GetString(resourceName);

				}

				catch

				{

					toolsMenuName = "Tools";

				}



				Microsoft.VisualStudio.CommandBars.CommandBar menuBarCommandBar = ((Microsoft.VisualStudio.CommandBars.CommandBars)_applicationObject.CommandBars)["MenuBar"];



				CommandBarControl toolsControl = menuBarCommandBar.Controls[toolsMenuName];

				CommandBarPopup toolsPopup = (CommandBarPopup)toolsControl;



				try

				{

					Command command = commands.AddNamedCommand2(_addInInstance, "MySSMSAddin", "Test Menu", "Executes the command for MySSMSAddin", true, 59, ref contextGUIDS, (int)vsCommandStatus.vsCommandStatusSupported+(int)vsCommandStatus.vsCommandStatusEnabled, (int)vsCommandStyle.vsCommandStylePictAndText, vsCommandControlType.vsCommandControlTypeButton);



					if((command != null) && (toolsPopup != null))

					{

						command.AddControl(toolsPopup.CommandBar, 1);

					}

				}

				catch(System.ArgumentException)

				{

					//如果出现此异常,原因很可能是由于具有该名称的命令

					//  已存在。如果确实如此,则无需重新创建此命令,并且

                    //  可以放心忽略此异常。

				}

			}

		}
  
  该方法有4个参数:application表示宿主对象,这里指SSMS本身(在VS2008中表示DTE);connectMode表示外接程序的加载方式,在SSMS中此值总是ext_cm_Startup;addInInst表示插件本身,这里指我们的MySSMSAddin.Connect;custom 不知道什么作用(MSDN中的解释是 一个空数组,可用来传递在外接程序中使用的特定于主机的数据)。

  DTE对象是操作SSMS的核心对象,包括菜单、工具栏、文档、工具箱、错误列表等都通过该对象获取。所以,在OnConnect方法的一开始,就取得DTE对象,代码如下:

    _applicationObject = (DTE2)ServiceCache.ExtensibilityModel;

_addInInstance = (AddIn)addInInst;
  
  上面代码的第2句获取插件的实例。

  _applicationObject.Commands(DTE.Commands)表示SSMS中所有的命令项;而_applicationObject.CommandBars(DTE.CommandBars)包含所有的菜单项,例如文件菜单、工具菜单以及快捷菜单。要想在“工具”菜单中增加一个命令,必须先找到“工具”菜单,由于不同语言版本菜单的名称不一样,所以首先通过资源文件(CommandBar.resx)找到工具菜单的名称,然后通过名称在主菜单(MenuBar)中找到“工具”菜单,再在“工具”菜单中增加菜单项。

    //获取所有的菜单命令

Commands2 commands = (Commands2)_applicationObject.Commands;
  

  在资源文件中查找“工具”菜单的名称:

    try

{

    string resourceName;

    ResourceManager resourceManager = new ResourceManager("MySSMSAddin.CommandBar", Assembly.GetExecutingAssembly());

    CultureInfo cultureInfo = new CultureInfo(_applicationObject.LocaleID);



    if (cultureInfo.TwoLetterISOLanguageName == "zh")

    {

        System.Globalization.CultureInfo parentCultureInfo = cultureInfo.Parent;

        resourceName = String.Concat(parentCultureInfo.Name, "Tools");

    }

    else

    {

        resourceName = String.Concat(cultureInfo.TwoLetterISOLanguageName, "Tools");

    }



    toolsMenuName = resourceManager.GetString(resourceName);

}

catch

{

    toolsMenuName = "Tools";

}
  

  获取主菜单,并在主菜单中获取“工具”菜单的引用:

    Microsoft.VisualStudio.CommandBars.CommandBar menuBarCommandBar = ((Microsoft.VisualStudio.CommandBars.CommandBars)_applicationObject.CommandBars)["MenuBar"];

CommandBarControl toolsControl = menuBarCommandBar.Controls[toolsMenuName];

CommandBarPopup toolsPopup = (CommandBarPopup)toolsControl;
  

  通过 Commands.AddNamedCommand2 增加一个菜单命令:

    Command command = commands.AddNamedCommand2(_addInInstance

    , "MySSMSAddin"

    , "Test Menu"

    , "Executes the command for MySSMSAddin"

    , true

    , 59

    , 

    ref contextGUIDS

    , (int)vsCommandStatus.vsCommandStatusSupported + (int)vsCommandStatus.vsCommandStatusEnabled

    , (int)vsCommandStyle.vsCommandStylePictAndText, vsCommandControlType.vsCommandControlTypeButton

    );
  
  第1个参数为要添加菜单命令的插件;
  第2个参数为菜单命令的名称, 该方法会自动在给定的名称前加上Progid前缀,在这里这个前缀为命名空间.类名(MySSMSAddin.Connect)
  第3个参数为菜单显示的文本;
  第4个参数为菜单的提示信息;
  第5个参数为true表示使用Office图标,false表示使用其他来源的图标;怎么使用自定义图标,以后再说。
  第6个参数图标ID;
  第7个参数确定哪些环境上下文(即调试模式、设计模式等)启用此命令;
  第8个参数指示当指定上下文不存在时,此命令是不可见还是禁用状态等信息;
  第9个参数确定菜单的显示风格,例如是光文字还是光图标,或者两者都显示。

  将新增的菜单加入到“工具”菜单中,作为“工具”菜单的子菜单:

    command.AddControl(toolsPopup.CommandBar, 1);
  

  以上步骤仅仅在“工具”菜单中增加了一个菜单命令,但是单击该命令并没有任何响应,要响应自定义菜单,还需要实现IDTCommandTarget接口的两个方法:QueryStatus和Exec。QueryStatus方法 返回指定命名命令的当前状态(启用、禁用、隐藏等) ;Exec方法用于 执行指定的命名命令

  返回命令状态:

    /// <summary>

/// 实现 IDTCommandTarget 接口的 QueryStatus 方法。此方法在更新该命令的可用性时调用

/// </summary>

/// <param term='commandName'>要确定其状态的命令的名称。</param>

/// <param term='neededText'>该命令所需的文本。</param>

/// <param term='status'>该命令在用户界面中的状态。</param>

/// <param term='commandText'>neededText 参数所要求的文本。</param>

/// <seealso class='Exec' />

public void QueryStatus(string commandName, vsCommandStatusTextWanted neededText, ref vsCommandStatus status, ref object commandText)

{

    if (neededText == vsCommandStatusTextWanted.vsCommandStatusTextWantedNone)

    {

        if (commandName == "MySSMSAddin.Connect.MySSMSAddin")

        {

            status = (vsCommandStatus)vsCommandStatus.vsCommandStatusSupported | vsCommandStatus.vsCommandStatusEnabled;

            return;

        }

    }

}
  
  注意上述代码中的命令名称 MySSMSAddin.Connect.MySSMSAddin ,我们在指定命令名称的时候,只指定为MySSMSAddin,怎么这里的内容变多了呢?因为前面说过,使用Commands.AddNamedCommand2方法时,会在名称前面自动加上Progid。

  响应菜单事件:

    /// <summary>

/// 实现 IDTCommandTarget 接口的 Exec 方法。此方法在调用该命令时调用。

/// </summary>

/// <param term='commandName'>要执行的命令的名称。</param>

/// <param term='executeOption'>描述该命令应如何运行。</param>

/// <param term='varIn'>从调用方传递到命令处理程序的参数。</param>

/// <param term='varOut'>从命令处理程序传递到调用方的参数。</param>

/// <param term='handled'>通知调用方此命令是否已被处理。</param>

/// <seealso class='Exec' />

public void Exec(string commandName, vsCommandExecOption executeOption, ref object varIn, ref object varOut, ref bool handled)

{

    handled = false;

    if (executeOption == vsCommandExecOption.vsCommandExecOptionDoDefault)

    {

        if (commandName == "MySSMSAddin.Connect.MySSMSAddin")

        {

            System.Windows.Forms.MessageBox.Show("Hello World");

            handled = true;

            return;

        }

    }

}
  

  以上添加菜单的方法比较复杂,而且是COM时代的用法,下面的方法也许更适合C#, 可以参考这里
    /// <summary>

/// 实现 IDTExtensibility2 接口的 OnConnection 方法。接收正在加载外接程序的通知。

/// </summary>

/// <param term='application'>宿主应用程序的根对象。</param>

/// <param term='connectMode'>描述外接程序的加载方式。</param>

/// <param term='addInInst'>表示此外接程序的对象。</param>

/// <seealso class='IDTExtensibility2' />

public void OnConnection(object application, ext_ConnectMode connectMode, object addInInst, ref Array custom)

{

    _applicationObject = (DTE2)ServiceCache.ExtensibilityModel;

    _addInInstance = (AddIn)addInInst;

    if (connectMode == ext_ConnectMode.ext_cm_Startup)

    {

        object[] contextGUIDS = new object[] { };

        Commands2 commands = (Commands2)_applicationObject.Commands;

        string toolsMenuName;



        try

        {

            string resourceName;

            ResourceManager resourceManager = new ResourceManager("MySSMSAddin.CommandBar", Assembly.GetExecutingAssembly());

            CultureInfo cultureInfo = new CultureInfo(_applicationObject.LocaleID);



            if (cultureInfo.TwoLetterISOLanguageName == "zh")

            {

                System.Globalization.CultureInfo parentCultureInfo = cultureInfo.Parent;

                resourceName = String.Concat(parentCultureInfo.Name, "Tools");

            }

            else

            {

                resourceName = String.Concat(cultureInfo.TwoLetterISOLanguageName, "Tools");

            }



            toolsMenuName = resourceManager.GetString(resourceName);

        }

        catch

        {

            toolsMenuName = "Tools";

        }



        Microsoft.VisualStudio.CommandBars.CommandBar menuBarCommandBar = ((Microsoft.VisualStudio.CommandBars.CommandBars)_applicationObject.CommandBars)["MenuBar"];

        CommandBarControl toolsControl = menuBarCommandBar.Controls[toolsMenuName];

        CommandBarPopup toolsPopup = (CommandBarPopup)toolsControl;



        try

        {

            //例如 CommandBarPopup 来添加菜单

            CommandBarControl command = toolsPopup.Controls.Add(MsoControlType.msoControlButton, 1, "", 1, true);

            command.Tag = "测试";

            command.Caption = "另一种菜单";     //菜单标题

            command.TooltipText = "测试另一种添加菜单的方法";  //提示



            //获取 command 的事件,注意:commandHandler不能在方法中定义,如果这样就不能响应事件

            //  必须要定义为类级变量,不知道为什么必须这样。而且VSTO编程中也是这样。

            commandHandler = (CommandBarEvents)_applicationObject.DTE.Events.get_CommandBarEvents(command);

            commandHandler.Click += new _dispCommandBarControlEvents_ClickEventHandler(commandHandler_Click);

        }

        catch (System.ArgumentException)

        {

        }

    }

}



//添加菜单的事件对象

CommandBarEvents commandHandler;



/// <summary>

/// 菜单响应事件

/// </summary>

/// <param name="CommandBarControl"></param>

/// <param name="Handled"></param>

/// <param name="CancelDefault"></param>

void commandHandler_Click(object CommandBarControl, ref bool Handled, ref bool CancelDefault)

{

    MessageBox.Show("hello");

}
  

还有另外一种绑定菜单事件的方法,核心代码如下
    public void OnConnection(object application, ext_ConnectMode connectMode, object addInInst, ref Array custom)

{

    _applicationObject = (DTE2)ServiceCache.ExtensibilityModel;

    _addInInstance = (AddIn)addInInst;

    if (connectMode == ext_ConnectMode.ext_cm_Startup)

    {

        ...



        try

        {

            ...

        }

        catch

        {

            toolsMenuName = "Tools";

        }



        ...



        try

        {

            //利用 CommandBarPopup 来添加菜单,

            //  相对于类型为 MsoControlType.msoControlButton 的菜单,其类型为CommandBarButton

            //  利用 CommandBarButton 的Click事件,来响应命令

            command = toolsPopup.Controls.Add(MsoControlType.msoControlButton, 1, "", 1, true) as CommandBarButton;

            command.Tag = "测试";

            command.Caption = "另一种菜单";     //菜单标题

            command.TooltipText = "测试另一种添加菜单的方法";  //提示

            command.Click += new _CommandBarButtonEvents_ClickEventHandler(command_Click);

        }

        catch (System.ArgumentException)

        {

        }

    }

}

//必须定义为类级变量

CommandBarButton command;

//响应命令

void command_Click(CommandBarButton Ctrl, ref bool CancelDefault)

{

    MessageBox.Show("Hello");

}
  
  后两种增加菜单的方法都有一个共同缺点,一个是菜单本身要定义为类级,另一个是响应事件对象要定义为类级,而且都不知道怎么查询命令的可用状态。
  下一次介绍SSMS及DTE对象模型。


SSMS2008插件开发(4)--自定义菜单


更多文章、技术交流、商务合作、联系博主

微信扫码或搜索:z360901061

微信扫一扫加我为好友

QQ号联系: 360901061

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描下面二维码支持博主2元、5元、10元、20元等您想捐的金额吧,狠狠点击下面给点支持吧,站长非常感激您!手机微信长按不能支付解决办法:请将微信支付二维码保存到相册,切换到微信,然后点击微信右上角扫一扫功能,选择支付二维码完成支付。

【本文对您有帮助就好】

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描上面二维码支持博主2元、5元、10元、自定义金额等您想捐的金额吧,站长会非常 感谢您的哦!!!

发表我的评论
最新评论 总共0条评论