Click here to Skip to main content
Click here to Skip to main content

Paste as Visual Basic

By , 10 Feb 2006
Rate this:
Please Sign up or sign in to vote.

Introduction

This article describes a Paste As Visual Basic Add-in based on the original code by Scott Swigart from the MSDN article A Visual Studio Add-In That Converts C# Code To Visual Basic[^]. The original article targets Visual Studio 2005. My purpose here is to show how a VS add-in can be created manually without using the Extensibility Projects Add-in Project wizard. This will allow you to create an add-in even if you only have the basic version of VS .NET 2003 which comes with Visual Basic .NET - Standard Edition, and does not include the Extensibility Projects in its project types.

Background

Since I first got my copy of Visual Basic .NET I've been a little jealous of those who have the full blown Visual Studio, not so much for the other languages but more for the ability to create other project types. I had been wanting to delve into the possibility of creating VS add-ins and when I saw the MSDN article I decided it was time to see if it was even feasible to recreate the add-in without using the wizard. It turns out that it is indeed possible, but it does require a good deal of patience and a little better understanding of what goes into creating an add-in to get the job done.

Doing it the hard way!

There are several considerations which must be taken into account when developing an add-in with the manual approach. The primary nuisance turned out to be that I could never get Visual Studio to load my add-in in a second copy for debugging automatically, which is something that happens when you use the wizard to create an add-in. The wizard does a lot of work for you behind the scenes, wiring up VS as a host for the add-in and registering the add-in, etc. This allows you to debug before actually installing the add-in and is much more convenient.

The manual method I used required building the project, shutting down Visual Studio, creating the registry entries, then starting Visual Studio and using the add-in to see if it was working. To rebuild the project required shutting down Visual Studio, un-registering the add-in, then reloading the project in VS and continuing working.

Step by step

A Visual Studio add-in requires several things to work properly. It must be a strong named assembly and be registered for COM Interop. The important class is called Connect and is registered in the system Registry under HKEY_CLASSES_ROOT with one or more ProgId entries. Of course, it must also be a class library, not an application.

This last requirement can be handled in a couple of ways. Thomas Erdösi has created a VS macro called SetOutputTypeToClassLibrary[^] which I recommend anyone with Visual Basic .NET - Standard Edition grab a copy of and install. It will let you easily change the project type of the current project in Solution Explorer, and comes with installation instructions.

The other way is to simply create a new Windows application project and then edit the project files with a text editor. Let's do this now.

Start Visual Studio and select New -> Project from the File menu. In the New Project dialog under Project Types: select Visual Basic Projects, and under Templates: select Windows Application. For Name: type MyAddin and click the OK button. Now from the File menu select Close Solution.

Open the project folder (e.g. in Windows Explorer) and open the MyAddin.vbproj.user file in your editor (e.g. Notepad). Find the lines in the Config sections below and change as follows:

    ...
    StartAction = "None"
    StartArguments = ""
    StartPage = ""
    StartProgram = ""
    ...

Note that there are two Config sections, one for Debug and the other for Release, change the above lines in both sections, then save the file. Now, open the MyAddin.vbproj file and make the changes under the Settings section as follows:

    ...
    OutputType = "Library"
    ...
    StartupObject = ""
    ...

Now save the file. You have just changed the project to a class library! Now back in Visual Studio, under the File menu select Open Solution... and open the MyAddin.sln solution file. Under the Project menu, select MyAddin Properties..., and in the MyAddin Property Pages dialog under Common Properties -> General note that Output Type: is blank and the Startup Object: is (None). Click to Configuration Properties -> Build and checkmark Register for COM Interop, and then click the OK button.

Under the Project menu select Add Class... and in the Add New Item dialog for Name: type Connect.vb, and then click the Open button. The Connect class opens in the code editor. Add the following Imports to the beginning of the file, above the Public Class Connect line:

Imports Microsoft.Office.Core
Imports EnvDTE
Imports Extensibility
Imports System.Runtime.InteropServices
Imports System.Text

