Paste As Visual Basic

A Visual Studio Add-In That Converts C# Code To Visual Basic

Scott Swigart

This article discusses:

  • Using code conversion Web services
  • Unit testing code
  • Creating a Visual Studio add-in
  • Adding menu items to Visual Studio
This article uses the following technologies:
Visual Studio 2005, Visual Basic .NET

Code download available at: Paste As.exe(875 KB)

Contents

Creating the Add-In
Testing Times
Adding an Add-In
Form of Conversion
Conclusion

The "Paste as..." functionality in applications like Microsoft® Word has become indispensable for me. I often copy something from the Web and want to paste it into a document without all of the HTML formatting. Paste as is the tool for the job.

While perusing the Web one day, looking for a code example, it occurred to me that Paste as would also be beneficial for Visual Studio®. However, instead of simply converting the text formatting, it could convert the language of the code example. It's common to be coding in C# and find a Visual Basic® example that does exactly what you want, or vice versa. Using such code requires copying the sample to the clipboard, going to one of the many great code conversion Web sites, pasting in the code, converting it, copying the new code, and finally pasting the converted code into Visual Studio.

When I find a C# code example on a Web site, I'd really like to eliminate the intermediate conversion steps and just Paste as Visual Basic right into the code editor. All the necessary building blocks are in place to make this work; I just need some code to glue it all together.

Creating the Add-In

There are a number of Web-based code converters available, so I wanted to architect the Paste as Visual Basic add-in so that the user can choose whichever converter he wants to use. When you're dealing with Web solutions, there's no guarantee that a particular Web site will remain operational indefinitely, or that it won't change its implementation in a way that breaks your code. Hooking up to more than one conversion service makes it more likely that there will always be a service available to perform the conversion. Also, different code converters use different algorithms, and one may work better or worse for a particular piece of code. Finally, I wanted it to be easy for the Visual Studio add-in to support additional converters in the future.

To abstract the converter implementation from the rest of the code, I crafted the IConvertCode interface. This describes the conversion operation in general, but will leave the specific implementation to other classes. The IConvertCode interface is pretty simple:

Public Interface IConvertCode
    Function Convert(ByVal csCode As String) As String
    ReadOnly Property ConverterName() As String
End Interface

The Convert method takes C# code as a string argument and returns Visual Basic code as a string. The ConverterName property returns the name of the converter so that the user can choose the converter that they would like to use.

The first implementation of this interface uses Kamal Patel's C# to Visual Basic code converter Web service, currently available at ConvertCSharp2VB. Once a reference is added to this service, the code to call it is very straightforward, as shown in the code in Figure 1.

Figure 1 Calling a Conversion Web Service

Public Class WSConvert
    Implements IConvertCode

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

        Dim codeConverter As _
            New CSToVBWebService.ConvertCSharp2VBService()
        Return codeConverter.Execute(csCode)

    End Function

    Public ReadOnly Property ConverterName() As String _
            Implements IConvertCode.ConverterName
        Get
            Return "Kamal Patel's Converter"
        End Get
    End Property
End Class

The Web service exposes an Execute method that takes C# code and returns Visual Basic code. Since the Web service just happens to use the same signature as IConvertCode.Convert, the arguments to IConvertCode.Convert can just be passed through to the Web service, and the results can be returned.

Another great tool is Carlos Aguilar Mares's AJAX-powered code converter, available at CodeTranslator. This converts between C# and Visual Basic as you type. Behind the scenes, the code is posted to carlosag.net/Tools/CodeTranslator/translate.ashx as a set of form fields. Another implementation of IConvertCode uses this facility to perform translation, as shown in Figure 2.

Figure 2 Using a Form-Based Converter

Public Class CAConvert
    Implements IConvertCode

    Public Function Convert(ByVal csCode As String) As String _
            Implements IConvertCode.Convert
    
        Dim formFields As New NameValueCollection
        formFields.Add("code", csCode)
        formFields.Add("Language", "C#")
        formFields.Add("DestinationLanguage", "VB")

        Dim client As New WebClient
        Return Encoding.ASCII.GetString( _
            client.UploadValues( _
                "http://www.carlosag.net/Tools/" & _
                "CodeTranslator/translate.ashx", _
                "POST", formFields _
            ) _
        )

    End Function

    Public ReadOnly Property ConverterName() As String _
            Implements IConvertCode.ConverterName
        Get
            Return "Carlos Aguilar's Converter"
        End Get
    End Property
End Class

