Introduction
The other day, I was surfing through the web for third party UIs for WPF. I was amused to see a vendor who claimed to have a complete design time support for their components. Having worked as a UI developer, I know the pain of navigating through the XAML to configure the UIs. Ribbon controls and docking controls are complex enough to cause you headaches. There comes the designer extensions for resolution.
A week later after reading many documentations, walkthroughs and experimenting with some simple little things, it turned out to be simple to bring things right on the designer. So at this moment, I am ready to get things done for our control library, library? Oops!! There are plenty of controls for the simplest need to the complex layouts requirements, are we going to write designer extender for every control? Nope, there must be a better way, and it turned out to be DesignSurface
- the theme of this article.
So What is a DesignSurface?
And you know, I am a Windows Forms developer jump started into WPF. In my previous article, I tried to bring animation framework back to Windows Forms. This article brings the concept of ControlDesiger and DesignerVerbs together into DesignSurface
to bring your WPF designer alive.
DesignSurface
is an adorner capable of auto-generating property editors for the designer verbs collection you have in your designer class. It leaves you with less work to create a designer for your control, no matter what the control type is, it takes care of the value changes and updates the designer for you, all you have to do is derive from the DesignSurface
and add the designer verbs of interest, and build it into a separate assembly and deploy with the control library. At this point, it is clear that this article is meant for the control developers / vendors, and not for the consumers of controls. If you are a consumer, don’t forget to ask your vendor for designer support.
Before We Begin, Things To Know
There are guidances in building and deploying a designer extender project. Here is a short glimpse of those rules.
- Add ‘Design.dll’ suffix to the project name, say my control library is named
Creatives.CustomControl
, then the designer project should be named as Creatives.CustomControl.Design
.
- Add
assembly
: ProvideMetaData
attribute to your namespace that indicates the designer your assembly provides metadata about the control designer.
- Deploy your designer DLL with the control library into GAC or at the root folder from which the control library is picked up by the designer.
- Designer extender is deployed in a different DLL and is valid in the Visual Studio designer context alone, it has no effect on the control’s runtime behavior.
All this information is necessary for the designer to pick up the right DLL and the control designer type, unless the designer detects these information and the DLL, you will see no difference in the designer, so be sure to check against the rules when you encounter nothing on the designer.
How to Get Started ?
That’s enough introduction about the DesignSurface
. Let’s take a look at how to begin with the DesignSurface
, I assume you already have your own custom control and you are going to make use of DesignSurface
to add designer support. For demonstration, I have my DesignerExpander
a simple control derived from Expander
has nothing else literally, note I named my control library Creatives.CustomControl
.
namespace Creatives.CustomControl
{
public class DeisgnerExpander : Expander
{
static DeisgnerExpander() { }
}
}
And now I need to provide my customers a good design time experience, all I going to do is to define designer verbs and my DesignSurface
will take care of the rest of the job. DesignerExpander
is actually a Expander
control and I need the following properties to be exposed on the designer, two of them define the controls appearance and the other two define control behavior, and I am going to add headers for the categories too.
DeisgnerExpander.HeaderProperty
– The title text on the control
DeisgnerExpander.ContentProperty
- The text on the body of the control
DeisgnerExpander.IsExpandedProperty
– Gets or sets whether the content is expanded or not
DeisgnerExpander.VisibilityProperty
– Gets or sets the visibility of the control
The first thing is to set up a project for the designer, and I named my designer project Creatives.CustomControl.Desiger
therefore I satisfied the first rule, and the second one is to add the attribute to the namespace and this is how the class will look like when you are done.
[assembly: ProvideMetadata(typeof(Creatives.CustomControl.Design.CustomControlDesigner))]
namespace Creatives.CustomControl.Design
{
public class CustomControlDesigner : DesignSurface
{
}
}
And the second thing is to implement IProvideDesignerVerbs
used to collect the designerverb
s, and here I add the above four properties to the designer verb collection, remember things appear in the order they are added to the collection.
public DesignerVerbCollection GetDesignerVerbs()
{
DesignerVerbCollection verbs = new DesignerVerbCollection();
verbs.AddHeader("CustomControl Editor");
verbs.AddHeader("Appearance");
verbs.AddProperty(DeisgnerExpander.HeaderProperty, "Header");
verbs.AddProperty(DeisgnerExpander.ContentProperty, "Content");
verbs.AddHeader("Behavior");
verbs.AddProperty(DeisgnerExpander.IsExpandedProperty, "IsExpanded");
verbs.AddProperty(DeisgnerExpander.VisibilityProperty, "Visibility");
return verbs;
}
Finally, you have two things left to be completed, to register the designer type with the DesignSurface
in its constructor, and to provide information on the control type. Here goes the code:
DesignSurface.RegisterDesignerVerbProvider(typeof(DeisgnerExpander),
typeof(CustomControlDesigner));
public override Type ControlType { get { return typeof(DeisgnerExpander); } }
That’s it! Build the project and deploy with the controls DLL, believe me you now have the designer support added for your custom control. Go create a test project, drag and drop your custom control, and selecting the custom control will give you the editor right on the designer. How awesome is that to have it!!!
How All These Works?
Yeah, it’s working, and I found it simple and elegant. But how do all these things work? I understand you have this question in your mind, and I did it on purpose. ;) I started writing articles from the day I understood how things works, however my articles received no attentions, even I’ve got comments on my writing too :P It's more than a year and things have gotten better than before. This is an initiative to make a difference and tried things upside down, and I believe it works out for me this time. Don’t forget to leave your comments and suggestions, better or worse they make me improve and I am sure I will help you write lesser code in my future articles.
Back to the Point: How It Works
Let’s make a quick tour on the helpers and wrappers used in the project before we dive into the concepts.
DesignerVerb
– defines an Item
shown in the design time adorner, be it a Header
or a PropertyEditor
. Header
holds a text that will be displayed, and property editor holds a dependency property and its display name.
DesignerVerbCollection
– defines an IEnumerable
, lets you add a property, a header, or a DesignerVerb
to the collection.
- And I have a
PropertyGrid
and a GridFactory
to generate the corresponding editor for the given dependency property type. PropertyGrid
does a simple job and consumes the designer verbs and builds the property editors with the help of GridFactory
, most of the work done in PropertyGrid
is adding the FrameWorkElements
to the panel, and setting appropriate values for the grid columns and grid rows.
GridFactory
– This factory implementation decides the editor type for the given properties and generates the editor. (For instance, textbox
for string
type. Forgive me, I don’t have editors for brushes and other color types.)
public static PropertyEditor GetEditor(DependencyProperty property)
{
if (property == null) return null;
Type valueType = GetValueType(property);
Type editorType = GetEditorType(valueType);
FrameworkElement editor = Activator.CreateInstance(editorType) as FrameworkElement;
if ( editor!=null && editorType == typeof(ComboBox))
(editor as ComboBox).ItemsSource = Enum.GetValues(valueType);
return new PropertyEditor(editor, property);
}
You now have the knowledge on how most of the things are worked out, and there is one thing left to be unfolded - the FeatureProvider WPF defines many feature providers, each contributes a specific design time behavior. AdornerProvidres
, ContextMenu
Provider, DefaultInitializer
and more. This article makes use of PrimarySelectionAdornerProvider – provides an adorner in the designer when a selection is made on the designer. When a control is activated, it invokes AdornerProvider.Activate
member and here we are going to do all the stuff we need on the designer.
protected override void Activate(Microsoft.Windows.Design.Model.ModelItem item)
{
ModelItem = item;
InitSurfacePanel();
SurfacePanel.PropertyGrid.UpdateLayout();
base.Activate(item);
}
That’s okay, what is this new ModelItem
? Let me explain, ModelItem
is an abstraction of the view (the element being selected in the designer), with this reference we are going to sync the value changes in the property editors to the model item.
ModelItem.Properties[property.Name].SetValue(newValue);
What’s Next?
That’s it, my job is done here. Hope you enjoyed it. Don’t forget to leave your comments and suggestions. See you in my next post!.
One last word! This will work for Visual Studio 2010 alone, VS2008 has a different architecture!
Happy coding!!!