Notice the first three lines are marked as errors. We need to add references to the files containing these items. Select Solution Explorer from the File menu. Right-click the References item, select Add Reference... and in the Add Reference dialog locate and double-click on the office, envdte and extensibility entries, and then click OK. The Solution Explorer now shows the three new items and the code view should not have any errors marked.

Now let's add the following ProgIdAttribute to our Connect class on the line immediately above the Public Class Connect line:

<ProgIdAttribute("MyAddin.Connect")> _

Now add the following lines after the Public Class Connect line:

Implements IDTExtensibility2
Implements IDTCommandTarget

Dim _applicationObject As DTE
Dim _addInInstance As AddIn

Upon entering these lines Visual Studio will stub in seven methods for you automatically (finally some help!) and now our class looks like this:

Imports Microsoft.Office.Core
Imports EnvDTE
Imports Extensibility
Imports System.Runtime.InteropServices
Imports System.Text
<ProgIdAttribute("MyAddin.Connect")> _
Public Class Connect
   Implements IDTExtensibility2
   Implements IDTCommandTarget

   Dim _applicationObject As DTE
   Dim _addInInstance As AddIn

   Public Sub OnAddInsUpdate(ByRef custom As System.Array) _
     Implements Extensibility.IDTExtensibility2.OnAddInsUpdate
   End Sub

   Public Sub OnBeginShutdown(ByRef custom As System.Array) _
     Implements Extensibility.IDTExtensibility2.OnBeginShutdown
   End Sub

   Public Sub OnConnection(ByVal Application As Object, _
     ByVal ConnectMode As Extensibility.ext_ConnectMode, _
     ByVal AddInInst As Object, ByRef custom As System.Array) _
     Implements Extensibility.IDTExtensibility2.OnConnection
   End Sub

   Public Sub OnDisconnection(ByVal RemoveMode As _
     Extensibility.ext_DisconnectMode, ByRef custom As System.Array) _
     Implements Extensibility.IDTExtensibility2.OnDisconnection
   End Sub

   Public Sub OnStartupComplete(ByRef custom As System.Array) _
     Implements Extensibility.IDTExtensibility2.OnStartupComplete
   End Sub

   Public Sub Exec(ByVal CmdName As String, ByVal ExecuteOption As _
     EnvDTE.vsCommandExecOption, ByRef VariantIn As Object, _
     ByRef VariantOut As Object, ByRef handled As Boolean) _
     Implements EnvDTE.IDTCommandTarget.Exec
   End Sub

   Public Sub QueryStatus(ByVal CmdName As String, ByVal NeededText As _
     EnvDTE.vsCommandStatusTextWanted, ByRef StatusOption As _
     EnvDTE.vsCommandStatus, ByRef CommandText As Object) _
     Implements EnvDTE.IDTCommandTarget.QueryStatus
   End Sub
End Class

Actually, yours won't look exactly like that because I went through and added some line breaks to improve the formatting, but the code is the same. Now of these seven methods in our Connect class, we only need to complete three: OnConnection, Exec and QueryStatus. The others can be left as empty methods.

Let's look first at OnConnection, the method which receives notification that the add-in is being loaded. In our example, we want to add a new command to the Edit menu, so we insert the following into OnConnection:

_applicationObject = CType(Application, DTE)
_addInInstance = CType(AddInInst, AddIn)
If ConnectMode = ext_ConnectMode.ext_cm_UISetup Then
   Dim commands As Commands = CType(_applicationObject.Commands, Commands)
   Dim editMenuName As String = "Edit"
   Dim commandBars As CommandBars = _
       CType(_applicationObject.CommandBars, CommandBars)
   Dim menuBarCommandBar As CommandBar = commandBars.Item("MenuBar")
   Dim editControl As CommandBarControl = _
       menuBarCommandBar.Controls.Item(editMenuName)
   Dim editPopup As CommandBarPopup = CType(editControl, CommandBarPopup)
   Try
       Dim command As Command = commands.AddNamedCommand( _
           _addInInstance, "MyAddin", _
           "MyAddin menu text", _
           "MyAddin tooltip text", _
           True, 59, Nothing, _
           CInt(vsCommandStatus.vsCommandStatusSupported) + _
           CInt(vsCommandStatus.vsCommandStatusEnabled))
       command.AddControl(editPopup.CommandBar, 9)
   Catch
   End Try
