
Abstract
This article describes DesignSurface
/DesignSurfaceManager
inherited classes which add to the .NET classes some design facilities (TabOrder
, UndoEngine
, SnapLines
/Grid
alignment, ContextMenu
with Cut/Copy/Paste commands, Toolbox
, and Propertygrid
). These classes are hosted inside DLL assemblies and are ready to use inside any .NET solution.
Introduction
Some people asked me how they could use the DesignSurfaceExt
class together with a DesignSurfaceManager
class, and for sample code demonstrating how to implement drag and drop operations on the DesignSurface
; therefore, I resolved to write a second article explaining these issues. My previous article is available here: DesignSurfaceExt, and introduces you to the DesignSurfaceExt
class which extends the .NET DesignSurface
class. I extended this class further, improving its design facilities (TabOrder
, UndoEngineSnapLines
/Grid
alignment) with a drag and drop facility and adorning the DesignSurface
with a Toolbox
and a Propertygrid
-like User Control. The code is developed like Chinese boxes: each project encapsulates another one, and each one has a demo form which uses the classes to implement what can be perceived as a Form Designer. Finally, I wrote another User Control to host all these design facilities (exposed via an Interface). Feel free to use the code and to modify it as you like for building your own Form Designer.
Background
It is necessary to be familiar with C# programming. It'd useful to know the concepts used by the .NET designer and to read my previous CodeProject article: DesignSurfaceExt.
How to Use DesignSurfaceExt and DesignSurfaceManagerExt
Inside the attached code, I have included lots of demo projects to show how to use the classes, so I shall spend no more time talking about the usage.
The Nuts and Bolts of the Classes
The classes are structured as Chinese boxes: each class adds design functionalities over another one. From the ground up:
DesignSurfaceExt
extends the .NET DesignSurface
, adding the following facilities: TabOrder
, UndoEngine
, Cut/Copy/Paste/Delete commands.
//- DesignSurfaceExt
//- |
//- +--|DesignSurface|
//- |
//- +--|TabOrder|
//- |
//- +--|UndoEngine|
//- |
//- +--|Cut/Copy/Paste/Delete commands|
Please refer to the project DemoConsoleForDesignSurfaceExt to see a "Designer
", based on DesignSurfaceExt
, in action.
DesignSurfaceExt2
extends the previous DesignSurfaceExt
, adding Toolbox
mechanisms and ContextMenu
with Cut/Copy/Paste/Delete commands.
//- DesignSurfaceExt2
//- |
//- +--|Toolbox|
//- |
//- +--|ContextMenu|
//- |
//- +--|DesignSurfaceExt|
//- |
//- +--|DesignSurface|
//- |
//- +--|TabOrder|
//- |
//- +--|UndoEngine|
//- |
//- +--|Cut/Copy/Paste/Delete commands|
Please refer to the project "DemoConsoleForDesignSurfaceExt2" to see a "Designer
", based on DesignSurfaceExt2
, in action.
DesignSurfaceManagerExt
manages a collection of DesignSurfaceExt2
instances; it adds a PropertyGrid
to each DesignSurfaceExt2
instance:
//- DesignSurfaceExt2
//- |
//- +--|PropertyGridHost|
//- |
//- +--|Toolbox|
//- |
//- +--|ContextMenu|
//- |
//- +--|DesignSurfaceExt|
//- |
//- +--|DesignSurface|
//- |
//- +--|TabOrder|
//- |
//- +--|UndoEngine|
//- |
//- +--|Cut/Copy/Paste/Delete commands|
Please refer to the project "DemoConsoleForDesignSurfaceManagerExt" to see a "Designer
", based on DesignSurfaceManagerExt
, in action.
Finally the pDesigner
- p(ico) Designer User Control handles all the major aspects of what is perceived as a "Designer
" and encompasses the code to manage the above classes.
Please refer to the project "DemoConsoleForpDesigner" to see a "Form Designer", based on pDesigner
, in action.
Points of Interest
Every class useful for designing has an accompanying interface. You can call the design facilities of the class through this interface, but if you prefer, you can ignore them and straightaway use the classes.
About DesignSurfaceExt
, please see my previous article: DesignSurfaceExt.
About DesignSurfaceExt2
: very often, what the user wants for a designing environment is a toolbox. This is usually a nice little list of controls which tell you what kind of controls you can either "point and click" or "drag and drop" on to your surface. The .NET design environment is expressly built to manage these aspects via the ItoolboxService
interface. When you implement this interface, you can code most toolbox functionality. But, the key point is the implementation of IToolboxService.DeserializeToolboxItem()
together with IToolboxService.SerializeToolboxItem()
and IToolboxService.SetSelectedToolboxItem()
, together with IToolboxService.GetToolboxItems()
. These are the methods called whenever you deploy a control on your brand new design surface. I implemented what is strictly necessary in order to have these functionalities inside DesignSurfaceExt2
(if you launch the demo for this class, you will see that the controls can be sited with both "point and click" and "drag and drop" mechanisms, while if you comment IDesignSurfaceExt2.EnableDragandDrop()
, you will lose the "drag and drop" mechanism).
Now, it's time to talk about DesignSurfaceManagerExt
. This class is used to extend the .NET 2.0 class DesignSurfaceManager
. MSDN states: "Using DesignSurfaceManager
is optional, but it is recommended if you intend to have several designer windows." What is this class used for? Basically, it's a central point to manage a collection of DesignSurface
objects. Since it is a container of DesignSurface
objects, we can use it to contain every object which is a DesignSurface
, that is to say, every object which is derived from DesignSurface
and therefore also DesignSurfaceManagerExt
instances. To do that, we override the DesignSurface CreateDesignSurfaceCore( IServiceProvider parentProvider)
method. You can see that the overridden method simply returns a new instance of the DesignSurfaceExt2
class. Inside this method, you can do whatever you want. Anyway, the core concept of DesignSurfaceManagerExt
is that "it provides common services that handle event routing between designers [MSDN]". So, pay attention to the private method named Init()
inside DesignSurfaceManagerExt
: inside the method, I add to the services collection of the DesignSurfaceManagerExt
class, my PropertyGrid
user-control (actually the PropertyGrid
and its ComboBox
), virtually letting it be available to each DesignSurface
which is created via DesignSurfaceManagerExt
. How? See the DesignSurfaceExt2 CreateDesignSurfaceExt2()
public method where, after creating the DesignSurface
, I put the code necessary to maintain in sync the PropertyGrid
with whatever is selected in the current surface.
Another point of interest is the following. MSDN states: "The ActiveDesignSurface
property should be set by the designer's user interface whenever a designer becomes the active window". What I understand by this phrase is that it's supposed that I have to implement by myself an Observer Pattern of UI event "the current surface is changed", because only my UI knows when the current surface is changed! Again, inside the Init()
method, you can see how the ActiveDesignSurfaceChanged
event is managed with a delegate which updates the PropertyGrid
(and the ComboBox
) by hand.
Finally, some more words about the pDesigner
User Control (it stands for picoDesigner) and its interface IpDesigner
. I coded them because the final user, when thinking about a Form Designer, expects to find something like this:
//- +-------------+-----------------------------+-----------+
//- |toolboxItem1 | ____ ____ ____ | |
//- |toolboxItem2 ||____|____|____|___________ +-----------+
//- |toolboxItem3 || | | | |
//- | || | | | |
//- | TOOLBOX || DESIGNSURFACES | | PROPERTY |
//- | || | | GRID |
//- | ||__________________________| | | |
//- +-------------+-----------------------------+-----------+
That is to say: a toolbox, a collection of DesignSurface
s which can be switched/added/deleted, and a PropertyGrid
which tells her/him the properties of the control currently selected. Good, so here is the interface:
public interface IpDesigner {
ListBox Toolbox { get; set; } TabControl TabControlHostingDesignSurfaces { get; } PropertyGridHost PropertyGridHost { get; }
DesignSurfaceExt2 ActiveDesignSurface { get; }
DesignSurfaceExt2 AddDesignSurface<TT>(int startingFormWidth,
int startingFormHeight, AlignmentModeEnum alignmentMode,
Size gridSize ) where TT : Control;
void RemoveDesignSurface ( DesignSurfaceExt2 activeSurface );
void UndoOnDesignSurface();
void RedoOnDesignSurface();
void CutOnDesignSurface();
void CopyOnDesignSurface();
void PasteOnDesignSurface();
void DeleteOnDesignSurface();
void SwitchTabOrder();
}
The User Control pDesigner
encompasses all the functionalities said above and the management of the collection of DesignSurface
s via a TabControl
used as a concrete container for the surfaces. Here you can see how the TabControl
SelectedIndexChanged
event is used to propagate the syncing of the PropertyGrid
.
The last point which is worthy to be mentioned concerns the service IComponentChangeService
. This service is marked as "Non replaceable service", and you can use it to monitor the changes which happen inside the surface; i mean, when a control is added/deleted or simply changed. Adding a handler to the events fired by this service lets you set a fine grained behaviour of your designer according to what happens inside the DesignSurface
.
Conclusion
As usual, feel free to use the code and to modify it as you like (and let me know your impressions)!
P.S.: Some of you complained that I posted a bunch of code but there is no accompanying article (by the way, Marc: your comment "very useful" is really a big reward for me). I agree with you, therefore with this update, I dug a little more into the source code to explain some issues ;)
History
- 19 February 2010 - First submission of the article/code.
- 22 February 2010 - Updated the "Points of Interest" section of the article.