Introduction
While using Visual Studio .NET, we rarely think about the things which makes it a WYSIWYG editor. I am targeting this series to have a working knowledge of the rich capabilities provided by Microsoft .NET 2.0 which enables us to make our own Layout Designers.
There is one more article available on CodeProject related to DesignSurface extension, which can be found here.
Background
I recently got a chance to work on a layout editor for a Windows application. The concept was simple, of having a GUI tool to create form layouts and save them in XML format for further processing. The interesting part here was how to get the VS.NET kind of feasibility and friendlessness while writing my own editor. The Microsoft .NET framework saved my day! Otherwise, I would have to have written every single thing from scratch. I am planning to publish an article every week so, please be patient for the next one.
Microsoft .NET and Designer Support
Microsoft .NET provides the System.Design.dll assembly. This assembly has some important classes which enables users to write their own layout designers. In this series, we are going to discuss about these one by one as we keep on adding more and more functionality to our layout designer.
The DesignSurface Class
Now, let's talk about the DesignSurface
class. This is the base container for your layout designer. As we need a surface on which we can design our own form, the DesignSurface class enables the user to have his own container. Following is the definition of DesignSurface
(extracted from meta data):
public class DesignSurface : IDisposable, IServiceProvider
{
public DesignSurface();
public DesignSurface(IServiceProvider parentProvider);
public DesignSurface(Type rootComponentType);
public DesignSurface(IServiceProvider parentProvider, Type rootComponentType);
.
.
.
.
.
For more information about this class, please refer to the MSDN documentation.
Following is a small code snippet for instantiating the DesignSurface
class. This code creates a surface and adds it on the main form of the sample application.
namespace L1
{
public partial class DemoForm : Form
{
private DesignSurface m_designSurface;
public DemoForm()
{
InitializeComponent();
m_designSurface = new DesignSurface(typeof(Form));
}
private void DemoForm_Load(object sender, EventArgs e)
{
Control designView = m_designSurface.View as Control;
designView.Dock = DockStyle.Fill;
this.Controls.Add(designView);
}
}
}
The code:
m_designSurface = new DesignSurface(typeof(Form));
Adds the DesignSurface
by creating the base component, that is the Form
. So, here, we are designing a form inside our application.
Services Provided by DesignSurface
We got a surface for designing the components and, for accessing the controls over this surface, we should have services. These services are provided to help us in accessing individual components on the surface, for getting events and other information like information about selected components and so on. There are two types of these services: Replaceable and Non-Replicable.
Replaceable Services
We can call these services as replaceable because we are able to plug-in our own implementations and replace these by our own components. Following are the replaceable services available. For replacing these services, we have to subclass the DesignSurface
class as, in the constructor, it adds the default services. These services are accessible through the ServiceContainer
property and can be replaced by overriding it and adding our own implementation.
IExtenderProviderService
IExtenderListService
ITypeDescriptorFilterService
ISelectionService
IReferenceService
DesignSurface
DesignerOptionService
Non-Replaceable Services
These services can not be replaced and are provided by default. Following is the list:
IComponentChangeService
IDesignerHost
IContainer
IServiceContainer
For our small demo application, we are going to make use of this service. The IDesignerHost
service is used to access/manage the components that are available over the design surface. This is the non-replaceable service, and for getting the reference to the default implementation, we have to call the GetService()
method of the design surface. Following is the code snippet:
m_designSurface = new DesignSurface(typeof(Form));
IDesignerHost designerHost =
m_designSurface.GetService(typeof(IDesignerHost)) as IDesignerHost;
Form templateForm = designerHost.RootComponent as Form;
templateForm.Text = "Demo Form";
This code creates a DesignSurface
, and by getting the reference to the IDesignerHost
, we are able to access the components that are available on the surface. Here, we have the RootComponent
which is a Form
. Having a reference to the root component, we can modify its properties, and here, we are setting the Text
property to "Demo Form".
As the name suggests, service container is the container with services registered. The IServiceContainer
interface provides the mechanism to add/modify/remove the registered services. For drawing controls on the design template, we have to override one service and that is the toolbox. For registering our own toolbox implementation, we need to have a reference to the service container, and it can be retrieved using the GetService()
method of DesignSurface
.
IServiceContainer serviceContainer =
m_designSurface.GetService(typeof(IServiceContainer)) as IServiceContainer;
We got the form and we can register new services now. We will now look at how to add components on this form. For ease of use, .NET framework provides an interface: IToolboxService
interface. For ease of development, the .NET framework has the ToolboxService
abstract class which contains an implementation for the important methods. Now, we are going to create our own toolbox service named DemoToolboxService
. This is more or less a dumb class for the basic implementation of the toolbox. We need it to get our small application working, and will discuss it in detail in my second article.
namespace L1
{
class DemoToolboxService : ToolboxService
{
CategoryNameCollection m_categoryNameCollection;
protected override CategoryNameCollection CategoryNames
{
get
{
if (m_categoryNameCollection == null)
m_categoryNameCollection =
new CategoryNameCollection(new string[] { "Controls" });
return m_categoryNameCollection;
}
}
protected override System.Collections.IList
GetItemContainers(string categoryName)
{
throw new Exception("The method or operation is not implemented.");
}
protected override System.Collections.IList GetItemContainers()
{
throw new Exception("The method or operation is not implemented.");
}
protected override void Refresh()
{
throw new Exception("The method or operation is not implemented.");
}
protected override string SelectedCategory
{
get
{
return "Controls";
}
set
{
}
}
protected override ToolboxItemContainer SelectedItemContainer
{
get
{
return new ToolboxItemContainer(new ToolboxItem(typeof(Button)));
}
set
{
}
}
}
}
In DemoToolboxService
, we have only one category of controls, and that is "Controls". The user is not able to select any thing, and can draw only buttons as, the toolbox is not yet implemented.
Goof Ups!
While using this application, did you note??? Where is the name on the button? How do we provide it? Try to find it! Or wait until the next article.