Library tutorials & articles

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;
        }
    }
}

Comments

  1. 30 Jan 2009 at 17:58
    Nice article, I'm writing a toolstrip type control and this helped me figure out how to remove the child component on deletion. I liked how you selected components in the control on mouse down, however, I had done it differently. Because my control was written in the compact framework, with the designer code being in the full framework. I couldn't select my component in the control code itself by checking for design mode. Therefore, I handled wndproc: protected override void WndProc(ref System.Windows.Forms.Message m) { // left mouse down if (m.Msg == WM_LBUTTONDOWN) { int data = m.LParam.ToInt32(); int y = (int)(data & 0xFFFF0000) >> 16; int x = (int)(data & 0x0000FFFF); Point pt = new Point(x, y); OptionStrip strip = (Control as OptionStrip); clickItem = strip.GetItemAt(pt); if (clickItem != null) return; } else if (m.Msg == WM_LBUTTONUP && clickItem != null) { int data = m.LParam.ToInt32(); int y = (int)(data & 0xFFFF0000) >> 16; int x = (int)(data & 0x0000FFFF); Point pt = new Point(x, y); OptionStrip strip = (Control as OptionStrip); if (strip.GetItemAt(pt).Equals(clickItem)) { ISelectionService ss = (ISelectionService)GetService(typeof(ISelectionService)); ArrayList list = new ArrayList(); list.Add(clickItem); ss.SetSelectedComponents(list, SelectionTypes.Primary); } clickItem = null; return; } I think your solution is simpler, and probably more solid, but this is necessary as the ISelectionService does not exist in the compact framework. However, it does seperate design time code.
  2. 23 May 2007 at 19:35

    GREAT ARTICLE¡¡¡  thank you very, very much, now i can finish my own control. 

    I could not save my own custom class object, NOW I CAN.

  3. 19 Mar 2007 at 09:08
    I've tried to compile the code I've downloaded but at run-time (placed in a form on another project) I don't see the buttons I've created at deign-time..
  4. 17 Jan 2007 at 19:46
    Is it safe to assume value (of complex type) of a property has instance descriptor converter for every component/control you dropped on design surface.

    Regards
    Phani




  5. 01 Jan 1999 at 00:00

    This thread is for discussions of Collection Controls with Rich Design Time Support.

Leave a comment

Sign in or Join us (it's free).

Tim Dawson
AddThis

Related discussion

Related podcasts

  • More jQuery in ASP.NET

    In this episode Chris Brandsma, Rick Strahl, Dave Ward, Bertrand Le Roy, and Scott Koon conclude their discussion of Microsoft's jQuery in ASP.NET announcement1.This episode of the Alt.NET Podcast is brought to you by LLBLGen Pro, the most mature O/R mapper and code generator out there.Are ...

Events coming up

  • Nov 18

    15 Minutes of Fame

    Dresher, United States

    This is a yearly tradition. We select 10 of the favorite speakers from monthly meetings, code camps, and hands on labs. Each one does a 15 minute talk on their favorite .NET technology. This is our 10th anniversary so we plan a gala event with special prizes and refreshments.

We'd love to hear what you think! Submit ideas or give us feedback