End If

The code above will insert our new menu item in the Edit menu the first time our add-in loads. Now we add some code to the QueryStatus method, which is responsible for setting the status of our command, that is whether it should be shown, disabled etc:

If NeededText = vsCommandStatusTextWanted.vsCommandStatusTextWantedNone Then
  If CmdName = "MyAddin.Connect.MyAddin" Then
    With _applicationObject
       If Not .ActiveDocument Is Nothing _
        AndAlso .ActiveWindow.Type = vsWindowType.vsWindowTypeDocument _
        AndAlso (.MainWindow.Caption.ToLower.EndsWith(".vb") _
        OrElse .MainWindow.Caption.ToLower.EndsWith(".vb*")) Then
          StatusOption = CType(vsCommandStatus.vsCommandStatusEnabled + _
             vsCommandStatus.vsCommandStatusSupported, _
             vsCommandStatus)
       Else
          StatusOption = vsCommandStatus.vsCommandStatusSupported
       End If
    End With
  Else
    StatusOption = vsCommandStatus.vsCommandStatusUnsupported
  End If
End If

The code above will try to insure that our new menu item is only available if the window with focus in Visual Studio is a code view of a .vb file. Now on to the final method, Exec:

handled = False
If ExecuteOption = vsCommandExecOption.vsCommandExecOptionDoDefault Then
   If CmdName = "MyAddin.Connect.MyAddin" Then
      System.Windows.Forms.MessageBox.Show("Hello from MyAddin!")
      handled = True
   End If
End If

The above code simply pops up a message box for our example. At this point, you should build the solution and check for any errors. If the build is successful continue on to the next section, else fix the errors and try again!

Are we there yet?

Well you've accomplished a lot so far but you're not done yet! The next steps involve creating a key file for signing our assembly, creating a .reg file for installing the needed registry entries, creating a command script to un-register the add-in (for uninstalling) and finally testing our creation!

Open a Visual Studio .NET 2003 Command Prompt. To do this locate the shortcut on your Programs menu under Microsoft Visual Studio .NET 2003 -> Visual Studio .NET Tools -> Visual Studio .NET 2003 Command Prompt and click to open it. Examine the prompt in the command window, it should look like C:\Documents and Settings\<username> where <username> is your user name (i.e. your login name). Type cd Desktop and press Enter. Now the prompt will read C:\Documents and Settings\<username>\Desktop. Type sn -k MyAddin.snk and press Enter. You should see the following:

Microsoft (R) .NET Framework Strong Name Utility  Version 1.1.4322.573
Copyright (C) Microsoft Corporation 1998-2002. All rights reserved.

Key pair written to MyAddin.snk

Now type exit and press Enter to close the Command window. Locate the MyAddin.snk file on your desktop and copy it to the MyAddin project folder (e.g. C:\My Documents\Visual Studio Projects\MyAddin). Note: When you first created the project if you had checked the box in the New Project dialog labeled Create directory for Solution, then the folder becomes C:\My Documents\Visual Studio Projects\MyAddin\MyAddin.

In Visual Studio open the Solution Explorer and double-click the AssemblyInfo.vb item. In the code window, place the cursor on the line just under <Assembly: CLSCompliant(True)> and type the following line:

<Assembly: AssemblyKeyFile("..\..\MyAddin.snk")>

Now from the Build menu select Rebuild Solution and make sure there are no build errors. If you see an error like:

Error creating assembly manifest: Error reading key file '..\..\MyAddin.snk'
 -- The system cannot find the file specified.

