Collection Controls with Rich Design Time Support

Adding the Designer

Just before we add the designers, we'll clean up a couple of things. Firstly, we apply a BrowsableAttribute to the Buttons property, specifying False so the property doesn't appear in the propertygrid. Secondly, you may have noticed that when testing the control, adding buttons to the collection caused the buttons to appear in the component tray area of the form. This is normal since we are using components, but in this case we want to hide them. We do so using the DesignTimeVisibleAttribute class, again specifying False. Lastly, and this is a very minor detail, we use the ToolboxItemAttribute class to stop our ColourButton classes from appearing in their own right in the toolbox.

Now, we can go on to creating our designer. It will inherit from ControlDesigner. What we need it to do is handle clicks on the main control so that they select individual buttons, and listen for events on the design surface so we know when the user has selected something else. We also need to listen for an event fired when the user deletes one of the button components. Lastly, we need to override the AssociatedComponents property and simply pass it the Buttons collection, so it knows they go along with the control. It makes use of this information when the user copies the control to the clipboard and pastes it somewhere else.

This is a good time to tell you about the GetService function. The VS.NET IDE hosts a great deal of services, tied to a hierarchical chain of resources. They go up as high as the project level, and as low as a view of a particular source file (design view and code view). The services we are interested in are ISelectionService and IComponentChangeService. Every design view of a source file has these, and we can access them through the protected GetService method of the ComponentDesigner class, which ControlDesigner inherits from.

Designers have an Initialize function, which is called pretty much immediately after they are created. This function accepts a parameter which contains the object the designer is to provide support for. It is in this function that we will get a hold of ISelectionService and IComponentChangeService and wire up the events we need, which are SelectionChanged and ComponentRemoving. It is important to remember to unwire the events, which we do by overriding the Dispose method.

It's important to note that when writing designers, things can go wrong. They certainly have for me. Because designers are integrated quite tightly with the host environment, if you code something wrong or forget to clean up after yourself, things can really go awry. The kind of things it takes a restart of the IDE to fix. This is referred to as "playing nice with the other designers". Heaven forbid you should cause an exception to be thrown in designer code - debugging them is a real pain.

Anyway - here's the code to start off our designer. I've also tied the designer to the main control by using the DesignerAttribute class. This designer does nothing apart from wiring up the events we need and calling an internal function (with no code as yet) in the main control.

VB.NET

Friend Class CollectionControlDesigner
    Inherits ControlDesigner
    Private MyControl As CollectionControl
    Public Overrides Sub Initialize(ByVal component As System.ComponentModel.IComponent)
        MyBase.Initialize(component)
        'Record instance of control we're designing
        MyControl = DirectCast(component, CollectionControl)
        'Hook up events
        Dim s As ISelectionService = DirectCast(GetService(GetType(ISelectionService)), _
            ISelectionService)
        Dim c As IComponentChangeService = DirectCast(GetService(GetType _
        (IComponentChangeService)), IComponentChangeService)
        AddHandler s.SelectionChanged, AddressOf OnSelectionChanged
        AddHandler c.ComponentRemoving, AddressOf OnComponentRemoving
    End Sub
    Private Sub OnSelectionChanged(ByVal sender As Object, ByVal e As System.EventArgs)
        MyControl.OnSelectionChanged()
    End Sub
    Private Sub OnComponentRemoving(ByVal sender As Object, ByVal e As ComponentEventArgs)
    End Sub
    Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
        Dim s As ISelectionService = DirectCast(GetService(GetType(ISelectionService)), _
            ISelectionService)
        Dim c As IComponentChangeService = DirectCast(GetService(GetType _
        (IComponentChangeService)), IComponentChangeService)
        'Unhook events
        RemoveHandler s.SelectionChanged, AddressOf OnSelectionChanged
        RemoveHandler c.ComponentRemoving, AddressOf OnComponentRemoving
        MyBase.Dispose(disposing)
    End Sub
    Public Overrides ReadOnly Property AssociatedComponents() As _
    System.Collections.ICollection
        Get
            Return MyControl.Buttons
        End Get
    End Property
End Class

C#

internal class CollectionControlDesigner : ControlDesigner
{
    private CollectionControl MyControl;
    public override void Initialize(IComponent component)
    {
        base.Initialize(component);
        // Record instance of control we're designing
        MyControl = (CollectionControl) component;
        // Hook up events
        ISelectionService s = (ISelectionService) GetService(
        typeof(ISelectionService));
        IComponentChangeService c = (IComponentChangeService)
            GetService(typeof(IComponentChangeService));
        s.SelectionChanged += new EventHandler(OnSelectionChanged);
        c.ComponentRemoving += new ComponentEventHandler(
        OnComponentRemoving);
    }
    private void OnSelectionChanged(object sender, System.EventArgs e)
    {
        MyControl.OnSelectionChanged();
    }
    private void OnComponentRemoving(object sender, ComponentEventArgs e)
    {
    }
    protected override void Dispose(bool disposing)
    {
        ISelectionService s = (ISelectionService) GetService(
        typeof(ISelectionService));
        IComponentChangeService c = (IComponentChangeService)
            GetService(typeof(IComponentChangeService));
        // Unhook events
        s.SelectionChanged -= new EventHandler(OnSelectionChanged);
        c.ComponentRemoving -= new ComponentEventHandler(
        OnComponentRemoving);
        base.Dispose(disposing);
    }
    public override System.Collections.ICollection AssociatedComponents
    {
        get
        {
            return MyControl.Buttons;
        }
    }
}

You might also like...

Comments

Contribute

Why not write for us? Or you could submit an event or a user group in your area. Alternatively just tell us what you think!

Our tools

We've got automatic conversion tools to convert C# to VB.NET, VB.NET to C#. Also you can compress javascript and compress css and generate sql connection strings.

“Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.” - Brian Kernighan