locked
Adding New Menu to Visual Studio RRS feed

  • Question

  • I created a project from the wizard and followed the following article to add a new menu to visual studio.

    http://www.mztools.com/articles/2005/mz2005003.aspx

     

    however I received and error here 

    toolsCommandBar = (CommandBar)menuBarCommandBar.Controls[toolsMenuName];
    Here is some parts of the code

    private CommandBar myTemporaryToolbar;
        private CommandBarPopup myTemporaryCommandBarPopup1;
        private CommandBarPopup myTemporaryCommandBarPopup2;
        CommandBarButton myCommandBarPopup2Button = null;
    
        CommandBarControl toolsCommandBarControl = null;
        CommandBar toolsCommandBar = null;
        CommandBar menuCommandBar = null;
        Command myCommand = null;
        int position = 0;
    
    		/// <summary>Implements the constructor for the Add-in object. Place your initialization code within this method.</summary>
    		public Connect()
    		{
    		}
    
    		/// <summary>Implements the OnConnection method of the IDTExtensibility2 interface. Receives notification that the Add-in is being loaded.</summary>
    		/// <param term='application'>Root object of the host application.</param>
    		/// <param term='connectMode'>Describes how the Add-in is being loaded.</param>
    		/// <param term='addInInst'>Object representing this Add-in.</param>
    		/// <seealso class='IDTExtensibility2' />
    		public void OnConnection(object application, ext_ConnectMode connectMode, object addInInst, ref Array custom)
    		{
    			_applicationObject = (DTE2)application;
    			_addInInstance = (AddIn)addInInst;
    
          object[] contextGUIDS = new object[] { };
          Commands2 commands = (Commands2)_applicationObject.Commands;
          string toolsMenuName = "Tools";
    
          Microsoft.VisualStudio.CommandBars.CommandBar menuBarCommandBar = ((Microsoft.VisualStudio.CommandBars.CommandBars)_applicationObject.CommandBars)["MenuBar"];
          
          toolsCommandBar = (CommandBar)menuBarCommandBar.Controls[toolsMenuName];
          ////////////////////////////////////////////////////////////////////
          
          position = toolsCommandBarControl.Index + 1;
          
          myTemporaryCommandBarPopup2 = (CommandBarPopup)toolsCommandBar.Controls.Add(
          MsoControlType.msoControlPopup, System.Type.Missing, System.Type.Missing, position, true);
    
          myTemporaryCommandBarPopup2.CommandBar.Name = "MyMenuName";
          myTemporaryCommandBarPopup2.Caption = "Command One";
    
          myCommandBarPopup2Button = (CommandBarButton)myCommand.AddControl(
          myTemporaryCommandBarPopup2.CommandBar, myTemporaryCommandBarPopup2.Controls.Count + 1);
    
          myCommandBarPopup2Button.Caption = "Command Two";
    
          myTemporaryCommandBarPopup2.Visible = true;
    		}
    
    		/// <summary>Implements the OnDisconnection method of the IDTExtensibility2 interface. Receives notification that the Add-in is being unloaded.</summary>
    		/// <param term='disconnectMode'>Describes how the Add-in is being unloaded.</param>
    		/// <param term='custom'>Array of parameters that are host application specific.</param>
    		/// <seealso class='IDTExtensibility2' />
    		public void OnDisconnection(ext_DisconnectMode disconnectMode, ref Array custom)
    		{
          try
          {
            if ((myTemporaryToolbar != null))
            {
              myTemporaryToolbar.Delete();
            }
    
            if ((myTemporaryCommandBarPopup1 != null))
            {
              myTemporaryCommandBarPopup1.Delete(true);
            }
    
            if ((myTemporaryCommandBarPopup2 != null))
            {
              myTemporaryCommandBarPopup2.Delete(true);
            }
          }
          catch (Exception ex)
          {
            System.Windows.Forms.MessageBox.Show(ex.Message);
          }
    		}

    Monday, February 21, 2011 3:44 PM

