Walkthrough: Autoloading Toolbox Items

This walkthrough illustrates how a managed VSPackage can use reflection to automatically load all the ToolboxItem items provided by its own assembly.

This walkthrough guides you through the following steps:

  1. Add and properly register all toolbox controls in the VSPackage objects using ToolboxItemAttribute, ToolboxBitmapAttribute, and DisplayNameAttribute.

  2. Create the following two controls, and add icons for each to the Toolbox:

    • Add one control using the default of the ToolboxItem class.

    • Add another control using a custom class derived from the ToolboxItem class.

  3. Register the VSPackage as providing ToolboxItem objects with the ProvideToolboxItemsAttribute class.

  4. Use reflection to generate a list of all ToolboxItem objects that the VSPackage provides when loaded.

  5. Create a handler for the ToolboxInitialized and ToolboxUpgraded events, which ensure that the ToolboxItem objects of the VSPackage are properly loaded.

  6. Implement a command on the VSPackage to force re-initialization of the toolbox.

Creating a Managed VSPackage

Carry out the following procedures to create a managed VSPackage.

To create a Managed VSPackage to provide toolbox items

  1. On the File menu, point to New, and then click Project to open the New Project dialog box.

  2. From the list of Other Project Types, select Extensibility and then click Visual Studio Extensibility.

  3. Run the Visual Studio Extensibility Package Wizard and carry out the following steps:

    1. Create a new project named LoadToolboxMembers.

    2. In the Select a Programming Language page, set the language to Visual C#.

    3. In the Select VSPackage Options page, check the Menu Command box.

    4. In the Command Options page, enter Initialize LoadToolboxMembers for the Command Name.

    5. Accept all other defaults.

    The wizard generates a managed project named LoadToolboxMembers. (If you create this VSPackage in Visual C++, an unmanaged resource-only project named LoadToolboxMembersUI is also created.)

  4. The walkthrough requires functionality available in System.Drawing.Design, and therefore, System.Drawing.Design must be added to the LoadToolboxMembers project's References:

    1. Right-click the references item in the LoadToolboxMembers hierarchy in Solution Explorer.

    2. Click Add Reference on the menu.

    3. In the .NET tab of the Add References dialog box, scroll down to System.Drawing.Design entry and double-click it.

  5. Verify the correctness of the wizard-generated code:

    1. Build the solution and verify that it compiles without errors.

    2. Test the solution by either clicking the Start command on the Debug menu of the Visual Studio IDE, or by pressing F5.

    You should see a new command item, Initialize LoadToolboxMembers, on the Tools menu when you run a new instance of the IDE (running with the experimental hive).

    Selecting this command opens a message box containing the text Inside Company.LoadToolboxMembers.LoadToolboxMembers.MenuItemCallback().

    Proceed to the next procedure, Creating a Toolbox Control.

Creating a Toolbox Control

In this section, you create and register a simple Toolbox control, Control1, derived from the UserControl class. For more information about authoring Windows Form controls and the ToolboxItem class, see Developing Windows Forms Controls at Design Time.