This implementation posts the C# code as form fields to a Web address. The Visual Basic code is returned as the body of the HTTP response. To send the C# code, a NameValueCollection is used. The C# is added as the code item, the source language is specified as the Language item, and the Visual Basic result is specified as the DestinationLanguage item. Once the collection is populated, it is then sent to the Web address using the WebClient.UploadValues method. This method posts the form fields and gets the results back as an array of bytes. Encoding.ASCII.GetString is used to convert the array of bytes back to a string. This string contains the Visual Basic translation.

You can see that although the internal implementations are quite different, both converters satisfy the IConvertCode interface in that they provide methods that take C# code as a string and return Visual Basic code as a string.

Testing Times

I've placed the implementations of code conversion into a class library project. In the past, I probably would have created a simple Windows Form application to test my two converters to make sure they're functioning properly. However, Visual Studio 2005 Team Edition for Software Developers provides built-in unit testing capabilities. All I have to do is right-click inside of a method and I can quickly generate a test for that method.

The first time you create a unit test, a new project is added to your solution and the test code is placed in that project. The unit test for one of the conversion methods is shown in Figure 3.

Figure 3 Unit Test for Form-Based Conversion

<TestMethod()> _
Public Sub CAConvertTest()
    Dim target As IConvertCode = New CAConvert()

    Dim csCode As String = My.Computer.FileSystem.ReadAllText( _
        DataDir & "\CSCode.txt")

    Dim expected As String = My.Computer.FileSystem.ReadAllText( _
        DataDir & "\CAVBCode.txt")
    Dim actual As String

    actual = target.Convert(csCode)
    Assert.AreEqual(expected, actual, _
        "PasteAsVB.CAConvert.Convert did not return the expected value.")
End Sub

The test loads the C# source code from a text file. The code is converted to Visual Basic and compared with another file that contains the expected results. If the results match, then the test passes. The Test Manager window lets me quickly run my unit tests. If I add more converters in the future, or make changes to my existing logic, these tests should give me a quick way to make sure that everything is working properly.

Adding an Add-In

Now that I know the converters are working as expected, it's time to create an add-in for Visual Studio 2005. You create an add-in by adding a new add-in project to your solution. Add-ins are available under Extensibility projects, as shown in Figure 4.

Figure 4 Creating an Add-In Project

Figure 4** Creating an Add-In Project **

The wizard walks you through a number of questions to assist in building the skeleton for the add-in. You're prompted to specify a name, whether you want the add-in to show up in the toolbar, whether the add-in should load when Visual Studio starts, and whether it's always modeless so that it's safe to use from command-line builds.

Unlike in Visual Studio .NET 2003, the add-in project for Visual Studio 2005 contains the boilerplate code needed to correctly connect your add-in into the development environment. The add-in class implements IExtensibility2, which is the interface that Visual Studio will use to communicate with the add-in. Some key methods, like OnConnection and QueryStatus, are also written for you. The Exec method—the method that's called when the user invokes your add-in—is stubbed out, too.

When you set the add-in as the start-up project and press F5, an amazing thing happens: a new instance of Visual Studio launches with the add-in installed. You can use this new instance of Visual Studio to test the add-in. You can even set breakpoints and otherwise debug your add-in just like any other piece of code. Launching the add-in in a test instance of Visual Studio is a new and very welcome feature in Visual Studio 2005.

There are a couple changes that I want to make to the boilerplate at this point in the process. First, the add-in shows up under the Tools menu by default. I really want it to show up under the Edit menu, right after the Paste menu item. The code in Figure 5 shows the modified OnConnection method.

Figure 5 Modified OnConnection Method

Public Sub OnConnection(ByVal application As Object, _
        ByVal connectMode As ext_ConnectMode, _
        ByVal addInInst As Object, _
        ByRef custom As Array) Implements IDTExtensibility2.OnConnection

    _applicationObject = CType(application, DTE2)
    _addInInstance = CType(addInInst, AddIn)
    If connectMode = ext_ConnectMode.ext_cm_UISetup Then

        Dim commands As Commands2 = CType( _
            _applicationObject.Commands, Commands2)
        Dim editMenuName As String

        Try
            Dim resourceManager As System.Resources.ResourceManager = _
                New System.Resources.ResourceManager( _
                    "PasteAsVBAddIn.CommandBar", _
                    System.Reflection.Assembly.GetExecutingAssembly())

            Dim cultureInfo As System.Globalization.CultureInfo = _
                New System.Globalization.CultureInfo( _
                    _applicationObject.LocaleID)
            editMenuName = resourceManager.GetString( _
                String.Concat(cultureInfo.TwoLetterISOLanguageName, _ 
                    "Edit"))

        Catch e As Exception
            editMenuName = "Edit"
        End Try

        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.AddNamedCommand2( _
                _addInInstance, "PasteAsVBAddIn", _
                "Paste as Visual Basic", _
                "Convert the clipboard content from C# " & _
                    "to Visual Basic and paste", _
                True, 59, Nothing, _
                CType(vsCommandStatus.vsCommandStatusSupported,Integer)+ _
                CType(vsCommandStatus.vsCommandStatusEnabled, Integer), _
                vsCommandStyle.vsCommandStylePictAndText, _
                vsCommandControlType.vsCommandControlTypeButton)

            command.AddControl(editPopup.CommandBar, 12)
        Catch argumentException As System.ArgumentException
        End Try

    End If