it means you didn't get the MyAddin.snk file in the right folder. When the solution builds successfully you can close down Visual Studio and continue to the next step.

Open a text editor such as Notepad and paste in the following:

Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\7.1\
                                    AddIns\MyAddin.Connect]
"FriendlyName"="MyAddin"
"Description"="My first add-in"
"LoadBehavior"=dword:00000001
"CommandLineSafe"=dword:00000000
"CommandPreload"=dword:00000001

[HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\7.1\
                                         PreloadAddinState]
"MyAddin.Connect"=dword:00000001

Save this file as RecreateCommands.reg in the project folder. Create another file in the text editor and paste the following:

Windows Registry Editor Version 5.00
-[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\7.1\
                                    Addins\MyAddin.Connect]
[HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\7.1\
                                         PreloadAddinState]
"MyAddin.Connect"=-

Save this file as RemoveCommands.reg in the project folder. Now run the RecreateCommands.reg file and when prompted click Yes to allow the commands to be entered into the registry. Note: If you have any script blocking software installed on your computer you must choose Allow when prompted by it to allow the script to run. When the registry entries are loaded continue to the next step.

Drum roll, please!

Restart Visual Studio and load the MyAddin project. Select one of the code views for the active window and select the Edit menu. You will see your new item added as MyAddin menu text with a yellow smiley icon! Give yourself a big pat on the back, you deserve it!

What's next?

OK, so we just created a trivial example of a Visual Studio add-in... now let's uninstall it! Close Visual Studio and from the MyAddin project folder run the RemoveCommands.reg file. Now, if you restart Visual Studio you will find the MyAddin menu item is still there... why is this? Well it turns out that once the ProgId(s) are registered the system won't let you just remove the add-in without unregistering it first. So close Visual Studio, start up your text editor one last time and paste the following:

regasm bin\MyAddin.dll /unregister
regedit RemoveCommands.reg
pause

Save this file as Unregister.cmd in the project folder, then run it (the same advice applies here as above for script blocking software). Now when you restart Visual Studio you will be prompted to remove the add-in because it is not registered, so respond Yes:

So the development sequence will be:

  1. Change the code and build.
  2. Shut down Visual Studio.
  3. Install with RecreateCommands.reg.
  4. Restart Visual Studio and test.
  5. Need changes, shut down Visual Studio.
  6. Uninstall with Unregister.cmd.
  7. Restart Visual Studio.
  8. Go back to step 1.

Let's talk paste

By now you may be wondering where the Paste as Visual Basic command went to. Download the project source from this article and build it (Note: You must complete this step below before the project builds successfully). Shut down Visual Studio and install the PasteAsVB add-in by running the RecreateCommands.reg file in the project's Misc folder. Now when you restart Visual Studio the add-in command will be available from the Edit menu. I won't go into much detail about the code in this article, but rather refer you to the original article on MSDN[^] for details about the inner workings of the code. There is one portion of the code however that I will discuss here, the CAConvert class which uses Carlos Aguilar's converter.

Although the original code[^] compiles and runs with no errors, I could not get anything back from it but an empty return. So after examining the code behind Carlos' translator page, I came up with an alternative method which does work. The relevant part is as follows:

Public Function Convert(ByVal csCode As String) As String _
                                   Implements IConvertCode.Convert

  Dim url As String = _
      "http://www.carlosag.net/Tools/CodeTranslator/translate.ashx"
  Dim sb As New StringBuilder("Code=")
  With sb
     .Append(Encode(csCode))
     .Append("&Language=C#")
     .Append("&DestinationLanguage=VB")
     Return GetResult(url, .ToString)
  End With
End Function

Private Function GetResult(ByVal url As String, _
                      ByVal data As String) As String
  Dim xreq As New Interop.MSXML2.XMLHTTP26
  With xreq
     .open("POST", url, False)
     .setRequestHeader("Content-Type", _
            "application/x-www-form-urlencoded")
     .send(data)
     Return .responseText
  End With
End Function