Answers

  • Hello Jean,

    This is quite messy because a number of factors:

    - First of all, although not related to the problem, you are using

    if (connectMode == ext_ConnectMode.ext_cm_UISetup)

    to create a "temporary UI", which is not correct as explained in the article

    http://www.mztools.com/articles/2005/mz2005003.aspx

    - In one of your posts you wrote this code:

    toolsCommandBar = menuBarCommandBar;   // This line doesn't make sense
    toolsCommandBar = (CommandBar)menuBarCommandBar.Controls[toolsMenuName];
    ////////////////////////////////////////////////////////////////////
    toolsCommandBarControl = (CommandBarPopup)toolsCommandBar.Parent;

    - Now that you have posted the "whole code":

    1) The try/catch block of the OnConnection method only catches System.ArgumentException. It doesn't catch "Unable to cast object of type '_Marshaler' to type 'Microsoft.VisualStudio.CommandBars.CommandBar'." exception that you mentioned. Without the correct try/catch block, exceptions will crash the add-in.

    2) The menuCommandBar variable of the line:

     toolsCommandBarPopup = (CommandBarPopup)menuCommandBar.Controls[toolsMenuName]
    is not initialized and will cause a NullReferenceException (not catched). The menuBarCommandBar variable is the one being initialized.

    3)The toolsCommandBarControl variable of this line hasn't been initialized and will be null:

    position = toolsCommandBarControl.Index + 1;




    Anyway, once you fix the code, if it ever ends calling something like:

    (CommandBarControl)  commandBarPopup.CommandBar.Parent

    it will get an InvalidCastException that I blogged about back in October 2009 during the VS 2010 beta:

    VS 2010 and the Microsoft.VisualStudio.CommandBars.CommandBarPopup interface
    http://msmvps.com/blogs/carlosq/archive/2009/10/23/vs-2010-and-the-commandbarpopup-interface.aspx

    and I reported to Microsoft here but was not fixed for RTM:

    VSIP: VS 2010 Beta2: InvalidCastException casting DirectCast(CommandBarControl, CommandBarPopup).CommandBar.Parent back to CommandBarControl
    https://connect.microsoft.com/VisualStudio/feedback/details/499483/vsip-vs-2010-beta2-invalidcastexception-casting-directcast-commandbarcontrol-commandbarpopup-commandbar-parent-back-to-commandbarcontrol#details

    Whether SP1 fixes this or not is somewhat irrelevant if your add-in is not for internal use, because it will fail on VS 2010 without the SP1. Fortunately, there is a workaround to get the Index of a CommandBarControl which is a CommandBarPopup inside a parent CommandBar, and it solves too the problem of:

    toolsCommandBarPopup = (CommandBarPopup)menuCommandBar.Controls[toolsMenuName];

    in non English versions of VS 2005 / 2008. The workaround is a variant of the code of this article:

    HOWTO: Locate commandbars in international versions of Visual Studio

    http://www.mztools.com/articles/2007/MZ2007002.aspx

    and it would be something like this (VB.NET), that takes a parent CommandBar and the name of a child popup and it returns its CommandBarControl, from which you get the Index property that you are looking for:

          Friend Shared Function GetCommandBarControlByCommandBarPopupName(ByVal objCommandBar As CommandBar, ByVal sPopupControlCommandBarName As String) As CommandBarControl

             Dim objResultCommandBarControl As CommandBarControl
             Dim objCommandBarControl As CommandBarControl
             Dim objCommandBarPopup As CommandBarPopup

             For Each objCommandBarControl In objCommandBar.Controls

                If objCommandBarControl.Type = MsoControlType.msoControlPopup Then

                   objCommandBarPopup = DirectCast(objCommandBarControl, CommandBarPopup)

                   If objCommandBarPopup.CommandBar.Name = sPopupControlCommandBarName Then

                      objResultCommandBarControl = objCommandBarControl
                      Exit For

                   End If

                End If

             Next

             Return objResultCommandBarControl

          End Function

    Update: I have remembered that I already had an article about this:

    HOWTO: Locate the index of a CommandBarControl on a Commandbar to add a control or menu before or after it from a Visual Studio add-in

    http://www.mztools.com/articles/2010/MZ2010001.aspx

    HTH


    MZ-Tools: Productivity add-ins for Visual Studio: http://www.mztools.com. My blog about developing add-ins: http://msmvps.com/blogs/carlosq/
    Tuesday, February 22, 2011 11:21 PM