End Sub

There are a few important modifications made to the boilerplate for this method. First, the project includes a resource file called CommandBar.resx, which contains the text for the top-level Visual Studio menu items in many languages. The following line of code looks up a menu item, prefixed with your two-letter language name:

editMenuName = resourceManager.
    GetString( _
    String.Concat(cultureInfo.TwoLetterISOLanguageName, "Edit"))

If you're running this on a computer set for U.S. English, it looks up a resource string named enEdit in CommandBar.resx. If the computer was configured for German, it would look up deEdit. This will return culture-specific text for the menu item, which is needed to locate that menu object programmatically. Once the top-level menu is located, you can add submenus to it. The following lines of code locate the actual menu object:

Dim editControl As CommandBarControl = _
    menuBarCommandBar.Controls.Item(editMenuName)
Dim editPopup As CommandBarPopup = CType(editControl, CommandBarPopup)

Once the menu bar is located, the new Paste as Visual Basic menu item can be added to it. This is done by creating a command and then adding the command to the menu:

Dim command As Command = commands.AddNamedCommand2( _
    _addInInstance, "PasteAsVBAddIn", "Paste as Visual Basic", _
    "Convert the clipboard content from C# to Visual Basic and paste", _
    True, 59, Nothing, _
    CType(vsCommandStatus.vsCommandStatusSupported, Integer) + _
    CType(vsCommandStatus.vsCommandStatusEnabled, Integer), _
    vsCommandStyle.vsCommandStylePictAndText, _
    vsCommandControlType.vsCommandControlTypeButton)

command.AddControl(editPopup.CommandBar, 12)

The command is added to the menu at position 12, which places it right after the Paste menu item (see Figure 6).

Figure 6 Add-In on Edit Menu

Figure 6** Add-In on Edit Menu **

It's also important that the add-in only be enabled when it could conceivably be used. You would only want to Paste as Visual Basic if you were actually editing a Visual Basic code file. Enabling and disabling the add-in is handled by the QueryStatus method. The code shown in Figure 7 will cause the add-in to display only when there's an active document that ends with the extension .vb, and there's text on the clipboard.

Figure 7 Control Whether Add-In is Enabled

Public Sub QueryStatus( _
    ByVal commandName As String, _
    ByVal neededText As vsCommandStatusTextWanted, _
    ByRef status As vsCommandStatus, _
    ByRef commandText As Object) Implements IDTCommandTarget.QueryStatus

    If neededText = _
        vsCommandStatusTextWanted.vsCommandStatusTextWantedNone Then

        If commandName = "PasteAsVBAddIn.Connect.PasteAsVBAddIn" Then
            If _applicationObject.ActiveDocument IsNot Nothing _
            AndAlso _
                LCase(_applicationObject.ActiveDocument.FullName) _
                    .EndsWith(".vb") _
            AndAlso My.Computer.Clipboard.GetText <> String.Empty Then
                status = CType(vsCommandStatus.vsCommandStatusEnabled + _
                    vsCommandStatus.vsCommandStatusSupported, _
                    vsCommandStatus)
            Else
                status = CType(vsCommandStatus.vsCommandStatusSupported, _
                    vsCommandStatus)
            End If
        Else
            status = vsCommandStatus.vsCommandStatusUnsupported
        End If
    End If
End Sub

At this point in the project, the add-in is properly wired into Visual Studio and nearly ready to be used to convert the code. The only remaining task is to connect the Exec method of the add-in to the code that will actually perform the conversion and paste the results into the editor.

Form of Conversion

In order to provide a rich experience for the user, I've created a form that allows the user to select from various conversion options and preview the conversion results. Figure 8 shows the conversion form that's displayed when the user selects the Paste as Visual Basic menu command.

Figure 8 Conversion User Interface

Figure 8** Conversion User Interface **

The C# source area shows the current contents of the clipboard. A listbox allows the user to choose the converter that they want to use. Clicking the Preview button will cause the output of the conversion to be shown at the bottom of this form, to insure that the conversion will be acceptable before pasting it into your code file. Clicking Convert will perform the conversion and paste the results directly into the open code file. If the Format Code After Convert checkbox is checked, then the code will be reformatted to insure proper indenting after the converted code is pasted in.

