The Visual
Studio extensibility is showing how you create and deploy a Visual
Studio Package. Basically Visual Studio is made of packages. Each
package represents a working unit. For example, the toolbox is a
package; Solution Explorer is another package, and so on. You can extend
Visual Studio by building custom integration packages. There are
different kinds of integration packages, such as tool windows, menus,
wizards, and languages. The big difference between a package and an add-in
is that a package can add completely new tools and features to the IDE,
whereas add-ins typically extend existing features in the IDE with
other features. In the next code example you learn to build a custom
tool window that can provide the ability of compiling code snippets
on-the-fly in both Visual Basic and Visual C#. Open the New Project window by selecting File, New Project. When the New Project window appears, select the Other Project Types, Extensibility subfolder on the left and then the Visual Studio Integration Package project template. Name the new project SnippetCompilerVSPackage (see Figure 1) and then click OK.
At this point the Visual
Studio Integration Package Wizard starts. In the first step select
Visual Basic as the programming language and leave unchanged the new key
file option, as shown in Figure 2.
In the next window you can set information for the new package, such as author, description, and icon. Figure 3 shows an example for these settings.
The next step is important and is the place where you can select the package type. Select Tool Window and then click Next (see Figure 4). You can also select multiple options depending on where you want the tool to be available.
In the next step, represented in Figure 5, you can specify the Window name and Command ID. The Window name is actually the tool window title, whereas the ID is used internally by Visual Studio for invoking the new package. Figure 5 shows an example about setting the information.
The next step requires you
to specify if you want to add test projects for the new package. For
this example, uncheck both projects and proceed. At this point you can
complete the wizard and Visual Studio will generate the new project.
When the new project is ready, the first thing you notice is that,
differently from previous version, the tool window is nothing but a WPF
custom control. Now double-click the MyControl.xaml file in Solution
Explorer, in order to enable the designer. Figure 6 shows how the IDE appears at this point.
Visual Studio implements a WPF
skeleton for a new tool window that you need to customize. Before going
into that, consider the meaning of files available within Solution
Explorer. This is summarized in Table 1.
Table 1. VS Package Code Files
File | Description |
---|
Guids.vb | Defines a number of GUIDs that Visual Studio will utilize to recognize and implement the tool window |
MyToolWindow.vb | A class implementing the tool window hosting it as a user control |
PkgCmdId.vb | Exposes a unique identifier for the package within Visual Studio |
Resources.resx | Exposes resources required by the IDE |
VSPackage.resx | Exposes resources required by the package |
MyControl.Xaml | The WPF custom control actually implementing the tool window content |
SnippetCompilerVsPackagePackage.vb | The class implementing the tool window |
SnippetCompilerVsPackage.vsct | An xml file defining the layout of the package, including company information |
Source.extension.vsixmanifest | An xml file used for deploying packages as a .Vsix file (see later in this chapter) |
Key.snk | Strong name file required for signing the package assembly |
There is also a subfolder
named Resources that contains the icons used within the package and
that identifies the new tool in Visual Studio. All code files contain
comments that can help you understand what that particular file is for.
For example, take a look at the SnippetCompilerVsPackagePackage.vb file.
For your convenience, Listing 1
shows the content of this file. Notice how comments are detailed and
how they provide complete explanations on types and their role within
the user interface.
Listing 1. Understanding Packages Behind the Scenes
Imports Microsoft.VisualBasic
Imports System
Imports System.Diagnostics
Imports System.Globalization
Imports System.Runtime.InteropServices
Imports System.ComponentModel.Design
Imports Microsoft.Win32
Imports Microsoft.VisualStudio.Shell.Interop
Imports Microsoft.VisualStudio.OLE.Interop
Imports Microsoft.VisualStudio.Shell
''' <summary>
''' This is the class that implements the package exposed by this assembly.
'''
''' The minimum requirement for a class to be considered a valid package for
''' Visual Studio is to implement the IVsPackage interface and register itself with
''' the shell.
''' This package uses the helper classes defined inside the
''' Managed Package Framework (MPF)
''' to do it: it derives from the Package class that provides the implementation of
''' the IVsPackage interface and uses the registration attributes defined in the
''' ''' framework to register itself and its components with the shell.
''' </summary>
' The PackageRegistration attribute tells the PkgDef creation utility
' (CreatePkgDef.exe) that this class is a package.
'
' The InstalledProductRegistration attribute is used to register the information
needed to show this package
' in the Help/About dialog of Visual Studio.
'
' The ProvideMenuResource attribute is needed to let the shell know that this
' package exposes some menus.
' The ProvideToolWindow attribute registers a tool window exposed by this package.
<PackageRegistration(UseManagedResourcesOnly := true), _
InstalledProductRegistration("#110", "#112", "1.0", IconResourceID := 400), _
ProvideMenuResource("Menus.ctmenu", 1), _
ProvideToolWindow(GetType(MyToolWindow)), _
Guid(GuidList.guidSnippetCompilerVSPackagePkgString)> _
Public NotInheritable Class SnippetCompilerVSPackagePackage
Inherits Package
''' <summary>
''' Default constructor of the package.
''' Inside this method you can place any initialization code that does not require
''' any Visual Studio service because at this point the package object is created
''' but not sited yet inside Visual Studio environment. The place to do all the
''' other initialization is the Initialize method.
''' </summary>
Public Sub New()
Trace.WriteLine(String.Format(CultureInfo.CurrentCulture,
"Entering constructor for: {0}",
Me.GetType().Name))
End Sub
''' <summary>
''' This function is called when the user clicks the menu item that shows the
''' tool window. See the Initialize method to see how the menu item is associated to
''' this function using the OleMenuCommandService service and the MenuCommand class.
''' </summary>
Private Sub ShowToolWindow(ByVal sender As Object, ByVal e As EventArgs)
' Get the instance number 0 of this tool window. This window is single instance
' so this instance
' is actually the only one.
' The last flag is set to true so that if the tool window does not exists it
' will be created.
Dim window As ToolWindowPane = Me.FindToolWindow(GetType(MyToolWindow), 0, True)
If (window Is Nothing) Or (window.Frame Is Nothing) Then
Throw New NotSupportedException(Resources.CanNotCreateWindow)
End If
Dim windowFrame As IVsWindowFrame = TryCast(window.Frame, IVsWindowFrame)
Microsoft.VisualStudio.ErrorHandler.ThrowOnFailure(windowFrame.Show())
End Sub
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Overriden Package Implementation
#Region "Package Members"
''' <summary>
''' Initialization of the package; this method is called right
''' after the package is sited, so this is the place
''' where you can put all the initilaization code that rely on services provided by
''' VisualStudio.
''' </summary>
Protected Overrides Sub Initialize()
Trace.WriteLine(String.Format(CultureInfo.CurrentCulture,
"Entering Initialize() of: {0}",
Me.GetType().Name))
MyBase.Initialize()
' Add our command handlers for menu (commands must exist in the .vsct file)
Dim mcs As OleMenuCommandService = _
TryCast(GetService(GetType(IMenuCommandService)), OleMenuCommandService)
If Not mcs Is Nothing Then
' Create the command for the tool window
Dim toolwndCommandID As New CommandID(GuidList.
guidSnippetCompilerVSPackageCmdSet,
CInt(PkgCmdIDList.cmdidSnippetCompiler))
Dim menuToolWin As New MenuCommand(New EventHandler _
(AddressOf ShowToolWindow), toolwndCommandID)
mcs.AddCommand(menuToolWin)
End If
End Sub
#End Region
End Class
|
The class inherits from Microsoft.VisualStudio.Shell.Package, the base class exposing the required interface for every functional package. Notice how the ShowToolWindow method gets an instance of the Microsoft.VisualStudio.Shell.ToolWindowPane class pointing to the custom tool window (Me.FindToolWindow (GetType(MyToolWindow))).
The same exam can be done on the ToolWindow.vb file. After doing this,
it is possible to customize the WPF control. The goal of the tool window
is enabling on-the fly compilation for code snippets. With that said,
there is the need of implementing the user interface side, so in the
XAML editor type the code shown in Listing 2.
Listing 2. Implementing the Tool Window User Interface
<UserControl x:Class="MyControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:vsfx="clrnamespace:Microsoft.VisualStudio.Shell;assembly=Microsoft.VisualStudio.Shell.10.0"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
Name="MyToolWindow"
Background="{DynamicResource
{x:Static vsfx:VsBrushes.ToolWindowBackgroundKey}}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="30" />
<RowDefinition Height="40" />
<RowDefinition />
<RowDefinition Height="50" />
<RowDefinition Height="40" />
<RowDefinition />
</Grid.RowDefinitions>
<!— This will allow selecting the compiler —>
<ComboBox Name="LanguageCombo" Text="VisualBasic" Margin="5">
<ComboBoxItem Content="VisualBasic" />
<ComboBoxItem Content="CSharp" />
</ComboBox>
<TextBlock Margin="5" Grid.Row="1"
Foreground="{DynamicResource
{x:Static vsfx:VsBrushes.ToolWindowTextKey}}">
Write or paste your code here:</TextBlock>
<TextBox Grid.Row="2" Name="CodeTextBox" Margin="5"
Foreground="{DynamicResource
{x:Static vsfx:VsBrushes.ToolWindowTextKey}}"
AcceptsReturn="True" AcceptsTab="True"
VerticalAlignment="Stretch"
VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Auto" />
<Button Grid.Row="3" Content="Compile code" Width="80" Height="40"
Name="button1"/>
<TextBlock Grid.Row="4" Margin="10"
Foreground="{DynamicResource
{x:Static vsfx:VsBrushes.ToolWindowTextKey}}">
Compilation results:</TextBlock>
<ListBox Grid.Row="5" ItemsSource="{Binding}"
Name="ErrorsListBox" Margin="5"
Foreground="{DynamicResource
{x:Static vsfx:VsBrushes.ToolWindowTextKey}}"/>
</Grid>
</UserControl>
|
On the Visual Basic side, enter the MyControl.xaml.vb file and write the code shown in Listing 3. This adds compile functionalities to the tool window when the button is clicked. Basically the code makes use of the System.CodeDom namespace for getting instances of the .NET compilers, as you will understand through comments in the code.
Listing 3. Code for Compiling On-the-Fly the Code Typed Inside the Tool Window
Imports System.Security.Permissions
Imports System
Imports System.Reflection
Imports System.Reflection.Emit
Imports System.CodeDom.Compiler
Imports System.Windows.Controls
'''<summary>
''' Interaction logic for MyControl.xaml
'''</summary>
Partial Public Class MyControl
Inherits System.Windows.Controls.UserControl
<System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization",
"CA1300:SpecifyMessageBoxOptions")> _
Private Sub button1_Click(ByVal sender As Object,
ByVal e As System.EventArgs) Handles button1.Click
Try
If String.IsNullOrEmpty(CodeTextBox.Text) = False Then
Me.ErrorsListBox.ItemsSource = _
Compile(CType(Me.LanguageCombo.SelectedItem,
ComboBoxItem).Content.ToString)
End If
Catch ex As Exception
'Handle other exceptions here, no compiler errors
End Try
End Sub
Private Function Compile(ByVal language As String) As IEnumerable(Of String)
'Gets the ComboBox selected language
Dim languageProvider As String = language
'Creates an instance of the desired compiler
Dim CompilerProvider As CodeDomProvider = _
CodeDomProvider.CreateProvider(languageProvider)
'Sets compiler parameters
Dim params As New CompilerParameters()
Dim results As CompilerResults
'Configure self-explanatory parameters
With params
.GenerateExecutable = False
.GenerateInMemory = True
.IncludeDebugInformation = False
'You can add multiple references here
.ReferencedAssemblies.Add("System.dll")
End With
'Compiles the specified source code
results = CompilerProvider.
CompileAssemblyFromSource(params,
CodeTextBox.Text)
'If no errors, the ListBox is empty
If results.Errors.Count = 0 Then
Return Nothing
Else
'If any errors, creates a list of errors...
Dim errorsList As New List(Of String)
'..iterating the compiler errors
For Each item As CompilerError In results.Errors
errorsList.Add(item.ErrorText & " Line " & item.Line.ToString)
Next
Return errorsList.AsEnumerable
errorsList = Nothing
End If
End Function
End Class
|
At this point you can test the new tool window. This can be accomplished by simply pressing F5
as you would do in any other kind of .NET application. This starts a
new instance of Visual Studio known as Experimental Hive. It is a fully
functional instance of Visual Studio that is used for debugging custom
extensions. If the new tool window is not visible, simply click the new View, Other Windows, Snippet Compiler command. Figure 7 shows how the new tool window appears in the IDE.
The new tool window is a fully
functional one, so it can be anchored like any other Visual Studio
window. To stop the test environment, simply close the experimental
instance of Visual Studio. Until now you saw a debugging scenario. When
the debugging and testing phase is completed, you need to deploy the
extension to other developers. As explained in the next section, Visual
Studio 2010 offers a new, simple deployment system for this kind of
extensions.