All replies

  • What is the error you received?

    Ryan

    Monday, February 21, 2011 4:13 PM
  • I crashed right here

     

    toolsCommandBar = (CommandBar)menuBarCommandBar.Controls[toolsMenuName];

    I am not in the PC now, but it looks like it is an object instantiation error. I couldn't find a way to get rid off it

    Monday, February 21, 2011 6:43 PM
  • The type of the object assocaited with the tools menu is not a CommandBar, it is a CommandBarPopup, you can get the associated CommandBar object via the CommandBar property on the CommandBarPopup object.

    Ryan

    Monday, February 21, 2011 8:07 PM
  • I will try your suggestion and you know if the solution works
    Monday, February 21, 2011 11:17 PM
  • I still receive an error when I try the following



    toolsCommandBar = menuBarCommandBar;
                        toolsCommandBar = (CommandBar)menuBarCommandBar.Controls[toolsMenuName];
                        ////////////////////////////////////////////////////////////////////
                        toolsCommandBarControl = (CommandBarPopup)toolsCommandBar.Parent;



    I get this error



    Unable to cast object of type '_Marshaler' to type 'Microsoft.VisualStudio.CommandBars.CommandBar'.



    here



    toolsCommandBar = (CommandBar)menuBarCommandBar.Controls[toolsMenuName];
    Tuesday, February 22, 2011 3:54 AM
  • Yes, I believe this was a known bug.  Have you installed the SP1 beta to see if the fix is in there (I can't recall if it is or isn't).

    Ryan

    Tuesday, February 22, 2011 4:00 AM
  • Hello (I am the author of the article)

     

    There is no bug, the correct code is not the following that you wrote:


                toolsCommandBar = menuBarCommandBar;
                toolsCommandBar = (CommandBar)menuBarCommandBar.Controls[toolsMenuName];
                ////////////////////////////////////////////////////////////////////
                toolsCommandBarControl = (CommandBarPopup)toolsCommandBar.Parent;

    but:

                CommandBar toolsCommandBar = null;
                CommandBarPopup toolsCommandBarPopup;

                toolsCommandBarPopup = (CommandBarPopup)menuCommandBar.Controls[toolsMenuName];
                toolsCommandBar = toolsCommandBarPopup.Parent;

    It works on VS 2010. Update: it only works correctly on VS 2010 (English or other languages), but not on non-English versions of VS 2008/2005, because Controls are localized and somehow the logic in VS 2010 was changed to make it work. In fact the correct way to make it work on VS 2005/2008/2010 is this other article of mine:

    HOWTO: Locate commandbars in international versions of Visual Studio

    http://www.mztools.com/articles/2007/MZ2007002.aspx

     


    MZ-Tools: Productivity add-ins for Visual Studio: http://www.mztools.com. My blog about developing add-ins: http://msmvps.com/blogs/carlosq/
    Tuesday, February 22, 2011 7:25 AM
  • It still crashed; this is the whole code

     

    using System;
    using Extensibility;
    using EnvDTE;
    using EnvDTE80;
    using Microsoft.VisualStudio.CommandBars;
    using System.Resources;
    using System.Reflection;
    using System.Globalization;
    
    namespace MyAddin2
    {
    	/// <summary>The object for implementing an Add-in.</summary>
    	/// <seealso class='IDTExtensibility2' />
    	public class Connect : IDTExtensibility2, IDTCommandTarget
    	{
        private CommandBar myTemporaryToolbar;
        private CommandBarPopup myTemporaryCommandBarPopup1;
        private CommandBarPopup myTemporaryCommandBarPopup2;
        CommandBarButton myCommandBarPopup2Button = null;
        CommandBarPopup toolsCommandBarPopup;
    
        CommandBarControl toolsCommandBarControl = null;
        CommandBar toolsCommandBar = null;
        CommandBar menuCommandBar = null;
        Command myCommand = null;
        int position = 0;
    
    		/// <summary>Implements the constructor for the Add-in object. Place your initialization code within this method.</summary>
    		public Connect()
    		{
    		}
    
    		/// <summary>Implements the OnConnection method of the IDTExtensibility2 interface. Receives notification that the Add-in is being loaded.</summary>
    		/// <param term='application'>Root object of the host application.</param>
    		/// <param term='connectMode'>Describes how the Add-in is being loaded.</param>
    		/// <param term='addInInst'>Object representing this Add-in.</param>
    		/// <seealso class='IDTExtensibility2' />
    		public void OnConnection(object application, ext_ConnectMode connectMode, object addInInst, ref Array custom)
    		{
    			_applicationObject = (DTE2)application;
    			_addInInstance = (AddIn)addInInst;
    			if(connectMode == ext_ConnectMode.ext_cm_UISetup)
    			{
    				object []contextGUIDS = new object[] { };
    				Commands2 commands = (Commands2)_applicationObject.Commands;
    				string toolsMenuName = "Tools";
    
    				//Place the command on the tools menu.
    				//Find the MenuBar command bar, which is the top-level command bar holding all the main menu items:
    				Microsoft.VisualStudio.CommandBars.CommandBar menuBarCommandBar = ((Microsoft.VisualStudio.CommandBars.CommandBars)_applicationObject.CommandBars)["MenuBar"];
    
    				//Find the Tools command bar on the MenuBar command bar:
    				CommandBarControl toolsControl = menuBarCommandBar.Controls[toolsMenuName];
    				CommandBarPopup toolsPopup = (CommandBarPopup)toolsControl;
    
    				//This try/catch block can be duplicated if you wish to add multiple commands to be handled by your Add-in,
    				// just make sure you also update the QueryStatus/Exec method to include the new command names.
    				try
    				{
    					//Add a command to the Commands collection:
    					Command command = commands.AddNamedCommand2(_addInInstance, "MyAddin2", "MyAddin2", "Executes the command for MyAddin2", true, 59, ref contextGUIDS, (int)vsCommandStatus.vsCommandStatusSupported+(int)vsCommandStatus.vsCommandStatusEnabled, (int)vsCommandStyle.vsCommandStylePictAndText, vsCommandControlType.vsCommandControlTypeButton);
    
    					//Add a control for the command to the tools menu:
    					if((command != null) && (toolsPopup != null))
    					{
    						command.AddControl(toolsPopup.CommandBar, 1);
    					}
              
              toolsCommandBarPopup = (CommandBarPopup)menuCommandBar.Controls[toolsMenuName];
              toolsCommandBar = toolsCommandBarPopup.Parent;
              position = toolsCommandBarControl.Index + 1;
    
              myTemporaryCommandBarPopup2 = (CommandBarPopup)toolsCommandBar.Controls.Add(
              MsoControlType.msoControlPopup, System.Type.Missing, System.Type.Missing, position, true);
    
              myTemporaryCommandBarPopup2.CommandBar.Name = "MyMenuName";
              myTemporaryCommandBarPopup2.Caption = "Command ONn";
    
              myCommandBarPopup2Button = (CommandBarButton)myCommand.AddControl(
              myTemporaryCommandBarPopup2.CommandBar, myTemporaryCommandBarPopup2.Controls.Count + 1);
    
              myCommandBarPopup2Button.Caption = "Command Two";
    
              myTemporaryCommandBarPopup2.Visible = true;
    				}
    				catch(System.ArgumentException)
    				{
    					//If we are here, then the exception is probably because a command with that name
    					// already exists. If so there is no need to recreate the command and we can 
              // safely ignore the exception.
    				}
    			}
    		}
    
    		/// <summary>Implements the OnDisconnection method of the IDTExtensibility2 interface. Receives notification that the Add-in is being unloaded.</summary>
    		/// <param term='disconnectMode'>Describes how the Add-in is being unloaded.</param>
    		/// <param term='custom'>Array of parameters that are host application specific.</param>
    		/// <seealso class='IDTExtensibility2' />
    		public void OnDisconnection(ext_DisconnectMode disconnectMode, ref Array custom)
    		{
          try
          {
            if ((myTemporaryToolbar != null))
            {
              myTemporaryToolbar.Delete();
            }
    
            if ((myTemporaryCommandBarPopup1 != null))
            {
              myTemporaryCommandBarPopup1.Delete(true);
            }
    
            if ((myTemporaryCommandBarPopup2 != null))
            {
              myTemporaryCommandBarPopup2.Delete(true);
            }
          }
          catch (Exception ex)
          {
            System.Windows.Forms.MessageBox.Show(ex.Message);
          }
    		}
    
    		/// <summary>Implements the OnAddInsUpdate method of the IDTExtensibility2 interface. Receives notification when the collection of Add-ins has changed.</summary>
    		/// <param term='custom'>Array of parameters that are host application specific.</param>
    		/// <seealso class='IDTExtensibility2' />		
    		public void OnAddInsUpdate(ref Array custom)
    		{
    		}
    
    		/// <summary>Implements the OnStartupComplete method of the IDTExtensibility2 interface. Receives notification that the host application has completed loading.</summary>
    		/// <param term='custom'>Array of parameters that are host application specific.</param>
    		/// <seealso class='IDTExtensibility2' />
    		public void OnStartupComplete(ref Array custom)
    		{
    		}
    
    		/// <summary>Implements the OnBeginShutdown method of the IDTExtensibility2 interface. Receives notification that the host application is being unloaded.</summary>
    		/// <param term='custom'>Array of parameters that are host application specific.</param>
    		/// <seealso class='IDTExtensibility2' />
    		public void OnBeginShutdown(ref Array custom)
    		{
    		}
    		
    		/// <summary>Implements the QueryStatus method of the IDTCommandTarget interface. This is called when the command's availability is updated</summary>
    		/// <param term='commandName'>The name of the command to determine state for.</param>
    		/// <param term='neededText'>Text that is needed for the command.</param>
    		/// <param term='status'>The state of the command in the user interface.</param>
    		/// <param term='commandText'>Text requested by the neededText parameter.</param>
    		/// <seealso class='Exec' />
    		public void QueryStatus(string commandName, vsCommandStatusTextWanted neededText, ref vsCommandStatus status, ref object commandText)
    		{
    			if(neededText == vsCommandStatusTextWanted.vsCommandStatusTextWantedNone)
    			{
    				if(commandName == "MyAddin2.Connect.MyAddin2")
    				{
    					status = (vsCommandStatus)vsCommandStatus.vsCommandStatusSupported|vsCommandStatus.vsCommandStatusEnabled;
    					return;
    				}
    			}
    		}
    
    		/// <summary>Implements the Exec method of the IDTCommandTarget interface. This is called when the command is invoked.</summary>
    		/// <param term='commandName'>The name of the command to execute.</param>
    		/// <param term='executeOption'>Describes how the command should be run.</param>
    		/// <param term='varIn'>Parameters passed from the caller to the command handler.</param>
    		/// <param term='varOut'>Parameters passed from the command handler to the caller.</param>
    		/// <param term='handled'>Informs the caller if the command was handled or not.</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 == "MyAddin2.Connect.MyAddin2")
    				{
    					handled = true;
    					return;
    				}
    			}
    		}
    		private DTE2 _applicationObject;
    		private AddIn _addInInstance;
    	}
    }
    

    Tuesday, February 22, 2011 2:50 PM
  • Did you see my post saying this is a known bug?  Have you tried installing SP1 Beta?


    Ryan

    Tuesday, February 22, 2011 4:18 PM
  • I am using vs2010 and I haven't installed the spk 1 beta yet
    Tuesday, February 22, 2011 7:15 PM
  • Yes, I am suggesting you try to install the Beta, the 'can't cast _Marshaller...' is a known/fixed bug, and I believe (though I am not 100% certain) that it is fixed in SP1.

    Ryan

    Tuesday, February 22, 2011 7:33 PM
  • Hello Jean,

    This is quite messy because a number of factors:

    - First of all, although not related to the problem, you are using

    if (connectMode == ext_ConnectMode.ext_cm_UISetup)

    to create a "temporary UI", which is not correct as explained in the article

    http://www.mztools.com/articles/2005/mz2005003.aspx

    - In one of your posts you wrote this code:

    toolsCommandBar = menuBarCommandBar;   // This line doesn't make sense
    toolsCommandBar = (CommandBar)menuBarCommandBar.Controls[toolsMenuName];
    ////////////////////////////////////////////////////////////////////
    toolsCommandBarControl = (CommandBarPopup)toolsCommandBar.Parent;

    - Now that you have posted the "whole code":

    1) The try/catch block of the OnConnection method only catches System.ArgumentException. It doesn't catch "Unable to cast object of type '_Marshaler' to type 'Microsoft.VisualStudio.CommandBars.CommandBar'." exception that you mentioned. Without the correct try/catch block, exceptions will crash the add-in.

    2) The menuCommandBar variable of the line:

     toolsCommandBarPopup = (CommandBarPopup)menuCommandBar.Controls[toolsMenuName]
    is not initialized and will cause a NullReferenceException (not catched). The menuBarCommandBar variable is the one being initialized.

    3)The toolsCommandBarControl variable of this line hasn't been initialized and will be null:

    position = toolsCommandBarControl.Index + 1;




    Anyway, once you fix the code, if it ever ends calling something like:

    (CommandBarControl)  commandBarPopup.CommandBar.Parent

    it will get an InvalidCastException that I blogged about back in October 2009 during the VS 2010 beta:

    VS 2010 and the Microsoft.VisualStudio.CommandBars.CommandBarPopup interface
    http://msmvps.com/blogs/carlosq/archive/2009/10/23/vs-2010-and-the-commandbarpopup-interface.aspx

    and I reported to Microsoft here but was not fixed for RTM:

    VSIP: VS 2010 Beta2: InvalidCastException casting DirectCast(CommandBarControl, CommandBarPopup).CommandBar.Parent back to CommandBarControl
    https://connect.microsoft.com/VisualStudio/feedback/details/499483/vsip-vs-2010-beta2-invalidcastexception-casting-directcast-commandbarcontrol-commandbarpopup-commandbar-parent-back-to-commandbarcontrol#details

    Whether SP1 fixes this or not is somewhat irrelevant if your add-in is not for internal use, because it will fail on VS 2010 without the SP1. Fortunately, there is a workaround to get the Index of a CommandBarControl which is a CommandBarPopup inside a parent CommandBar, and it solves too the problem of:

    toolsCommandBarPopup = (CommandBarPopup)menuCommandBar.Controls[toolsMenuName];

    in non English versions of VS 2005 / 2008. The workaround is a variant of the code of this article:

    HOWTO: Locate commandbars in international versions of Visual Studio

    http://www.mztools.com/articles/2007/MZ2007002.aspx

    and it would be something like this (VB.NET), that takes a parent CommandBar and the name of a child popup and it returns its CommandBarControl, from which you get the Index property that you are looking for:

          Friend Shared Function GetCommandBarControlByCommandBarPopupName(ByVal objCommandBar As CommandBar, ByVal sPopupControlCommandBarName As String) As CommandBarControl

             Dim objResultCommandBarControl As CommandBarControl
             Dim objCommandBarControl As CommandBarControl
             Dim objCommandBarPopup As CommandBarPopup

             For Each objCommandBarControl In objCommandBar.Controls

                If objCommandBarControl.Type = MsoControlType.msoControlPopup Then

                   objCommandBarPopup = DirectCast(objCommandBarControl, CommandBarPopup)

                   If objCommandBarPopup.CommandBar.Name = sPopupControlCommandBarName Then

                      objResultCommandBarControl = objCommandBarControl
                      Exit For

                   End If

                End If

             Next

             Return objResultCommandBarControl

          End Function

    Update: I have remembered that I already had an article about this:

    HOWTO: Locate the index of a CommandBarControl on a Commandbar to add a control or menu before or after it from a Visual Studio add-in

    http://www.mztools.com/articles/2010/MZ2010001.aspx

    HTH


    MZ-Tools: Productivity add-ins for Visual Studio: http://www.mztools.com. My blog about developing add-ins: http://msmvps.com/blogs/carlosq/
    Tuesday, February 22, 2011 11:21 PM
  • I will try your suggestion and let you know if the code is working
    Tuesday, February 22, 2011 11:30 PM