The code for the form is pretty simple and uses the conversion classes that were shown at the beginning of this article:

Private converters() As IConvertCode = { _
    New PasteAsVB.CAConvert(), _
    New PasteAsVB.WSConvert() _
    }

Private Sub ConverterForm_Load(ByVal sender As System.Object, _
        ByVal e As System.EventArgs) Handles MyBase.Load
    CsSourceTextBox.Text = My.Computer.Clipboard.GetText
    ConverterListBox.ValueMember = "ConverterName"
    ConverterListBox.DisplayMember = "ConverterName"
    ConverterListBox.DataSource = converters
End Sub

When the form loads, an array of converters is instantiated. These converters all implement the IConvertCode interface. In FormLoad, the listbox is bound to this array, and the ConvertName property is set to be displayed. This causes the name of each converter to appear in the listbox. If you wanted to add more converters, you would just create additional classes that implement IConvertCode, and then add them to the converters array. This could also be modified to pull the list of converters to be used from a configuration file.

When Preview is clicked, the selected converter is used to perform the conversion and display the results in the preview textbox. When Convert is clicked, the converted code is assigned to a property of the form, and the form closes. The code for the preview and convert functionality is shown in Figure 9.

Figure 9 Preview and Convert Functionality

Private Sub Preview_Click(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) Handles Preview.Click

    Dim oldCursor As Cursor = Cursor
    Cursor = Windows.Forms.Cursors.WaitCursor

    Dim convertedCode As String = GetConvertedCode(CsSourceTextBox.Text)
    VbResultsTextBox.Text = convertedCode
    Cursor = oldCursor
End Sub

Private Sub ConvertButton_Click(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) Handles ConvertButton.Click

    Dim oldCursor As Cursor = Cursor
    Cursor = Windows.Forms.Cursors.WaitCursor

    VBCode = GetConvertedCode(CsSourceTextBox.Text)
    Cursor = oldCursor
    Me.DialogResult = Windows.Forms.DialogResult.OK
End Sub

Private Function GetConvertedCode(ByVal csCode As String) As String
    Dim converter As IConvertCode = ConverterListBox.SelectedItem
    Dim convertedCode As String = converter.Convert(csCode)
    Return convertedCode
End Function

The conversion form is now complete and simply needs to be invoked from the Exec method of the add-in as shown in Figure 10. When the add-in is invoked, the conversion form is displayed. If the Convert button is clicked, the conversion is performed, and the results are stored in the VBCode property of the form. The form then returns with a result of DialogResult.OK. The add-in checks the result and then inserts the converted code into the code editor. If the format check-box is checked, the add-in also executes the Edit.FormatDocument command to reformat the current code file.

Figure 10 Performing a Conversion

Public Sub Exec(ByVal commandName As String, _
    ByVal executeOption As vsCommandExecOption, _
    ByRef varIn As Object, ByRef varOut As Object, _
    ByRef handled As Boolean) Implements IDTCommandTarget.Exec

    handled = False
    If executeOption=vsCommandExecOption.vsCommandExecOptionDoDefault Then
        If commandName = "PasteAsVBAddIn.Connect.PasteAsVBAddIn" Then
            Dim f As New ConverterForm()
            If f.ShowDialog() = System.Windows.Forms.DialogResult.OK Then
                _applicationObject.ActiveDocument _
                    .Selection.Insert(f.VBCode)
                If f.Reformat Then
                   _applicationObject.ExecuteCommand( _
                        "Edit.FormatDocument")
                End If
            End If
            handled = True
            Exit Sub
        End If
    End If
End Sub

Conclusion

Visual Studio 2005 simplifies the task of creating add-ins. First, it generates boilerplate code that will hook your add-in into the menu of Visual Studio. Second, when you press F5, a test instance of Visual Studio will be launched that contains your add-in. This makes debugging your add-in very simple.

The add-in shown here performs a very useful task—it allows you to paste C# code into your project as Visual Basic code. The add-in includes the ability to use two existing Web-based converters, but it's constructed in such a way that additional converters would be easy to add.

If you just want to install this add-in and go, the code download accompanying this article (on the MSDN Magazine Web site) includes an installer project that will let you install Paste as Visual Basic into any Visual Studio 2005 development environment. I hope that you see that creating your own add-ins doesn't need to become a daunting task. If you find yourself saying, "I wish I had an add-in that accomplished...," you just might be able to build that add-in yourself more easily than you may think.

Scott Swigart spends his time consulting, writing, and speaking about converging and emerging technologies. Scott is the author of several books on .NET, a certified Microsoft trainer (MCT) and developer (MCSD), and a Microsoft MVP. You can contact Scott at scott@swigartconsulting.com.