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