Notice the Dim statement in the function GetResult. It declares a variable of type XMLHTTP26. To use this normally you would select the COM tab in the Add Reference dialog and add a reference to the Microsoft XML, v2.6 component. But because all assemblies used in an add-in project require a strong name, this won't work. So now what?

It's TlbImp to the rescue! The .NET Framework Type Library to Assembly Converter will allow us to create a strong name assembly from the DLL we wish to use:

[Editor comment: Line breaks used to avoid scrolling.]

tlbimp "C:\WINDOWS\system32\msxml2.dll" 
             /out:Interop.MSXML2.dll /keyfile:PasteAsVB.snk

Open a Visual Studio .NET 2003 Command Prompt in the project folder and type the command above to create the new DLL we need. Add a reference to this DLL by using the Browse button in the Add Reference dialog to locate and select Interop.MSXML2.dll. Now the PasteAsVB solution should build correctly.

I haven't included my created copy of this DLL because I read somewhere (on some .NET blog or forum, I don't remember) that MS doesn't allow redistribution of their core DLLs or derivatives thereof. I'm not sure if this is exactly right or not, but at any rate it will give you more insight and experience in creating what may someday be your ultimate add-in!

Final thoughts

There are a couple of considerations I haven't mentioned which you may need to be aware of. First is the fact that when you create a new project in Visual Studio the active configuration is set to Debug. If you want to change to the Release configuration (for example, if you are done making changes to the project), you will need to checkmark the Register for COM Interop checkbox again because it is unchecked by default in both Debug and Release configurations.

Also, if you want to install an add-in permanently you should consider placing the resultant DLL in a more permanent location such as the PublicAssemblies folder (e.g.. C:\Program Files\Microsoft Visual Studio .NET 2003\Common7\IDE\PublicAssemblies). From the documentation:

The PublicAssemblies folder is designed to contain managed assemblies that run within the development environment and are typically called from macros, add-ins, and other user code.

To do this, build the release configuration but leave the Register for COM Interop checkbox unchecked. Open the \bin folder under the project folder and copy the DLL from there to the PublicAssemblies folder. Now open a Visual Studio .NET 2003 Command Prompt in the PublicAssemblies folder and type the following command:

regasm MyAddin.dll /codebase

Substitute the name of your add-in's DLL for MyAddin.dll in the above command. Remember to run RecreateCommands.reg to install the add-in. RecreateCommands.reg is not specific to any path, however you should edit it to change the ProgId, Friendly Name and Description entries to match your new add-in. Remember to change the ProgIDAttribute in your add-in's Connect class as well.

Reference

History

  • 11th Feb, 2006 - Initial version.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

Stumpy842

United States United States
No Biography provided

Comments and Discussions

 
GeneralControl Outline PinmemberJordon4Kali30-Aug-07 8:00 
QuestionClass Not Registered? Pinmemberskirby5524-May-06 11:02 
AnswerRe: Class Not Registered? PinmemberStumpy84224-May-06 13:30 
GeneralRe: Class Not Registered? Pinmemberskirby5525-May-06 4:35 
GeneralRe: Class Not Registered? PinmemberStumpy84225-May-06 17:26 
Generalhmmm... Pinmembercpayne_stargames15-Feb-06 11:03 
GeneralRe: hmmm... PinmemberStumpy84215-Feb-06 11:43 
GeneralMSXML Interop PinmemberEvanBasalik14-Feb-06 7:42 
QuestionRe: MSXML Interop PinmemberStumpy84214-Feb-06 12:42 
AnswerRe: MSXML Interop Pinmembersharma.monal23-Apr-10 3:05 
QuestionVB.NET -> C# ? Pinmembertkdmaster11-Feb-06 8:30 
AnswerRe: VB.NET -> C# ? PinmemberStumpy84211-Feb-06 16:20 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140415.2 | Last Updated 11 Feb 2006
Article Copyright 2006 by Stumpy842
Everything else Copyright © CodeProject, 1999-2014
Terms of Use
Layout: fixed | fluid