Hosting Windows Forms Designers, Part 2

divil

Ultimate Contributor
Joined
Nov 17, 2002
Location
England
User Rank
*Guru*
We have to remove the component from our list of extender providers if necessary, then dispose of and remove the designer associated with it. After that, we set the components Site to null and thats all thats needed.

Implementing ISite

Our ISite implementation, called DesignSite, is going to be a poweful little beast that also implements IDictionaryService, the interface that for some reason some components use instead of using their own internal dictionaries. There isnt really much point going in to any of the stuff in this class in detail because its mostly just template code except in the setter for the Name property.
The setter needs to check there isnt already a component with the name passed in the container. If there isnt, it can then proceed. To play friendly with the rest of the design environment, it must cause the IComponentChangeService implementation to raise the OnComponentChanging, OnComponentRename and OnComponentChanged events in that order while the change is made.

Implementing IComponentChangeService

This is an easy one. All this interface has is seven events and two methods which raise two of the events. All we do is add code for those two methods, and add another, internal method which is needed for our DesignSite class to raise the ComponentRename event. Although implementing this interface is easy, whats hard is working out where else in our implemented code to raise the events on it.

Implementing IDesignerHost

This should really have been the class we started with, but IDesignerHost, IContainer, ISite and IComponentChangeService all depend on each other so much it makes sense to write about them in the order its necessary to code them in. Ill cover the implementations of the members on IDesignerHost as I write them as best I can.

Activate - This method is called to activate the root component. Our implementation will grab ISelectionService and set the root component as the primary selection.
CreateComponent - This method is used by designers to have the host environment create an instance of a class that is to be situated on the design surface. Calling it is functionally identical to creating instance themselves then adding it to the container.
DestroyComponent - This is called to remove a component from the design surface and dispose of it properly. This method is also responsible for using IComponentChangeService to let others know what is going on as the component is being removed.
CreateTransaction - Creates an instance of our template DesignerTransaction class (Ill come on to that in a minute) and adds it to the stack of transactions.
GetDesigner - We just use our designers hashtable to return the designer associated with the passed component.
GetType - We check if there is an ITypeResolutionService available, and if so use it. If not, return Type.GetType() instead.
Container - Just return the instance of the class since it implements IContainer itself.
InTransaction - Return true if the number of transactions in our stack is greater than zero.
Loading - For this example we are not concerned with loading hosts, so just return false.
RootComponent - Return rootComponent, the first component added to our container.
RootComponentClassName - Return the name of the class the first component added to our container was created from.
TransactionDescription - Return the name of the transaction at the top of our stack, if there is one.
Designer Transactions

Every small change to a component should go through the IComponentChangeService, but when lots of small changes need to be wrapped up logically, thats where designer transactions come in. If we were writing a full-scale implementation of a design environment, we would keep track of these for undo/redo support.
There is no interface to be implemented here, we just need to inherit the abstract class DesignerTransaction with our own. All ours will do is make sure the appropriate events are raised by the host when the transaction is committed or cancelled.

Extender Services

The host environment needs to maintain a list of extender providers, and provide one of its own. You could probably get away without implementing these but theyre pretty trivial and for completeness should definitely be included. Well make a class called ExtenderServices which will implement IExtenderListService and IExtenderProviderService.
IExtenderListService has just one method, GetExtenderProviders. This is easily implemented with a simple ArrayList.
IExtenderProviderService exposes the same as IExtenderListService only it includes methods to add and remove the extenderproviders to and from the list. Thats all we will put in this class. We instantiate it in the designer hosts constructor and add its services to the servicecontainer along with the rest.
Our DesignerHost class has to implement IExtenderProvider itself. We need to do this because we want to have that (name) entry in the propertygrid, and for that, code has to be written. That said, there isnt much to it - we just want to extend all objects of type IComponent, with a new property "name" which is parenthesized. We have already written to code to wrap getting and setting component names in our DesignSite class, so the rest is simple.

Implementing ISelectionService

While this service is fairly simple in what it does, the implementation is important to get right. Selected components are kept track of internally with a simply ArrayList, and the only method of note is the SetSelectedComponents method. This method has to actually look at the state of the control and shift keys to cater for the various standard selecting operations.
This class also subscribes to the IComponentChangeService, because when a component is removed the selectionservice needs to know, if it is selected. Once the deleted component has been removed from the list of selected components, if there are none left the root component is selected.

Implementing INameCreationService

This service is implemented on a different level to the rest of the services we have added so far. This example is very simple so it will actually end up being added to the same servicecontainer as the rest, but lets consider the example of Visual Studio again.
The INameCreationService interface is used by the code weve written already to come up with a name for a component being added to the design container, if none was supplied. When you add a textbox to a form in Visual Studio, it gets the name "TextBox1" if youre writing in VB, and "textBox1" if youre writing in C#. Thats because this service is implemented on a project level. Remember how servicecontainers are linked together in a tree? The request is made to the servicecontainer at the document view level but cascades up until it finds one.
Its a very simple interface to implement. You have the CreateName function, in which we will use the same naming algorithm as Visual Studio uses when youre writing in VB. We will just increment an integer counter until we find a name that isnt already in use.
The IsValidName function ensures that a name is valid. For this example well assume the name is going to be persisted to code at some point, and apply standard rules such as no spaces, and only alphanumeric characters are allowed. The ValidateName function just calls IsValidName, and if that returns false it throws an exception.

Implementing IUIService

This service is implemented only once and added to the top-level service container. It is how designers show messages, errors, popups and other windows. The documentation on this interface is pretty good so I wont explain what all the methods do here.

Making it Work

Thats about it for the DesignerHost and its related classes. The way weve written it, all it needs to be instantiated is a ServiceContainer instance. The framework provides a simple implementation of this, so well just use that. We want a very simple design surface so well just make a form with one side reserved for a propertygrid.
Now, how do we actually put our DesignerHost to use? It comes back to the Root Designer we discussed earlier. The key method is has is GetView, which returns a Control that can be added to any other form or control. This control is what you see when you look at a form in design view; it has a white background, and shows the form in design mode sitting in the top left.
Once we have created our DesignerHost, we (the host application) needs to subscribe to the SelectionChanged event of the ISelectionService interface that we already implemented. This is so that we can display the properties of the selected objects in our propertygrid.
We will use the following code to instantiate a form, put it in to design mode by adding it to our host, get its design view and add that view to the form:

Conclusion

Reading over the code should fill in any gaps that I missed out while writing this. Hopefully this article will encourage more people to make use of the designer architecture in their own applications.
I have provided a C# example project that covers everything in this article.

Download Solution from the full article on my website.
 
Top