Walkthrough: Autoloading Toolbox Items

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.

NoteNote

The recommended way to add custom controls to the Toolbox is to use the Toolbox Control templates that come with the Visual Studio 10 SDK, which include auto-loading support. This topic is retained for backward compatibility, for adding existing controls to the Toolbox, and for advanced Toolbox development.

For more information on creating toolbox controls by using the templates, see How to: Create a Toolbox Control That Uses Windows Forms and How to: Create a Toolbox Control That Uses WPF.

This walkthrough guides you through the following steps:

  1. Add and correctly register all Toolbox controls in the VSPackage objects by using ToolboxItemAttribute, ToolboxBitmapAttribute, and DisplayNameAttribute.

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

    • Add one control by using a default ToolboxItem class.

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

  3. Register the VSPackage as providing ToolboxItem objects that have the ProvideToolboxItemsAttribute class.

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

  5. Create a handler for the ToolboxInitialized and ToolboxUpgraded events. Doing this guarantees that the ToolboxItem objects of the VSPackage are correctly loaded.

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

To complete this walkthrough, you must install the Visual Studio 2010 SDK.

NoteNote

For more information about the Visual Studio SDK, see Visual Studio Integration SDK. To find out how to download the Visual Studio SDK, see Visual Studio Extensibility Developer Center on the MSDN Web site.

The Visual Studio Package project template can be found in three different locations in the New Project dialog:

  1. Under Visual Basic Extensibility. The default language of the project is Visual Basic.

  2. Under C# Extensibility. The default language of the project is C#.

  3. Under Other Project Types Extensibility. The default language of the project is C++.

To create the LoadToolboxMembers VSPackage

  1. Create a VSPackage named LoadToolboxMembers. For more information, see Walkthrough: Creating a Menu Command By Using the Visual Studio Package Template.

  2. Add a menu command.

    Name the command Initialize LoadToolboxMembers VB for Visual Basic or Initialize LoadToolboxMembers CS for Visual C#.

If you follow this walkthrough for more than one language, you must update the project to disambiguate the generated assemblies.

To disambiguate Visual Basic and Visual C# VSPackages

  1. For Visual Basic:

    • In Solution Explorer, open the project properties, and select the Application tab.

      Change the assembly name to LoadToolboxMembersVB, and change the default namespace to Company.LoadToolboxMembersVB.

  2. For Visual C#:

    1. In Solution Explorer, open the project properties, and select the Application tab.

      Change the assembly name to LoadToolboxMembersCS, and change the default namespace to Company.LoadToolboxMembersCS.

    2. Open the LoadToolboxMembersPackage class in the code editor.

      To use the refactoring tools to rename the existing namespace, right-click the existing namespace name, LoadToolboxMembers, point to Refactor, and then click Rename. Change the name to LoadToolboxMembersCS.

  3. Save all changes.

To add supporting references

  1. In the LoadToolboxMembers project, add a reference to the System.Drawing.Design .NET Framework component, as follows.

    1. In Solution Explorer, right-click the LoadToolboxMembers project, and then click Add Reference.

    2. On the .NET tab of the Add References dialog box, double-click System.Drawing.Design.

  2. For Visual Basic, add the following namespaces to the imported namespaces list in the project:

    • Company.LoadToolboxMembersVB

    • System

    • System.ComponentModel

    • System.Drawing

    • System.Windows.Forms

To test the generated code

  1. Compile and start the VSPackage in the Visual Studio experimental hive.

  2. On the Tools menu, click Initialize LoadToolboxMembers VB or Initialize LoadToolboxMembers CS.

    This opens a message box that contains text that indicates that the package's menu item handler was called.

  3. Close the experimental version of Visual Studio.

In this section, you create and register a user control, Control1, that declares an associated default Toolbox item. For more information about how to author Windows Form controls and the ToolboxItem class, see Developing Windows Forms Controls at Design Time.

