Library tutorials & articles
Hosting Windows Forms Designers
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 they're pretty trivial and for completeness should definitely be included. We'll 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. That's all we will put in this class. We instantiate it in the designer host's 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 isn't 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.
internal class ExtenderServices : IExtenderListService, IExtenderProviderService
{
ArrayList extenderProviders = new ArrayList();
public ArrayList ExtenderProviders
{
get
{
return extenderProviders;
}
}
public ExtenderServices()
{
}
public IExtenderProvider[] GetExtenderProviders()
{
IExtenderProvider[] e = new IExtenderProvider[extenderProviders.Count];
extenderProviders.CopyTo(e, 0);
return e;
}
public void RemoveExtenderProvider(System.ComponentModel.IExtenderProvider provider)
{
extenderProviders.Remove(provider);
}
public void AddExtenderProvider(System.ComponentModel.IExtenderProvider provider)
{
extenderProviders.Add(provider);
}
}
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 simple 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.
The behaviour of this class is copied from that of Visual Studio so the shift and control keys work in the same way. Clicking on an already selected component makes it the primary selection.
Implementing ITypeDescriptorFilterService
This is an easy one. When the designers add/remove/shadow properties on the components they're designing, they do so because this method tells them to. When the propertygrid requests info on the members of the component, it queries for this interface on the component's ServiceProvider. If it finds it, it uses the simple members on the interface to alter any of the properties before they are displayed.
All we have do to when we implement this interface is (for each of the three methods) get the designer for the component being passed (easy, since we've already implemented the GetDesigner method on IDesignerHost) and call the designer's PreFilterProperties and PostFilterProperties (or equivalent) methods with the attributes we were passed.
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 let's consider the example of Visual Studio again.
The INameCreationService interface is used by the code we've 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 you're writing in VB, and "textBox1" if you're writing in C#. That's 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.
It's 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 you're writing in VB. We will just increment an integer counter until we find a name that isn't already in use.
The IsValidName function ensures that a name is valid. For this example we'll 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 won't explain what all the methods do here.
Implementing IToolboxService and IMenuCommandService
This is actually a fairly complicated interface to implement. It is the gateway between the toolbox user interface in the development environment and the designers. The designers constantly query the toolbox when the cursor is over over them to get feedback about the selected control.
Properly implementing IToolboxService is beyond the scope of this article so I will write only a skeleton implementation. It will be enough so that you can select a tool (or the pointer) and create and manipulate components on the design surface.
IMenuCommandService is looked for by some controls and components and they don't fail gracefully when it isn't found. This interface is responsible for managing standard menu commands, which the designers add to it. We will use it to execute the Delete command that has been added to it when the user hits the delete key. This is the correct way of letting the user delete components from the design surface.
Related articles
Related discussion
-
Binary Studio | software development outsourcing Ukraine
by shane124 (4 replies)
-
Chart insertation in a windows form...
by pdhanik (1 replies)
-
Point of Sale Developers: Hardware & C# SDK
by ManiGovindan (7 replies)
-
help with the remote frame buffer protocol from real VNC
by poison (0 replies)
-
Need help making a complete program editable, C# or .net I think
by davelee (1 replies)
Related podcasts
-
A Practical Look at Silverlight 2 Part 1
Now that Silverlight 2 is at the Olympics and making a big splash, we wanted to explore this fascinating technology more. Microsoft Silverlight 2 is a cross-browser, cross-platform, and cross-device plug-in for delivering the next generation of .NET based media experiences and rich interactive ap...
Events coming up
-
Dec
9
GL.net Group Meeting - December 2009
Gloucester, United Kingdom
The beginning of this year holiday season will belong to mocks. Ronnie and Stephen will take us for a tour around exciting world of unit testing.
How would I go about saving a form that I have designed?
!--removed tag-->I have (am still) modifying this code to create a stand-alone Xaml generator for forms. The Xaml is structured for direct consumption by Windows Presentation Foundation. This was a great starting place.
In the course of my modifications and testing, I found a couple of bugs in the code which you may wish to correct in your source.
The Name property is listed twice in the property grid.
If you add a control (i.e. a Button), then change the name from the default (Button1) to anything else, then add another button, you get an unhandled exception. This does not occur if all like controls are added first then the names are modified. The cause is when the control is first added to the designer, it is added to the DesignerHost components collection using the default (Button1) control name as a key. When the Name property is updated, the components collection is not. When adding a second like control, the DesignerHost attempts to add it using the default name as well. However, since the first control was renamed, the default name of the second control is the same as it was for the first (Button1). An unhandled exception is thrown when the second control is added because the components collection already contains a control with the same key value.
I corrected this by attaching a handler to the PropertyValueChanged event of the PropertyGrid which removes the original entry for the control and readds it with the new name as the key whenever the Name property value is changed.
how to change Designer for design PocketPC components?
htx
v!tek
Hi Tim, great article. I am currently creating a designer in a similar manner to your article, used to create pdf files. I am using it to add only textboxes and pictures, and can save the data to a database. However I need to bring back the objects and add them to the form programmatically so the user can edit the objects (change the positions etc) Do you know how (or explain how) to add items to the designer surface programmatcally without using the drag and drop from the toolbox
Thanks
John Harry
Tim:
Thanks for your nice article.
How can I get the location of a control in the design surface. I want to show the coordinates when the user moves the control, but I can't figure how!
Did you figure it out how to persist the designer content? I would appreciate any help on this
Thanks
Sgirase
Hi!
That article is really great. It gave me useful hints to start my application in perfectmanner , which looks like an .Net IDE . but can you please tell me how to remove a control from the designer form ....
Hello Tim,
Thank you very much for this excellent article.
We have developed a graphical macro language in C# for our customers and one of the remaining hurdles that we have is the ability to edit the location of controls on a runtime form.
When the customer wants to edit our form, we scan our macro and generate controls on the form based upon the macro content.
We would then like to display that form with the controls where the customer can position and size those controls like you can in this Designer example. We would then iterate through the list of controls and remember their locations and sizes within the macro.
Can you please lead us in the right direction? We have been searching, trying, and reading many examples to no avail. It appears that you possess this knowledge and could help us. We are not inexperienced developers, I personally have been a software developer for 22 years in many languages.
For example, how would you modify this example so that controls can be placed on the form with out the user clicking on the designer surface?
Thank you very much for your reply,
Rick Wirch
Good question ... I am actually tasked with building a Web Forms Designer. I can load ASP.NET controls into my toolbox but of course can't drop those controls onto a Windows forms. Can somebody help me!!
Much appreciated -
Jon
Is it possible to use a treeview for that?
That article is really great.
It gave me the needed hints to step into this subject as deep as I wanted to. As i'm currently trying tom write a lightweight IDE for customumizing my own applications, i need to know, how the content auf the designer could be persisted and reloaded.
Is there any standard-mechanism for that, or will I have to implement my own handlers?
Any help would be appreciated!
Its possible to do something similar to your example using web forms designer?
This article is simply great. I wish to see more like this one.
Arthur
This thread is for discussions of Hosting Windows Forms Designers.