Introduction
What are Collection Controls?
Actually it's a term I just made up, but when I say it I am referring to those user interface
controls that present themselves as lists. The more obvious examples of these are listboxes,
listviews and treeviews. Less obvious are things like toolbars. They all maintain a collection
of objects which are displayed.
In some cases, these objects in the collection have collections of subitems themselves. An example
of this is the listview, where each item can have subitems when the control is in Report view mode.
When you introduce a collection to your control, your job suddenly gets a lot harder. You end
up having to write at least three more classes than you would if you were just developing a simple
control with properties, such as a Button.
Requirements of a Collection Control
When writing one of these controls, it is usual to spend a little more time in code when you
are still writing the object model. You have to define the property on the main control used to
access the collection. You have to write the class which will represent each individual item (for
example, the ListViewItem class). You have to write the class which will act as a collection for
your subitems. And that's just to get it functional.
To add design time support, you have to write a class to act as a Type Converter for your
subitems. When the user has populated your control at design time, the code serializers go through
each object in your collection and use this converter class to inspect it and give it the best
way of recreating it (i.e. which constructor to use).
Although this type of control has subitems, they are typically not responsible for drawing
themselves. They do not actually have windows of their own, instead it is up to the parent control
to calculate their positions and draw them.
Rich Design Time Support
Is another term I've invented. I use it to refer to doing that little bit of extra work to
really make your control easy to work with at design time. I have a couple of my own controls
posted, and neither of them use the Collection Editor which is the standard way of modifying
collections at design time. Instead, they use a system of designer verbs and selections to make
the changes visually.
To add rich design time support, you will likely be writing a designer for the main control,
and a designer for the subitems. It's in these designers and in extensions of the code in your
control that you will add the necessary code.
One of the requirements is that your subitems are selectable and modifiable with the usual
property grid control. To enable this, every subitem must be present on the design surface. This
means that it has to implement the IComponent interface, and the easiest way to do that is to
derive them from Component.
The beauty of rich design time support is the user being able to select each subitem just by
clicking on it. It's not a trivial task, and as far as the designer is aware (by default) you're
just clicking on part of the main control. It's up to our design time code to use the interfaces
provided by the host environment to select the subitem the user has clicked on and draw it as such.
We also need to listen to selection change events from the host environment, so that when the
user selects a different control, we are notified and can redraw.
Designing the Object Model
For this article we will create a control which is laid out like a toolbar. All the "buttons"
will have a Colour property which will be the only way of controlling their appearance. The buttons
will be selectable and modifiable at design time.
The main control will feature only one custom property, which we will call "Buttons". We will
hide this property at design time using the BrowsableAttribute class, because we want to use our
own logic to add and remove them, rather than the collection editor.
Our subitems, which we will call ColourButtons, will have just one property - Colour. When a
button is selected, a thick border will be drawn around it. I know this is a pretty useless
control we're developing, but you would use exactly the same method to develop any advanced
control, such as a toolbar or a list of some kind.
One of the most important things to get right with a control like this is separating the layout
logic from the drawing logic. Internally, the control needs to keep a list of rectangles
maintained, one for each button. We will implement a CalculateLayout function that loops through
the collection and generates the rectangles. This function will be called whenever a button is
added to or removed from the collection, or the main control is resized.
The drawing code is much easier if all the rectangles are pre-calculated like this. You should
never calculate positions in drawing code, because it just isn't necessary. Drawing is required far
more of the time than calculating positions.