To create a Toolbox control that will be used with a default ToolboxItem

  1. In Solution Explorer, add a UserControl object to the LoadToolboxMembers project, as follows:

    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 name to Control1.vb for Visual Basic or Control1.cs for Visual C#.

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

    The new control opens in Design view.

  2. From the Toolbox, drag a Button control (located in the Common Controls category) to the designer.

  3. Double-click the button you just created. Doing this generates an event handler for the button's Click event. Update the event handler by using the following code:

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

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

    
    // Set the display name and custom bitmap to use for this item.
    // The build action for the bitmap must be "Embedded Resource".
    [DisplayName("ToolboxMember 1 CS")]
    [Description("Custom toolbox item from package LoadToolboxMembers.")]
    [ToolboxItem(true)]
    [ToolboxBitmap(typeof(Control1), "Control1.bmp")]
    public partial class Control1 : UserControl
    {
    
    
    
  6. Save the file.

In the following procedure, you create and register a second user control, Control2, and an associated custom Toolbox item, Control2_ToolboxItem, that is derived from the ToolboxItem class.

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

  1. Create a second user control named Control2. Double click on the form to bring up the code file.

  2. Add System.Drawing.Design and System.Globalization to the namespaces that are used in the class.

    
    using System.Drawing.Design;
    using System.Globalization;
    
    
    
  3. Add a button and button click event handler and update the control's constructor just like you updated the first control.

  4. Add the DisplayNameAttribute, DescriptionAttribute, ToolboxItemAttribute, and ToolboxBitmapAttribute attributes to the file.

    These attributes enable the VSPackage to query for a ToolboxItem class.

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

    Together with the previous changes, your second control class should resemble the following code. The symbol Control2_ToolboxMenu will be undefined until after the next step.

    
    // Set the display name and custom bitmap to use for this item.
    // The build action for the bitmap must be "Embedded Resource".
    // Also declare a custom toolbox item implementation.
    [DisplayName("ToolboxMember 2 CS")]
    [Description("Custom toolbox item from package LoadToolboxMembers.")]
    [ToolboxItem(typeof(Control2_ToolboxItem))]
    [ToolboxBitmap(typeof(Control2), "Control2.bmp")]
    public partial class Control2 : UserControl
    {
        public Control2()
        {
            InitializeComponent();
    
            button1.Text = this.Name + " Button";
        }
    
        private void button1_Click(object sender, EventArgs e)
        {
            MessageBox.Show("Hello world from " + this.ToString());
        }
    }
    
    
    
  5. Create a class named Control2_ToolboxItem. This ToolboxItem is constructed for the second control and added to the Toolbox. The class must have SerializableAttribute applied to it.

    
    [Serializable()]
    internal class Control2_ToolboxItem : ToolboxItem
    {
        public Control2_ToolboxItem(Type toolType)
            : base(toolType) { }
    
        public override void Initialize(Type toolType)
        {
            if (!toolType.Equals(typeof(Control2)))
            {
                throw new ArgumentException(
                    string.Format(CultureInfo.CurrentCulture,
                        "The {0} constructor argument must be of type {1}.",
                        this.GetType().FullName, typeof(Control2).FullName));
            }
    
            base.Initialize(toolType);
        }
    }
    
    
    
  6. Save the file.

The two instances of ToolboxBitmapAttribute used earlier indicate that the project represents the two controls by using the following icons:

  • Control1.bmp, located in the namespace that contains the first control.

  • Control2.bmp, located in the namespace that contains the second control.

To embed bitmap icons for the ToolboxItem

  1. Add two new bitmaps to the project, as follows.

    1. Right-click the LoadToolboxMembers project.

    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 these steps for the second bitmap and name it Control2.bmp.

      Doing this opens each bitmap in the Visual Studio bitmap editor.

  2. Set the size of each icon to 16 x 16, as follows.

    1. For each bitmap, 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 to create an image for each icon.

  4. In Solution Explorer, click each bitmap file, and then, in the Properties window, set the Build Action property to Embedded Resource.

  5. Save all open files.

The default implementation of the VSPackage must be modified to do the following things:

The next procedure shows how to modify the package implementation.

To modify the package implementation to be a Toolbox item provider for the VSPackage

  1. Open the LoadToolboxMembersPackage.cs or LoadToolboxMembersPackage.vb file in the code editor.

  2. Modify the declaration of the LoadToolboxMembersPackage class, which is the implementation of the Package class in the solution, as follows.

    1. Add the following namespace directives to the LoadToolboxMembersPackage class file.

      
      using System.Collections;
      using System.ComponentModel;
      using System.Drawing.Design;
      using System.Reflection;
      
      
      
    2. Register the VSPackage as a ToolboxItem class by adding an instance of ProvideToolboxItemsAttribute.

      NoteNote

      The sole argument of ProvideToolboxItemsAttribute is the version of ToolboxItem that is 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 the following two new private fields to the LoadToolboxMembersPackage class:

      An ArrayList member, named ToolboxItemList, to hold a list of the ToolboxItem objects that the LoadToolboxMembersPackage class manages.

      A String, named CategoryTab, that contains the Toolbox category or tab that is used to hold the ToolboxItem objects that are managed by the LoadToolboxMembersPackage class.

    The result of this modification resembles the following code:

    
    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.LoadToolboxMembersCS
    {
        // ...
        [PackageRegistration(UseManagedResourcesOnly = true)]
        // ...
        [DefaultRegistryRoot("Software\\Microsoft\\VisualStudio\\9.0")]
        // ...
        [InstalledProductRegistration(false, "#110", "#112", "1.0", IconResourceID = 400)]
        // ...
        [ProvideLoadKey("Standard", "1.0", "Package Name", "Company", 1)]
        // ...
        [ProvideMenuResource(1000, 1)]
        [Guid(GuidList.guidLoadToolboxMembersPkgString)]
        [ProvideToolboxItems(1)]
        public sealed class LoadToolboxMembersPackage : Package
        {
            // List for the toolbox items provided by this package.
            private ArrayList ToolboxItemList;
    
            // Name for the Toolbox category tab for the package's toolbox items.
            private string CategoryTab = "LoadToolboxMembers Walkthrough CS";
    
            // ...
    
    
    
  3. Expand the Package Members region to modify the Initialize method to do the following things:

    • For Visual C#, subscribe to the ToolboxInitialized and ToolboxUpgraded events.

    • Call the CreateItemList method to fill the ArrayList object ToolboxItemList. The ToolboxItemList will contain a list of all the toolbox items that LoadToolboxMembersPackage manages.

    
    protected override void Initialize()
    {
        Trace.WriteLine (string.Format(CultureInfo.CurrentCulture,
            "Entering Initialize() of: {0}", this.ToString()));
        base.Initialize();
    
        // Add our command handlers for menu (commands must exist in the .vsct file)
        OleMenuCommandService mcs =
            GetService(typeof(IMenuCommandService)) as OleMenuCommandService;
        if ( null != mcs )
        {
            // Create the command for the menu item.
            CommandID menuCommandID =
                new CommandID(GuidList.guidLoadToolboxMembersCmdSet,
                    (int)PkgCmdIDList.cmdidMyCommand);
            MenuCommand menuItem =
                new MenuCommand(MenuItemCallback, menuCommandID );
            mcs.AddCommand( menuItem );
    
            // Subscribe to the toolbox intitialized and upgraded events.
            ToolboxInitialized += new EventHandler(OnRefreshToolbox);
            ToolboxUpgraded += new EventHandler(OnRefreshToolbox);
        }
    
        // Use reflection to get the toolbox items provided in this assembly.
        ToolboxItemList = CreateItemList(this.GetType().Assembly);
        if (null == ToolboxItemList)
        {
            // Unable to generate the list.
            // Add error handling code here.
        }
    }
    
    
    
  4. Add two methods, CreateItemList and CreateToolboxItem, to construct, by using metadata, instances of the ToolboxItem objects that are available in the LoadToolboxMembers assembly, as follows:

    
    // Scan for toolbox items in the assembly and return the list of
    // toolbox items.
    private ArrayList CreateItemList(Assembly assembly)
    {
        ArrayList list = new ArrayList();
        foreach (Type possibleItem in assembly.GetTypes())
        {
            ToolboxItem item = CreateToolboxItem(possibleItem);
            if (item != null)
            {
                list.Add(item);
            }
        }
        return list;
    }
    
    // If the type represents a toolbox item, return an instance of the type;
    // otherwise, return null.
    private ToolboxItem CreateToolboxItem(Type possibleItem)
    {
        // A toolbox item must implement IComponent and must not be abstract.
        if (!typeof(IComponent).IsAssignableFrom(possibleItem) ||
            possibleItem.IsAbstract)
        {
            return null;
        }
    
        // A toolbox item must have a constructor that takes a parameter of
        // type Type or a constructor that takes no parameters.
        if (null == possibleItem.GetConstructor(new Type[] { typeof(Type) }) &&
            null == possibleItem.GetConstructor(new Type[0]))
        {
            return null;
        }
    
        ToolboxItem item = null;
    
        // Check the custom attributes of the candidate type and attempt to
        // create an instance of the toolbox item type.
        AttributeCollection attribs =
            TypeDescriptor.GetAttributes(possibleItem);
        ToolboxItemAttribute tba =
            attribs[typeof(ToolboxItemAttribute)] as ToolboxItemAttribute;
        if (tba != null && !tba.Equals(ToolboxItemAttribute.None))
        {
            if (!tba.IsDefaultAttribute())
            {
                // This type represents a custom toolbox item implementation.
                Type itemType = tba.ToolboxItemType;
                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
            {
                // This type represents a default toolbox item.
                item = new ToolboxItem(possibleItem);
            }
        }
        if (item == null)
        {
            throw new ApplicationException("Unable to create a ToolboxItem " +
                "object from " + possibleItem.FullName + ".");
        }
    
        // Update the display name of the toolbox item and add the item to
        // the list.
        DisplayNameAttribute displayName =
            attribs[typeof(DisplayNameAttribute)] as DisplayNameAttribute;
        if (displayName != null && !displayName.IsDefaultAttribute())
        {
            item.DisplayName = displayName.DisplayName;
        }
    
        return item;
    }
    
    
    
  5. Implement the OnRefreshToolbox method to handle the ToolboxInitialized and ToolboxUpgraded events.

    The OnRefreshToolbox method uses the list of ToolboxItem objects that is contained in the ToolboxItemList member of the LoadToolboxMembersPackage class. It also does the following things:

    • Removes all ToolboxItem objects that are already present in the Toolbox category that is defined by the variable CategoryTab.

    • Adds new instances of all ToolboxItem objects that are listed in ToolboxItemList to the category tab for the VSProject.

    • Sets the Toolbox active tab to the category tab for the VSProject.

    
    void OnRefreshToolbox(object sender, EventArgs e)
    {
        // Add new instances of all ToolboxItems contained in ToolboxItemList.
        IToolboxService service =
            GetService(typeof(IToolboxService)) as IToolboxService;
        IVsToolbox toolbox = GetService(typeof(IVsToolbox)) as IVsToolbox;
    
        //Remove target tab and all controls under it.
        foreach (ToolboxItem oldItem in service.GetToolboxItems(CategoryTab))
        {
            service.RemoveToolboxItem(oldItem);
        }
        toolbox.RemoveTab(CategoryTab);
    
        foreach (ToolboxItem itemFromList in ToolboxItemList)
        {
            service.AddToolboxItem(itemFromList, CategoryTab);
        }
        service.SelectedCategory = CategoryTab;
    
        service.Refresh();
    }
    
    
    
    NoteNote

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

To implement a command to initialize the Toolbox

  • Change the menu item command handler method, MenuItemCallBack, as follows.

    1. Replace the existing implementation of MenuItemCallBack with the following code:

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

You can exercise the product of this walkthrough by using an instance of Visual Studio that is running in the experimental hive.

To exercise this walkthrough

  1. In Visual Studio, on the Build menu, click Rebuild Solution.

  2. Press F5 to start a second instance of Visual Studio in the experimental registry hive.

    For more information about how to use the experimental hive, see Experimental Instance of Visual Studio.

  3. Click the Tools menu.

    A command named Initialize LoadToolboxMembers VB or Initialize LoadToolboxMembers CS should appear at the top of the menu, together with an icon that has the numeral 1.

  4. Create a new Visual C# or Visual Basic Windows Forms application.

    A Form-based designer should appear.

  5. Drag one or both of the new controls in the LoadToolboxMembers Walkthrough VB or LoadToolboxMembers Walkthrough CS category of the Toolbox to the form in the designer.

    NoteNote

    If the Toolbox is not displayed, click Toolbox on the View menu. If the category tab for the VSPackage does not appear in the Toolbox, re-initialize the Toolbox by clicking Initialize LoadToolboxMembers VB or Initialize LoadToolboxMembers CS on the Tools menu.

  6. Build the windows application by clicking Rebuild Solution on the Build menu.

  7. Run the application by clicking either Start or Start with Debugging on the Debug menu.

  8. When the application runs, click one of the controls that you added to the application.

    A message box appears and displays either "Hello world from Company.LoadToolboxMembers.Control1" or "Hello world from Company.LoadToolboxMembers.Control2".

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 the following two functions:

    • The assignment of ToolboxItemAttribute to Control1 and Control2, which indicates that each is a toolbox control.

    • The argument to the ToolboxItemAttribute constructor, which indicates whether the default ToolboxItem or a custom class derived from ToolboxItem is used when the control is added to the Toolbox.

      The instance of ToolboxItemAttribute that is assigned to Control1 is created by using an argument of true, which indicates that it uses a default ToolboxItem class when it is added to the Toolbox.

      The instance of ToolboxItemAttribute that is assigned to Control2 is created by using the Type of a class that is derived from ToolboxItem, Control2_ToolboxItem.

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

    Embedding a bitmap in an assembly by setting its Build Action property to Embedded Resource puts the bitmap in the namespace of the assembly. Therefore, Control1.bmp can be referred to as Company.LoadToolboxMembers.Control1.bmp.

    ToolboxBitmapAttribute supports a constructor that takes this full path as an argument. For example, a ToolboxBitmapAttribute class could be applied to Control1 by using [ToolboxBitmap("Company.LoadToolboxMembers.Control1.bmp")].

    To support flexibility, this walkthrough uses a constructor that takes a Type class as the first argument to the ToolboxBitmapAttribute constructor. The namespace that is used to identify the bitmap file is obtained from the Type and inserted in front of the base name of the bitmap.

    Because the Type object that implements Package, LoadToolboxMembers, is in the Company.LoadToolboxMembers namespace [ToolboxBitmap(typeof(Control1), "Control1.bmp")] is equivalent to [ToolboxBitmap("Company.LoadToolboxMembers.Control1.bmp")].

  • DisplayNameAttribute specifies the name of the control in the Toolbox.

Registering a Toolbox Control Provider

Applying the ProvideToolboxItemsAttribute class to the class that implements Package affects the registry settings of the resulting VSPackage. For more information about the registry settings for a ToolboxItem provider, see Registering Toolbox Support Features.

The Visual Studio environment uses the version argument to the ProvideToolboxItemsAttribute constructor to manage the caching of VSPackages that provide items to the Toolbox. After 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 you want to modify the product of this walkthrough after you build it, make sure to change the version argument of the ProvideToolboxItemsAttribute constructor that is applied to AddToolboxItem. For example, [ProvideToolboxItems(1)] should be changed to [ProvideToolboxItems(2)]. If the version is not changed, then the Visual Studio environment does not load any modifications that are made.

In this walkthrough, the VSPackage is configured to provide only Toolbox controls that support the default Clipboard format. For a list of default Clipboard formats, see Toolbox (Visual Studio SDK). If you want to support other Clipboard formats, or decide not to support a default format, apply the attribute ProvideToolboxFormatAttribute to the LoadToolboxMembersPackage class. For more information about registering a Toolbox control provider, see Advanced Toolbox Control Development.

Adding Controls to the Toolbox

The functionality in CreateItemList emulates what is available in GetToolboxItems.

The CreateItemList method only examines non-abstract Type objects that implement the IComponent interfaces.

Using GetToolboxItems rather than CreateItemList would make the product of this walkthrough more robust.

You could also modify CreateItemList to use ParseToolboxResource to load controls into the Toolbox based on a text list that is embedded in the LoadToolboxMembers assembly.

Community Additions

ADD
Show:
© 2016 Microsoft