VSPackage Builder Readme
VSPackage Builder Readme
The VSPackage Builder is a project template that has been developed to make it easier to get started building rich Visual Studio 2010 VSPackage-based extensions. VSPackage Builder projects incorporate a vspackage design surface that allows command UI and toolwindow instances to be composed graphically using a familiar VS designer model where the toolbox contains the available instance types and the property browser is used to set instance properties.
The toolbox also contains QuickStart elements that perform common tasks like adding a tool window. This interface allows you to experiment with the template and get to F5 quickly. The VSPackage Builder generates most of the boilerplate command handling code. You can also override event handlers and create user control content for tool windows. This approach facilitates experimentation and rapid prototyping, and enables greater focus on the business logic aspects of the extension.
Prerequisites
VSPackage Builder has been developed for Visual Studio 2010 and requires that the Visual Studio 2010 SDK be installed. For more information about Visual Studio extensibility or the VS SDK, please refer to the VSX Developer Center at http://msdn.com/vsx/.
Page 1 of 19
Visual Studio commands are composed from instances of subclasses of four main UI element types: menus are containers for groups groups are containers for buttons, combos, menus, and groups buttons represent menu items and toolbar buttons that are used to tell Visual Studio to execute a command combos represent dropdowns with commands
All command UI elements can be placed in multiple locations. For example, a single button or combo may be placed in several different groups, and a given group may be placed in different menus. The position of an element on a menu is determined by its priority relative to other elements. All elements have a unique identifier - a GUID/integer ID pair. The command UI for a VSPackage is usually described in the VS Command Table (VSCT) file that is compiled into a resource included in the VSPackage binary. The VSCT XML Schema Reference describes the structure of a VSCT file, which contains XML elements that correspond closely with command UI element types. Command handling requires that you implement the IOleCommandTarget interface and provide QueryStatus and Exec handlers for the commands you want to include. Manually managing and mapping the GUID/IDs declared in VSCT files to IDs declared in code files is onerous and creates the potential for errors. The VSPackage Builder generates the required VSCT file and the stub command handlers from the command UI instances and connections on the design surface. There is a close correlation between the command UI elements supported by the design surface and the VSCT XML elements. By exploring the vspackage design surface, you can gain greater expertise and understanding of the capabilities of Visual Studio commands in general. Page 2 of 19
2. Lets start by adding a command that well later use to launch our tool window. Open the toolbox if it is not showing. The toolbox allows you to add commands, tool windows, toolbars, and menus. You can use the Quick Starts to add all the different classes you'll need to add a command, menu, toolbar, or tool window. Select Add Command from the Quick Starts category and drag it onto the design surface. You'll see two shapes connected by a connector. The yellow shape represents the group on the menu where the button is placed, and the green shape represents the button.
Connections The shapes on the design surface are connected using the Connect toolbox element. In most cases you won't be able to draw illegal connections, but take care when connecting menus and groups. Because groups can contain menus and menus can contain groups, be sure to start drawing the Connect line on the contained element and finish on the container element. The arrow on the connection should always point to the container. 3. Select the Tools Menu shape on the design surface, and find the Location property in the property browser. You can choose a known location or define a custom location. Choose the Tools Menu [Command Line]. Location ID The vsshlids.hfile included in the VS 2010 SDK lists the location IDs for hundreds of IDE groups and menus. Many of these are available from the location picker, but if an ID is missing you can always use the Custom Location radio button and enter the details there. 4. Now click the Button1 shape and rename it MyButton. With the new button selected, the property browser displays the properties that can be changed. Be sure to set the Property Browser to Categorized. Modify the button's properties as follows: Click the Command Flags property and check the TextChanges checkbox. This enables us to modify the button text programmatically.
Page 3 of 19
Click the Icon property to open the Icon Picker dialog. Type happy in the search box and then select the HappyFace icon. Assign a command shortcut Ctrl-1 by entering 1 for the Key1 property and selecting the Control checkbox for the Mod1 property. You should now have a surface that looks like the following
5. Now save the project and rebuild it. To confirm that the MyButton command is available, hit Ctrl-F5 to run the experimental instance. Note that the button appears in the Tools menu. You can invoke the command either by pressing the button or by pressing Ctrl-1.
Page 4 of 19
Command Flags Command Flags affect the appearance and the behavior of buttons, combos, and menus. For a list of common Command Flags please refer to Appendix A: Common Command Flags. 6. Now lets look at the generated code (located in the Generated Code project folder) and see how you can modify it. Open the generated VSCT file by expanding the MyPackage.tt node and double-clicking the MyPackage.vsct file. As expected, there are definitions for the new button and for the keybinding. Notice that a GUID and ID for the button have been declared. Now open the generated package code file Package.cs (under Package.tt). Youll notice that the package implementation is declared as an abstract class, and that the OleMenuCommand command handler members are defined as virtual. The generated code uses a double-derived pattern where you subclass the generated class and override the methods in your code Note that a default command handler has been created. This default handler code gets more interesting with more advanced scenarios.
7. Now lets modify the user package code by overriding the command handling for the button. Lets make the button text change each time the button is clicked. Open the MyPackagePackage.cs file. This is where your package code can be added. Add the following line at the top of the package class definition:
private static int clickCount = 0;
Now override the buttons handler. On a line below the clickCount definition, start typing protected override and select the MyButtonExecuteHandler from the completion list . Modify the event handler code as follows:
protected override void MyButtonExecuteHandler(object sender, EventArgs e) { OleMenuCommand command = sender as OleMenuCommand; clickCount++; command.Text = "MyButton (clicked " + clickCount.ToString() + " times)"; }
Save the project and press Ctrl-F5 to launch the experimental instance. Click the command a few times and note that the commands menu label changes.
8. Lets go back to the design surface and make this example more interesting. You can create WPF or Winforms tool windows by just dragging a shape onto the design surface. Choose a WPF tool window from the toolbox, and drop into the Toolwindows swimlane. Name the tool window MyWindow. Update the tool windows Caption property in the property browser to My demo tool window, and leave all other properties with their defaults.
Page 5 of 19
Drag a new button from the toolbox to the Command UI region of the drawing surface, rename it LaunchToolwindow , change the icon to RightArrow, and set the shortcut to Ctrl-2 (Mod1= Ctrl and Key1=2). Use the Connect toolbox element to connect the button to the existing Tool Menu shape. Connect the new button to the tool window using a Launch toolbox connection element. The drawing surface should look as follows:
10. Save and rebuild the project and press Ctrl-F5 to launch the experimental instance. You can now open the tool window either from the Tools menu using the LaunchToolwindow button or by pressing Ctrl-2. Button Icons The Icon Picker lets you choose from over 100 icons built in to VS and from user bitmaps found in the Button Bitmaps project folder. The Icon Picker Import button allows you to select 16x16 icon bitmap files that will be added to the Button Bitmaps folder. VSCT files support bitmap strips and bitmaps in other image formats, but this first version of VSPackage Builder supports only 16x16 bitmap files. 11. Lets now look at the generated code for the tool window. In the generated Package.cs file youll notice that the MyPackagePackage class has an additional attribute because the VSPackage now provides a tool window (the ProvideToolWindowAttribute parameters map to several of the tool window properties). Also note that the default command handler for LaunchToolWindow has a call to a generated ShowToolWindowMyWindow method. The generated ToolWindowBase.cs (under ToolWindowBase.tt) provides the base implementation for the tool window. The MyWindowToolWindow.cs file(found directly under the project folder) subclasses the base tool window implementation and is intended for tool window user code (the double derived pattern again). The MyWindowToolWindow constructor adds a WPF user control as content, captured in the user MyWindowControl.xaml file. Page 6 of 19
Renaming Tool Windows If you rename the tool window shape on the design surface, you will need to manually rename the generated <Name>ToolWindow.cs file added to the project and the generated base class reference for the <Name>ToolWindow class. 12. Now lets add a tool window toolbar to the tool window by dragging elements onto the design surface. First drag a Tool window toolbar to the Command UI region of the drawing surface. Call it MyWindowToolbar and accept all other default properties. Then add a new group, call it MyToolbarGroup, and accept the default properties. Connect the group to MyWindowToolbar. Now add a Combo, name it MyToolbarCombo, and connect it to MyToolbarGroup. Connect MyWindowToolbar to the MyWindow tool window . The design surface should now look like this:
13. Now save the project, rebuild it, and hit Ctrl-F5 to run the experimental instance. When you launch the tool window, youll notice that it now has a toolbar that contains a combo, and that the combo has the values One, Two, and Three. Page 7 of 19
14. To make the resulting diagram more readable, drag a Comment shape to the drawing surface and type My first VSPackage Builder based extension. Comments are a convenient mechanism for adding annotations to your design diagrams. Notes: You can turn off sample data generation for combos by setting the Sample Data property of the Combo to false. You can change the type of combo in the property browser with the Type property.
3. Then update the CreateVisuals method to add a rectangle highlight for any TODO text as follows:
/// <summary> /// Within the given line add a shaded rectangle behind the TODO /// </summary> private void CreateVisuals(ITextViewLine line)
Page 8 of 19
{ // reference to the lines in the current TextView IWpfTextViewLineCollection textViewLines = _view.TextViewLines; // now extract the text from this line string lineText = line.Extent.GetText(); // Find the "TODO" matches in this line - and mark them up... int textIndex = 0; while (textIndex < lineText.Length) { int textMatchIndex; if ((textMatchIndex = lineText.IndexOf(_matchString, textIndex)) >= 0) { var span = new SnapshotSpan(line.Extent.Start + textMatchIndex, _matchString.Length); Geometry g = textViewLines.GetMarkerGeometry(span, false, new Thickness(1)); if (g != null) { // Create a rectangle adornment var rect = new Rectangle { Height = g.Bounds.Height, Width = g.Bounds.Width, Fill = _brush }; //Align the rectangle with the top of the bounds of the text geometry Canvas.SetLeft(rect, g.Bounds.Left); Canvas.SetTop(rect, g.Bounds.Top); // Add the adornment _layer.AddAdornment(AdornmentPositioningBehavior.TextRelative, span, null, rect, null); } textIndex = textMatchIndex + _matchString.Length; } else break; } } // no further matches on this line
4. Now hit Ctrl-F5 to run the experimental instance. In the experimental instance, open a text or code file containing TODO strings to see the extension in action.
Page 9 of 19
Notes: Shape sizing: you may need to resize shapes with long instance names, or to keep connections together (as above). Select the shape and drag the edge or corner youd like to resize. Diagram flow: you should try to keep menus, toolbars and external references at the top of the diagram, and the buttons and combos at the bottom. This makes it easier for readers and reviewers to see how your UI is organized. 7. Were going to use simple color icons for the button bitmaps using the Visual Studio bitmap editor. Right-click on the Button Bitmaps folder of the TodoPackage project in the Solution Explorer, select Add Item, then choose Bitmap File. Name the first bitmap Yellow.bmp.
Page 10 of 19
8. In the Bitmap Editor property browser, change the Colors property to 24 bit, the Height and Width properties to 16. Make sure the Colors tool window is active. Right-click on the Bitmap Editor design surface and select Show Colors Window if necessary. 9. Select the solid purple color on the top row of the Colors tool window and the Fill Tool from the Bitmap Editor toolbar, then click anywhere on the bitmap to set all the pixels purple. This color will be displayed as transparent when the bitmap is displayed in the Visual Studio command UI. 10. Select the solid yellow color from the Colors tool window, and the Filled Rectangle Tool from the Bitmap Editor toolbar. Now draw a 12x12 square centered on the bitmap. The result should look like this:
11. Repeat these steps to create a Green.bmp and Clear.bmp. For the latter choose a Rectangle Tool to draw a dark-gray outline 12x12. 12. Now go back to the design surface and select the appropriate icons for each button. As you work through this, update the priorities to set the order of the buttons in the HighlightCommands group so that the YellowHighlight button is at the top and the NoHighlight button is at the bottom:
Page 11 of 19
13. Now update the editor extension VSIX manifest to reference the package, and also prevent the TodoPackage project from generating a VSIX package. Start by right-clicking on the TodoPackage project node in the Solution Explorer and selecting Properties. Select the VSIX tab, and make sure that all checkboxes are unchecked. Now open the source.extension.vsixmanifest file for the TodoAdornment project. Click on the Add Content button in the VSIX Manifest designer, then select VS Package as the content type and the TodoPackage project as the source, and click OK. The TodoPackage references the Managed Package Framework so click Add Reference and select Visual Studio MPF in the Select Installed Extension list, and click OK. Close source.extension.vsixmanifest.
14. At this point you can Ctrl-F5 to run the experimental instance. If you open a text file and right-click to open the editor context menu, our new commands are visible. Of course they dont do anything useful yet!
15. We need to provide a means for our command handlers to affect the state of our editor extension. Given that the editor is WPF-based, were going to use a static Dependency Object for this purpose. 16. Open TodoPackagePackage.cs and add using statement references as follows:
using System.Windows; using System.Windows.Media;
17. Now add the following HighlightStateObject class and static member to the TodoPackagePackage class: Page 12 of 19
[Guid(GuidList.guidTodoPackagePkgString)] public class TodoPackagePackage : TodoPackagePackageBase { /// <summary> /// Dependency object and properties used to track and affect highlight state /// </summary> public class HighlightStateObject : DependencyObject { public static event DependencyPropertyChangedEventHandler HighlightIsOnChanged; private static readonly DependencyProperty HighlightIsOnProperty, HighlightBrushProperty;
static HighlightStateObject() { // Highlight brush HighlightBrushProperty = DependencyProperty.Register( "HighlightBrush", typeof(Brush), typeof(HighlightStateObject), new UIPropertyMetadata(new SolidColorBrush(Colors.LightGreen)) ); // Highlight On-Off HighlightIsOnProperty = DependencyProperty.Register( "HighlightIsOn", typeof(bool), typeof(HighlightStateObject), new UIPropertyMetadata(true, new PropertyChangedCallback(HighlightIsOnChangeCallback)) ); } // Dependency Property wrappers public bool HighlightIsOn { get { return (bool)GetValue(HighlightIsOnProperty); } set { SetValue(HighlightIsOnProperty, value); } } public Brush HighlightBrush { get { return (Brush)GetValue(HighlightBrushProperty); } set { SetValue(HighlightBrushProperty, value); } } // Property change handling private static void HighlightIsOnChangeCallback(DependencyObject o, DependencyPropertyChangedEventArgs e) { if (HighlightIsOnChanged != null) { HighlightIsOnChanged(o, e); } } } /// <summary> /// Static dependency object that will be referenced from the editor adornment /// </summary> public static HighlightStateObject HighlightState = new HighlightStateObject();
18. Then add command handlers to update the dependency properties based on which button has been pressed:
/// <summary> /// Execute handlers for the Yellow, Green and No Highlight commands /// </summary>
Page 13 of 19
protected override void YellowHighlightExecuteHandler(object sender, EventArgs e) { HighlightState.HighlightBrush = new SolidColorBrush(Colors.Yellow); HighlightState.HighlightBrush.Freeze(); if (!HighlightState.HighlightIsOn) HighlightState.HighlightIsOn = true; } protected override void GreenHighlightExecuteHandler(object sender, EventArgs e) { HighlightState.HighlightBrush = new SolidColorBrush(Colors.LightGreen); HighlightState.HighlightBrush.Freeze(); if (!HighlightState.HighlightIsOn) HighlightState.HighlightIsOn = true; } protected override void NoHighlightExecuteHandler(object sender, EventArgs e) { if (HighlightState.HighlightIsOn) HighlightState.HighlightIsOn = false; }
19. Before we can use the dependency properties in our adornment extension, we need to add a reference to the TodoPackage and supporting references to the TodoAdornment project. Right-click on the TodoAdornment References node and add the following references: Microsoft.VisualStudio.Shell.10.0 Microsoft.VisualStudio.Shell.Interop Microsoft.VisualStudio.Shell.Interop.10.0 Microsoft.VisualStudio.Shell.Interop.9.0 Microsoft.VisualStudio.Shell.Interop.8.0 Microsoft.VisualStudio.OLE.Interop
TodoPackage; System; Microsoft.VisualStudio.Shell; Microsoft.VisualStudio.Shell.Interop; Microsoft.VisualStudio.OLE.Interop; System.Windows.Data;
20. Open TodoAdornment.cs and add the following to the using statement section:
using using using using using using
21. To use the dependency object, add an event handler to the TodoAdornment class that will be called when the No Highlight command is executed:
/// <summary> /// Remove the adornments when the user selects highlighting off /// </summary> private void AdornmentOnOffHandler(object sender, DependencyPropertyChangedEventArgs e) { _layer.RemoveAllAdornments(); if (TodoPackagePackage.HighlightState.HighlightIsOn) { foreach (ITextViewLine line in _view.TextViewLines) { this.CreateVisuals(line); } } }
Page 14 of 19
22. Add an event handler for when the adornment text view has closed:
/// <summary> /// Unsubscribe to events, so that the adornment related objects are reference free for GC /// </summary> private void OnViewClosed(Object sender, EventArgs e) { _view.LayoutChanged -= OnLayoutChanged; TodoPackagePackage.HighlightStateObject.HighlightIsOnChanged -= AdornmentOnOffHandler; }
23. Now update the TodoAdornment constructor to wire in these new handlers:
public TodoAdornment(IWpfTextView view) { _view = view; _layer = view.GetAdornmentLayer("TodoAdornment"); // Initialise adornment settings // _brush = new SolidColorBrush(Colors.Yellow); _matchString = "TODO"; _padding = new Thickness(1); //Listen to any event that changes the layout (text changes, scrolling, etc) _view.LayoutChanged += OnLayoutChanged; //Listen for changes in highlight on state TodoPackagePackage.HighlightStateObject.HighlightIsOnChanged += AdornmentOnOffHandler; // Also wire up the view closed event _view.Closed += OnViewClosed; }
24. Finally change the line in the CreateVisuals() method that paints the adornment rectangle to use our HighlightBrush dependency property:
// Create a rectangle adornment using a brush bound to the dependency // property defined by the package var rect = new Rectangle { Height = g.Bounds.Height, Width = g.Bounds.Width}; rect.SetBinding(Rectangle.FillProperty, new Binding { Path = new PropertyPath("HighlightBrush"), Source = TodoPackagePackage.HighlightState });
25. Rebuild the solution and hit Ctrl-F5 to test the extension in the experimental instance. You should be able to change the color or remove the highlight of the TODO adornment. Pretty cool, huh?
Page 15 of 19
Start by dragging a Menu Controller shape onto the design surface (name it HighlightMenuController) and connecting the HighlightCommands group to it. Start the connection on the group, because the menu controller contains the group. Now add a group named HighlightToolbarGroup and a toolbar named HighlightToolbar, and connect the menu controller to the group and the group to the toolbar, so that the end result looks like:
28. We could go ahead and press Ctrl-F5 to load the extension in the experimental instance and show the new toolbar from View > Toolbars, but lets explore some command UI capabilities first. Select the HighlightToolbar shape and set the Visibility Constraints property to Text Editor so that the toolbar is shown only when the text editor is active. For each of the buttons, set the Button Text, Menu Text, and Tool Tip Text properties to "Show X Highlight". (Do not add spaces to the Name of the buttons, since this property becomes the identifier of the corresponding class when the code is generated.) Now select the HighlightMenuController instance and set the Command Flags property to IconAndText, TextChanges and TextIsAnchorCommand, so that the menu controller displays the menu text and icon for each of the buttons.
29. Now lets run the experimental instance. If you open a text file in the experimental instance, youll notice that our new toolbar appears and that the commands function as expected.
Page 16 of 19
Known issues
1.
In certain situations, simply building the solution does not cause the VSPackage to be updated in the VS Experimental Instance. If you suspect your project has not been updated properly, rebuilding the solution should solve the problem. If you remove a tool window shape from the design surface, and then add another instance with the same name, you will see an error message because the supporting code files are still present in the project folder. To work around this manually, remove the <Name>ToolWindow.cs file added to the project.
2.
To learn more
To learn more about the Visual Studio command system, start with the MSDN Menu and Toolbars Essentials topic and explore from there. Drilling into the VSCT file-related topics will be particularly helpful. The VSX samples on Code Gallery (http://code.msdn.microsoft.com/vsx) are also helpful when learning about Visual Studio extensibility. None of the samples leverage the VSPackage Builder template (yet!). The VSCT PowerToy is an extension that lets you explore existing commands and placement locations within Visual Studio. It is also helpful when attempting to understand why your command does not appear where you expected it to. To learn more about tool window properties start with the MSDN How to: Register a Tool Window (C#) article.
Sending feedback
The VSPackage Builder template started out as an exploration project to determine whether leveraging a DSL-based design surface would make it easier to create commands, menus, and toolbars in Visual Studio. The results from the exploration were sufficiently promising that it made sense to develop a complete functioning template and make it available for feedback and comment. Your feedback on the general usefulness of the template, feature suggestions, and bugs is welcomed. Please send feedback directly to [email protected].
Page 17 of 19
Description
Indicates that users can enter command parameters in the Command window when they type the canonical name of the command. Menu is created even if it has no groups or buttons. Apply this flag if the command does not appear on the top-level menu and you want to make it available for additional shell customization, for example, binding it to a key. After the VSPackage is installed, you may customize these commands by opening the Options dialog box from the Tools menu and then editing the command placement under the Keyboard Environment category. This flag does not affect placement on shortcut menus, toolbars, menu controllers, or submenus. By default, the command is disabled if the VSPackage that implements the command is not loaded - or the QueryStatus method has not been called. By default, the command is invisible if the VSPackage that implements the command is not loaded or the QueryStatus method has not been called. This flag does not affect commands placed on toolbars. This flag is usually combined with the DynamicVisibility flag. The visibility of the command can be changed through the QueryStatus method or through a VisibilityConstraints UI context GUID. This applies to commands that appear on menus, not on toolbars. Top-level toolbar items can be disabled but not hidden, when the OLECMDF_INVISIBLE flag is returned from the QueryStatus method. On a menu, this flag also indicates that it should be automatically hidden when all its members are hidden. This flag is typically assigned to submenus because top-level menus already have this behavior. This flag is usually combined with the DefaultInvisible flag.
Button
Combo
Menu
AlwaysCreate CommandWellOnly
DefaultDisabled
DefaultInvisible
DynamicVisibility
FixMenuController
If this command is positioned on a menu controller, the command is always the default, that is, the command is selected whenever the menu controller button itself is selected. If the menu controller has the TextIsAnchorCommand flag set, then the menu controller also takes its text from the command that has the FixMenuController flag. There should be only one command on a menu controller that is marked with the FixMenuController flag. If there is more than one command so marked, the last command in the menu becomes the default command.
IconAndText
Page 18 of 19
If this command is positioned on a menu controller, the command does not appear in the drop-down list. Show only an icon on a toolbar, but only text on a menu. If no icon is specified, the text is used on a toolbar. This flag applies to combo boxes placed on toolwindow toolbars. When this flag is set, the width becomes the minimum width for the combo box, and if there is room on the toolwindow toolbar, the combo box stretches to fill available space. This occurs only if the toolbar is horizontally docked, and only one combo box on the toolbar can use the flag (the flag is ignored on all except the first combo box). The command or menu text can be changed at run time, typically through the QueryStatus method. For a menu controller, the text of the menu is taken from the default (anchor) command. An anchor command is the last command selected or latched. If this flag is not set, the menu controller uses its own MenuText field. However, clicking the menu controller still enables the last selected command from that controller. This flag should be combined with the TextChanges flag. This flag applies only to menus of type MenuController or MenuControllerLatched.
TextChanges TextIsAnchorCommand
TextOnly
Show only text on a toolbar or a menu but no icon even if the icon is specified.
Page 19 of 19