To create a Toolbox control to be used with a default ToolboxItem

  1. In Solution Explorer, add a UserControl object named Control1 in a file named Control1.cs to the project LoadToolboxMembers:

    1. In Solution Explorer, right-click the LoadToolboxMembers project, point to Add, and then click User Control.

    2. In the Add New Item dialog box, change the default name UserControl1.cs to Control1.cs.

      For more information on how to add new items to a project, see How to: Add New Project Items.

    The new control opens in Design view.

  2. Click and drag a Button control (located in the Common Controls category of the Toolbox) to the designer.

    注意

    If the Button control is not visible, make the Toolbox visible by selecting Toolbox from the IDE's View menu.

  3. Double-click the button you just created to view its button1_Click method and then add the following MessageBox line:

    private void button1_Click(object sender, EventArgs e)
    {
        MessageBox.Show("Hello world from " + this.ToString());
    }
    
  4. Modify the constructor of the Control1 class to set the text appearing in the button1 control after the InitializeComponent method is called:

    {
        InitializeComponent();
        this.button1.Text = this.Name.ToString() + " Button";
    }
    
  5. Add attributes to the file to allow the VSPackage to query the supplied ToolboxItem class:

    namespace Company.LoadToolboxMembers 
    {
        [DisplayNameAttribute("ToolboxMember Case 1")]     //We will use this as the ToolboxItem display name.    [ToolboxItemAttribute(true)]     [ToolboxBitmap(typeof(LoadToolboxMembers), "Control1.bmp")]     //Indicate the custom bitmap to use.     //Note Build Action for bitmap must be set to "embed".
        public partial class Control1 : UserControl 
    {
    
  6. On the File menu, click Save to save the file.

    Proceed to the next procedure, To create a Toolbox control for using a custom ToolboxItem-derived class.

In the following procedure, you create and register a simple toolbox control, Control2, which derives from the User control, which in turn derives from the ToolboxItem class.

To create a Toolbox control for using a custom ToolboxItem-derived class

  1. Create another User control named Control2, following the directions provided in steps above.

  2. Add System.Drawing.Design to the namespaces used in Control2.cs.

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Drawing;
    using System.Data;
    using System.Text;
    using System.Windows.Forms;
    using System.Drawing.Design;
    
  3. Add attributes to the file.

    namespace Company.LoadToolboxMembers 
    {
        [DisplayNameAttribute("ToolboxMember Case 2")]     //This is the ToolboxItem display name.    [ToolboxItemAttribute(typeof(Control2_ToolboxItem))]     // This will be the ToolboxItem Class below with this control    [ToolboxBitmap(typeof(LoadToolboxMembers), "Control2.bmp")]     //Indicates the custom bitmap to use.     //Note Build Action for bitmap must be set to "embed".
        public partial class Control2 : UserControl 
    {
    

    These provide support that allows the VSPackage to query for a ToolboxItem class.

    For more information and examples about writing custom ToolboxItem objects, see the discussion in the ToolboxItem reference page.

  4. Create the class Control2_ToolboxItem. This ToolboxItem is constructed from the control Control2 and added to the toolbox. The class must have SerializableAttribute applied to it.

    [SerializableAttribute()]  //ToolboxItem implementations have this attribute.
    internal class Control2_ToolboxItem : ToolboxItem {
    
        public Control2_ToolboxItem(Type type)
            : base(type) {
    
        }
        public override void Initialize(Type type) //Initialize is called by the base constructor.
            {
            if(!type.Equals(typeof(Control2))) {
                throw new ArgumentException("Bad constructor argument " +
                    typeof(Control2_ToolboxItem).FullName + " takes only " + typeof(Control2).FullName +
                    "objects as an argument to its constructor");
            } else {
                base.Initialize(type);
            }
        }
    
    }
    
  5. From the File menu, choose Save to save the file.

Embedding Bitmap Icons

The two instances of ToolboxBitmapAttribute used above indicate that the project represents the two controls with these icons:

  • Control1.bmp, found in the namespace containing the class Control1

  • Control2.bmp, found in the namespace containing the class Control2

To embed bitmap icons for the ToolboxItem

  1. Add two new bitmaps to the project:

    1. Right-click the project LoadToolboxMembers.

    2. Point to Add, and then click New Item.

    3. In the Add New Item dialog box, select Bitmap File, and name the file Control1.bmp.

    4. Repeat steps a. - c. and name the second bitmap Control2.bmp.

      This opens each bitmap in the Visual Studio bitmap editor.

  2. Set the size of each icon to 16∝16.

    1. For each icon, click Properties Window on the View menu.

    2. In the Properties window, set Height and Width to 16.

  3. Use the bitmap editor in Visual Studio, create an image for each icon.

  4. In Solution Explorer, right-click Control1.bmp and then choose Properties.

    Repeat this step for Control2.bmp.

  5. Set the Build Action property for each bitmap to Embedded Resource.

  6. On the File menu, click Save All to save the files.

Modifying the VSPackage Implementation

The default implementation of the VSPackage provided by the Visual Studio wizard must be modified to:

This procedure leads you through the steps for modifying the package implementation.

注意

Beginning with Visual Studio 2008 SDK, use XML Command Table (.vsct) files instead of command table configuration (.ctc) files to define how menus and commands appear in your VSPackages. For more information, see XML-Based Command Table Configuration (.vsct) Files.

Modify the package implementation to be a Toolbox item provider for the VSPackage

  1. In Solution Explorer, right-click VsPkg.cs and click View Code to edit the LoadToolboxMembers class.

  2. Modify the declaration of the LoadToolboxMembers class, the solution's implementation of the Package class.

    1. Add the following namespaces to the other using statements in VsPkg.cs.

          System.Collections

          System.ComponentModel

          System.Drawing.Design

          System.Reflection

    2. Register the VSPackage as a ToolboxItem class by adding an instance of ProvideToolboxItemsAttribute.

          [ProvideToolboxItemsAttribute(1)]

      注意

      The sole argument of ProvideToolboxItemsAttribute is the version of ToolboxItem provided by the VSPackage. Changing this value forces the IDE to load the VSPackage even if it has an earlier cached version of ToolboxItem class.

    3. Add two new private fields to the LoadToolboxMembers class:

      An ArrayList member called ToolboxItemList to hold a list of the ToolboxItem objects the LoadToolboxMembers class manages.

      private ArrayList ToolboxItemList = null;
      

      A String named CategoryTab containing the Toolbox category or tab that is used to hold the ToolboxItem objects managed by the LoadToolboxMembers class.

      private String CategoryTab = "LoadToolboxMembers Walkthrough";
      

    The result of this modification looks like this:

    using System;
    using System.Diagnostics;
    using System.Globalization;
    using System.Runtime.InteropServices;
    using System.ComponentModel.Design;
    using Microsoft.Win32;
    using Microsoft.VisualStudio.Shell.Interop;
    using Microsoft.VisualStudio.OLE.Interop;
    using Microsoft.VisualStudio.Shell;
    using System.Collections;using System.ComponentModel;using System.Drawing.Design;using System.Reflection;
    
    namespace Company.LoadToolboxMembers
    {
        ...
        [PackageRegistration(UseManagedResourcesOnly = true)]
        ...
        [DefaultRegistryRoot("Software\\Microsoft\\VisualStudio\\8.0")]
        ...
        [InstalledProductRegistration(false, "#110", "#112", "1.0", IconResourceID = 400)]
        ...
        [ProvideLoadKey("Standard", "1.0", "Package Name", "Company", 1)]
        ...
        [ProvideMenuResource(1000, 1)]
        [ProvideToolboxItems(1)]
        [Guid(GuidList.guidLoadToolboxMembersPkgString)]
        public sealed class LoadToolboxMembers : Package
        {
            //List to hold array of toolboxItems
            private ArrayList ToolboxItemList = null;
            private String CategoryTab = "LoadToolboxMembers Walkthrough";
        ...
        }
    
  3. Modify the Initialize method in LoadToolboxMembers to:

    1. Subscribe to the ToolboxInitialized and ToolboxUpgraded events.

    2. Call the method CreateItemList to fill the ArrayList object ToolboxItemList. The ToolboxItemList will contain a list of all the ToolboxItems that LoadToolboxMembers manages.

          protected override void Initialize() {
              Trace.WriteLine(string.Format(CultureInfo.CurrentCulture, "Entering Initialize() of: {0}", this.ToString()));
              base.Initialize();
      
              //Add the command handlers for menu (commands must exist in the .ctc file)
              OleMenuCommandService mcs = GetService(typeof(IMenuCommandService)) as OleMenuCommandService;
              if (null == mcs) {
                  throw new ApplicationException("Unable to access the IMenuCommandService.");
              } else {
      
                  //Create the command for the menu item.
                  CommandID menuCommandID = new CommandID(GuidList.guidLoadToolboxMembersCmdSet, (int)PkgCmdIDList.cmdidMyCommand);
                  MenuCommand menuItem = new MenuCommand(new EventHandler(MenuItemCallback), menuCommandID);
                  mcs.AddCommand(menuItem);
                  Trace.WriteLine(string.Format(CultureInfo.CurrentCulture, "Subscribing to ToolboxInitialized and ToolboxUpgraded in Initialize() of : {0}", this.ToString()));            ToolboxInitialized += new EventHandler(OnToolboxInitialized);            ToolboxUpgraded += new EventHandler(OnToolboxUpgraded);
              }
              // Use reflection to fill a list of ToolboxItems in the current assembly        // to be used in OnToolboxInitialized and OnToolboxUpgraded        ToolboxItemList = CreateItemList(Assembly.GetExecutingAssembly());        if (ToolboxItemList == null) {            throw new ApplicationException("Unable to generate a toolbox Items listing for "                                            + GetType().FullName);
              }
          }
      
  4. Add a method named CreateItemList to construct instances of the ToolboxItem objects available in the LoadToolboxMembers assembly by using metadata:

    private ArrayList CreateItemList(Assembly assembly) {
                //Scan each type in the assembly, determine if it is a valid toolbox item implementation
                //If so, create the item and add it to our ToolboxItemList
    
                ArrayList List = new ArrayList();
                foreach (Type possibleItem in assembly.GetTypes()) {
                    //Check that the type meets criteria to be used for implementation of a toolboxItem
                    if ( ! typeof(IComponent).IsAssignableFrom(possibleItem) ||  //Must Implement IComponent
                        possibleItem.IsAbstract) {                            //Must not be abstract class
                        continue;
                    }
    
                    // Verify that that the usercontrol has appropriate constructors 
                    // either default, or taking an argument
                    if ( (possibleItem.GetConstructor(new Type[] {typeof(Type)})) == null &&
                         (possibleItem.GetConstructor(new Type[0])) == null ){
                        continue;
                    }
    
                    //Get Attribute of candidate class
                    AttributeCollection attribs = TypeDescriptor.GetAttributes(possibleItem);
                    ToolboxItemAttribute tba = attribs[typeof(ToolboxItemAttribute)] as ToolboxItemAttribute;
                    ToolboxItem item = null;
                    if (tba != null && !tba.Equals(ToolboxItemAttribute.None)) {
                        if (!tba.IsDefaultAttribute()) {  //Handle the case for custom ToolBoxItem implementation.
                            item = null;
                            Type itemType = tba.ToolboxItemType;
                            //Get the constructor for the ToolboxItem applied to the user control.
                            ConstructorInfo ctor = itemType.GetConstructor(new Type[] {typeof(Type)                        });
                            if (ctor != null && itemType != null) {
                                item = (ToolboxItem)ctor.Invoke(new object[] {
                                    possibleItem
                                });
                            } else {
                                ctor = itemType.GetConstructor(new Type[0]);
                                if (ctor != null) {
                                    item = (ToolboxItem)ctor.Invoke(new object[0]);
                                    item.Initialize(possibleItem);
                                }
                            }
                        } else { //Handle items created using the default constructor.
                            item = new ToolboxItem(possibleItem);
                        }
                    }
                    if (item == null) {
                         throw new ApplicationException("Unable to create a ToolboxItem  object from" + possibleItem.Name + ".");
                    }
                    //If there is a DisplayNameAttribute, use that as Display Name, otherwise the default name is used.
                    DisplayNameAttribute displayName = attribs[typeof(DisplayNameAttribute)] as DisplayNameAttribute;
                    if (displayName != null && !displayName.IsDefaultAttribute()) {
                        item.DisplayName = displayName.DisplayName;
                    }
    
                    List.Add(item);
                }
    
                return List;
            }
    
  5. Implement the OnToolBoxInitialized method, the handler for the ToolboxInitialized event.

    The OnToolboxInitialized method uses the list of ToolboxItem objects contained in the ToolboxItemList member of the LoadToolboxMembers class. It also:

    1. Determines whether any ToolboxItem objects managed by the LoadToolboxMembers VSPackage are already present in the Toolbox category defined by the variable CategoryTab, which is set to the string "LoadToolboxMembers Walkthrough".

      Any instances of the LoadToolboxMembers class's ToolboxItem objects found are removed from the Toolbox.

    2. Adds new instances of all ToolboxItem objects listed in ToolboxItemList to the category "Walkthrough Tab".

    3. Sets the category "LoadToolboxMembers Walkthrough" as the Toolbox's active tab.

      private void OnToolboxInitialized(object sender, EventArgs e) {
                  //Add new instances of all ToolboxItems contained in ToolboxItemList, 
                  //if a duplicate or near duplicate exists, remove it.
      
                  IToolboxService toolboxService = GetService(typeof(IToolboxService)) as IToolboxService;
                  IVsToolbox toolbox = GetService(typeof(IVsToolbox)) as IVsToolbox;
      
                  toolboxService.Refresh();
                  //Remove target tab and all controls under it.
                  foreach (ToolboxItem oldItem in toolboxService.GetToolboxItems(CategoryTab)) {
                      toolboxService.RemoveToolboxItem(oldItem);
                  }
                  toolbox.RemoveTab(CategoryTab);
      
                  foreach (ToolboxItem itemFromList in ToolboxItemList) {
      
                      //Make sure to clean up old item
                      foreach (ToolboxItem itemOnTab in toolboxService.GetToolboxItems(CategoryTab)) {
                          if (itemOnTab.Equals(itemFromList)) {
                              toolboxService.RemoveToolboxItem(itemOnTab);
                          }
                      }
                      toolboxService.AddToolboxItem(itemFromList, CategoryTab);
                      Trace.WriteLine(string.Format(CultureInfo.CurrentCulture, "Adding item {0} using control {1} to tab {2} on the Toolbox",
                          itemFromList.ToString(), itemFromList.TypeName, CategoryTab));
                      //Set the current Tab and item as selected.
                      toolboxService.SelectedCategory = CategoryTab;
                  }
                  toolboxService.Refresh();
              }
      
  6. Implement the method OnToolboxUpgraded, the handler for the ToolboxUpgraded event. The OnToolboxUpgraded method:

    1. Removes any existing instance of a ToolboxItem managed by the LoadToolboxMembers VSPackage currently present in the Toolbox category "Walkthrough Tab".

    2. Inserts any ToolboxItem listed in the ToolboxItemList that is not present in the "LoadToolboxMembers Walkthrough" category.

      private void OnToolboxUpgraded(object sender, EventArgs e) {
                  IToolboxService toolboxService = GetService(typeof(IToolboxService)) as IToolboxService;
                  //Remove all current items not in upgrade item list
                  foreach (ToolboxItem currentItem in toolboxService.GetToolboxItems(CategoryTab)) {
                          toolboxService.RemoveToolboxItem(currentItem);
                  }
                      //Add Upgraded items.
                  foreach (ToolboxItem newItem in ToolboxItemList){
                          toolboxService.AddToolboxItem(newItem);
                      }
                      toolboxService.Refresh();
              }
      

      注意

      As an exercise, one could consider developing a mechanism for testing the version of the VSPackage or the items and only update if the VSPackage's version has changed, or if the version of the ToolboxItem has changed.

Initializing the Toolbox

To implement a command to initialize the Toolbox

  • Change the menu item's command handler method MenuItemCallBack.

    1. Right-click VsPackage.cs and choose View Code. This opens the file in the text editor.

    2. Replace the existing implementation of MenuItemCallBack with:

          private void MenuItemCallback(object sender, EventArgs e) {
      
              IVsUIShell uiShell = (IVsUIShell)GetService(typeof(SVsUIShell));
              IVsPackage pkg = GetService(typeof(Package)) as IVsPackage;
              pkg.ResetDefaults((uint)__VSPKGRESETFLAGS.PKGRF_TOOLBOXITEMS);
      
          }
      

Building and Running the Solution

To build the solution

  1. Choose the Rebuild Solution command on the Visual Studio IDE's Build menu.

  2. Start a second instance of the Visual Studio IDE running using the experimental registry hive. This can be done one of two ways:

    • The Visual Studio IDE automatically starts another instance of the Visual Studio IDE running if you choose either the Start or Start with Debugging command from the Debug menu.

    • By manually starting a second copy of the Visual Studio IDE from the command prompt. At the command prompt, type:

      devenv /rootsuffix exp

      For more information on using the experimental registry hive, see Experimental Build.

  3. A menu item labeled Initialize LoadToolboxMembers should appear at the top of the Visual StudioTools menu beside an icon with the number 1 on it.

Once a new instance of the Visual Studio IDE starts using the experimental registry hive, the walkthrough can be exercised in the instance of the Visual Studio IDE running under the experimental hive.

To exercise this walkthrough

  1. Create a new C# or Visual Basic Windows application using C# or Visual Basic:

    • On the Visual StudioFile menu, point to New, and then click Project to open the New Project dialog box.

    • On the New Project dialog box, select Windows Application under either the Visual Basic or Visual C# list.

      A Form-based designer should appear.

  2. Add the new toolbox controls to the designer by selecting one or both of the controls found on the LoadToolboxMembers Walkthrough category of the Toolbox and drag them onto the form.

    注意

    If the Toolbox is not visible, make the it visible by clicking the IDE's View menu and choosing Toolbox.

    If the LoadToolboxMembers Walkthrough category does not appear when the Toolbox is visible, re-initialize the Toolbox by choosing Initialize LoadToolboxMembers on the Tools menu.

  3. Build the windows application by choosing Rebuild Solution on the Visual StudioBuild menu.

  4. Run the application by choosing either the Start or Start with Debugging command from the Debug menu.

  5. When the application runs, click one of this walkthrough's controls added to the application. A message box appears that says, "Hello world from Company.LoadToolboxMembers.Control1 or Hello world from Company.LoadToolboxMembers.Control2".

Analysis of the Implementation

Creating Toolbox Controls

The attributes assigned to Control1 and Control2 are used by the method CreateItemList when it queries the Assembly for available toolbox controls.

  • The ToolboxItemAttribute performs two functions:

  • The ToolboxBitmapAttribute class specifies bitmaps used by the environment to identify the controls.

    Embedding a bitmap in an assembly by setting its Build Action property to Embedded Resource, places the bitmap in the assembly's namespace, so Control1.bmp could be referred to as Company.LoadToolboxMembers.Control1.bmp.

    ToolboxBitmapAttribute supports a constructor which takes this full path as an argument. For instance, we could have applied a ToolboxBitmapAttribute class to Control1 using [ToolboxBitmap("Company.LoadToolboxMembers.Control1.bmp")].

    To support flexibility, we have chosen a constructor which takes a Type class as the first argument to the ToolboxBitmapAttribute constructor. The namespace used to identify the bitmap file is obtained from the Type and inserted in front of the bitmap's base name.

    As the Type object implementing Package, LoadToolboxMembers, is in the Company.LoadToolboxMembers namespace [ToolboxBitmap(Typeof(Control1), "Control1.bmp")] is equivalent to [ToolboxBitmap("Company.LoadToolboxMembers.Control1.bmp")].

  • DisplayNameAttribute specifies the control's name in the Toolbox.

Registering a Toolbox Control Provider

Applying the ProvideToolboxItemsAttribute class to the class implementing Package informs the Visual Studio environment that the VSPackage should be loaded to provide toolbox controls by modifying the registry. For more information on the registry settings for a ToolboxItem provider, see Registering Toolbox Support Features.

The Visual Studio environment uses the version argument to the ProvideToolboxItemsAttribute constructor in managing the caching of those VSPackages that provide items to the Toolbox. Once a VSPackage has been loaded to provide toolbox items, a cached version of the VSPackage is used until the registered version of the provider changes.

Therefore, if after having built and run this walkthrough, you want to modify it, be sure to change the version argument of the ProvideToolboxItemsAttribute constructor applied to AddToolboxItem. For instance, [ProvideToolboxItems(1)] should become [ProvideToolboxItems(2)].

If the version is not upgraded, then the Visual Studio environment does not load any modifications made.

In this walkthrough, the VSPackage is configured to provide only Toolbox user controls supporting the default Clipboard format (for a list of default Clipboard formats, see Toolbox (Visual Studio SDK)).

If you wish to support other Clipboard formats, or choose not to support a default format, apply the attribute ProvideToolboxFormatAttribute to the LoadToolboxMembers class. For more information on registering a Toolbox control provider, see How to: Provide Custom Toolbox Items Using the Managed Package Framework.

Adding Controls to the Toolbox

The functionality in CreateItemList emulates what is available in IToolboxService.GetToolboxItems. Rather than using this method, we explicitly poll the assembly's metadata as an exercise.

The method CreateItemList takes care to only examine non-abstract Type objects which implement the IComponent interfaces.

Next Steps

Rewriting this walkthrough using IToolboxService.GetToolboxItems rather than CreateItemList would make it more robust.

Another modification would be to modify CreateItemList to make use of ParseToolboxResource to load controls into the Toolbox based on a text list embedded as part of the LoadToolboxMembers assembly.

See Also

Concepts

Toolbox (Visual Studio SDK)

Registering Toolbox Support Features

How to: Provide Custom Toolbox Items Using the Managed Package Framework

Toolbox Walkthroughs