Hi Folks,
Whenever someone speaks about developers & designers working together on Silverlight/WPF they talk about the mythical “Designer-Developer workflow”.
Even if you’re your own designer, that workflow is always ever present when you switch between “designer” and “developer” hats.
I’m a huge supporter of using Tools in order to build your UI. HUGE.
I can’t for the life of me understand why someone would choose to be non-productive and XAML things manually.
One of the things you’ll find out when working with these UI Building tools (Expression Blend & Visual Studio WPF/Silverlight Designer) is that they can be a huge boost to productivity when used correctly.
This blog post however, is not about using tools, it’s about helping those who use your controls with tools.
It’s about the developers landing a hand to make the designer experience easier and frictionless.
Control vendors and people who author custom controls often find themselves wishing they could give a better experience for their custom controls.
However, there’s a huge lack of public information on this topic. And I’ve decided to correct this situation with this short 50+ pages article.
This is also relevant for any developer working with a designer on his own project.
The solution we’ve created here is available at: SilverlightControls.zip
Introduction
We’ll start by looking at the way design time assemblies work.
Consider the following class:
public class myControl : Control
{
public string MyStringProperty { get; set; }
}
Now, consider the following class with some design-time attributes:
[Description("I am a control")]
public class myControl : Control
{
[Description("I am a property")]
public string MyStringProperty { get; set; }
}
The way design-time assemblies work is they separate design time attributes from the actual class. So our class would look like so:
public class myControl : Control
{
public string MyStringProperty { get; set; }
}
And in our design-time assembly there would be code equivalent to this one:
AddCallback(typeof(myControl), builder =>
builder.AddCustomAttributes(new DescriptionAttribute("I am a control 2")));
AddCallback(typeof(myControl), builder =>
builder.AddCustomAttributes("MyStringProperty", new DescriptionAttribute("I am a property")));
Without delving too much into the API we can see that the first line says “myControl has the following DescriptionAttribute”, and the second line says “myControl.MyStringProperty has this DescriptionAttribute”.
We separate out the Design Time code from the Runtime code.
Let’s see how this looks like when we run it in Expression Blend:
What are the advantages of this approach?
- Decoupling design time code from runtime code helps create better code. (Separation of concerns, POCO, single responsibility and what not)
- Changing design time properties based on tooling. This approach allows supporting different design time attributes based on which tool they are run at.
- Design time changes do no require recompiling the runtime assemblies.
- Adding design time properties can be done by someone who isn’t the runtime assemblies author.
- advanced design-time support doesn’t require us to register visual studio packages with weird GUIDs.
Reference Architecture
There’s a lot of steps here. Essentially we’d like to create the following architecture:
Not confusing at all, Is it? Simply speaking, it’s just the reference model.
We’ll create 3 new projects.
*.Design.dll – will contain design time attributes that work for both Visual Studio WPF/Silverlight designer and Expression Blend.
*.VisualStudio.design.dll – Will contain design time features that only work in Visual studio.
*.Expression.design.dll – will contain design time features that only work in Expression blend.
This naming conventions are mandatory.
The first step is adding a reference to the project which we’re adding design-time support for from the design-time assemblies:
Add a reference to the Shared design time DLLs:
And add a reference between the Blend design-time project to blend specific assemblies:
Setup
1. Add a Silverlight Class Library that will contain our Controls. We’ll call it SilverlightControls.
2. Add a myControl class.
public class myControl : Control
{
public string MyStringProperty
{
get { return GetValue(MyStringPropertyProperty) as string; }
set { SetValue(MyStringPropertyProperty, value); }
}
public static readonly DependencyProperty MyStringPropertyProperty =
DependencyProperty.Register("MyStringProperty",typeof(string), typeof(myControl), null);
}
3. Create the shared design time DLL. Since we named the control assembly “SilverlightControls” this assembly must be called “SilverlightControls.Design”.
I’ve chosen to create it of type “WPF Custom Control library” since I’ll be using some some WPF features later on.
You can just add references manually later on to a normal Class Library project if you’d like.
4. Add a reference from “SilverlightControls.Design” to the “SilverlightControls” project, the Microsoft design time assemblies and the Silverlight System.Windows.dll which holds our base classes.
(Silverlight 2 Reference directory – defaults on my machine to c:\Program Files\Microsoft SDKs\Silverlight\v2.0\Reference Assemblies)
And here are our references:
5. Create the visual studio design time DLL.
Since we named the assembly it’s adding design-time for “SilverlightControls” this assembly must be called “SilverlightControls.VisualStudio.Design”.
6. Add a reference from “SilverlightControls.VisualStudio.Design” to the “SilverlightControls” project, the Microsoft design time assemblies and the Silverlight System.Windows.dll.
And here are our references:
7. Create the expression blend design time DLL.
Since we named the assembly it’s adding design-time for “SilverlightControls” this assembly must be called “SilverlightControls.Expression.Design”.
8. Add a reference from “SilverlightControls.Expression.Design” to the “SilverlightControls” project, the Microsoft design time assemblies and Blend specific dlls.
(From the Blend application directory, default on my machine is: c:\Program Files\Microsoft Expression\Blend 2\)
And here are our references:
9. We need to add a class that’s Implements the IReigsterMetadata interface in all 3 design-time assemblies.
I use the following template for all three.
public class MetadataRegistration : IRegisterMetadata
{
private static AttributeTable _customAttributes;
private static bool _initialized;
public void Register()
{
if (!_initialized)
{
MetadataStore.AddAttributeTable(CustomAttributes);
_initialized = true;
}
}
public static AttributeTable CustomAttributes
{
get
{
if (_customAttributes == null)
{
_customAttributes = new CustomMetadataBuilder().CreateTable();
}
return _customAttributes;
}
}
private class CustomMetadataBuilder : AttributeTableBuilder
{
public DeveloperMetadataBuilder()
{
// TODO: Add Design time code here!
}
private void AddTypeAttributes(Type type, params Attribute[] attribs)
{
base.AddCallback(type, builder => builder.AddCustomAttributes(attribs));
}
private void AddMemberAttributes(Type type, string memberName, params Attribute[] attribs)
{
base.AddCallback(type, builder => builder.AddCustomAttributes(memberName, attribs));
}
}
}
I won’t go into what the class does and what IRegisterMetadata & AttributeTableBuilder are, you’ve got MSDN for that. Suffice to say, that they are the classes from the extensibility framework that allow us to add design-time support for our classes.
As well, We won’t go into what my template does. Basically it ensures that Metadata is only created once, and provides easy access methods we’ll be using throughout this article.
So here’s out current project structure:
Note that we have one metadata class per project.
10. Setup design time files copy.
The design time DLL files must be located in the same directory as the runtime DLL.
I like having this step of the process of copying the *design.dll files to the runtime debug directory automated. You can choose not to automate this step and do it manually.
The way I’ve chosen to do the automated copying is add a post-build action to each of the design-time project.
Right Click on our project file –> Properties –> Build Events.
Paste this into the Post Build Events: copy "$(TargetPath)" "$(SolutionDir)\SilverlightControls\Bin\Debug"
Repeat this step for all 3 design-time projects.
It’s important to note that you can do this step in multiple ways, I’ve chosen to use Post-Build action.
Creating our testing project
1. We’ll create a new project in Expression blend that uses our DLL. Throughout this article we’ll use this project to preview our design-time improvements.
2. Add a reference to our “SilverlightControls.dll”.
What just happened here?
We have 2 restrictions on the design time DLLs. Because of those 2 restrictions they get automatically loaded up by the tools:
- Design time DLLs are in the directory as the runtime DLL.
- Design time DLLs follow a naming convention.
- <RuntimeName>.Design.dll – for the shared design time
- <RuntimeName>.Expression.Design.dll – for expression blend design time
- <RuntimeName?.VisualStudio.Design.dll – for Visual studio wpf/silverlight designer design time
Now that we have all the infrastructure in place, let’s start using it.
DescriptionAttribute (Shared)
Description is a way for us to show “Intellisense-like” comments in our design-time tools.
This attribute works for both Classes and properties.
When we say an attribute is “Shared” it means that it works in both tools and should be placed in the shared design-time dll.
All though Visual studio currently has a read-only Silverlight design surface, once it gets an editable design surface, those would be supported by both tools.
So let’s add the DescriptionAttribute to both myControl and MyControl.MyStringProperty in the SilverlightControls.Design.MetadataRegistration type.
public CustomMetadataBuilder()
{
AddTypeAttributes(typeof(myControl),
new DescriptionAttribute("I am a control"));
AddMemberAttributes(typeof(myControl),"MyStringProperty",
new DescriptionAttribute("I am a property"));
}
We’ll compile our application and see those descriptions in Expression Blend.
DisplayNameAttribute (shared)
Display names are used to show friendlier names of type members.
AddMemberAttributes(typeof(myControl),"MyStringProperty",
new DescriptionAttribute("I am a property"),
new DisplayNameAttribute("My String Property"));
And this is how it looks like in Blend:
CategoryAttribute (shared)
Each property has a category by default. Blend features the following categories for our control:
By default our property gets added to the “Miscellaneous” Category. And if we’re fine with it, we can just keep it there.
We might want to add our property to an existing category, like “Common Properties”.
AddMemberAttributes(typeof(myControl),"MyStringProperty",
new DescriptionAttribute("I am a property"),
new DisplayNameAttribute("My String Property"),
new CategoryAttribute("Common Properties"));
And we can see in Blend that it did move to that Category:
We might also like to create our own category called “My Category”.
AddMemberAttributes(typeof(myControl),"MyStringProperty",
new DescriptionAttribute("I am a property"),
new DisplayNameAttribute("My String Property"),
new CategoryAttribute("My Category"));
Blend would even group properties in the same custom category together.
Let’s add another property of type Integer to our control:
public class myControl : Control
{
public string MyStringProperty
{
get { return GetValue(MyStringPropertyProperty) as string; }
set { SetValue(MyStringPropertyProperty, value); }
}
public static readonly DependencyProperty MyStringPropertyProperty =
DependencyProperty.Register("MyStringProperty",typeof(string), typeof(myControl), null);
public int MyIntProperty
{
get { return (int)GetValue(MyIntPropertyProperty) ; }
set { SetValue(MyIntPropertyProperty, value); }
}
public static readonly DependencyProperty MyIntPropertyProperty =
DependencyProperty.Register("MyIntProperty", typeof(int), typeof(myControl), null);
}
We’ll add it to our custom category.
AddMemberAttributes(typeof(myControl),"MyStringProperty",
new DescriptionAttribute("I am a property"),
new DisplayNameAttribute("My String Property"),
new CategoryAttribute("My Category"));
AddMemberAttributes(typeof(myControl), "MyIntProperty",
new CategoryAttribute("My Category"));
And we can see both properties in ‘My Category’ in Blend:
BrowsableAttribute (shared)
Browsable attribute allows us to hide properties that should not be available in Blend’s Data pane or Visual Studio’s properties windows.
AddMemberAttributes(typeof(myControl),"MyStringProperty",
new DescriptionAttribute("I am a property"),
new DisplayNameAttribute("My String Property"),
new CategoryAttribute("My Category"));
AddMemberAttributes(typeof(myControl), "MyIntProperty",
new CategoryAttribute("My Category"),
BrowsableAttribute.No);
When we run this in Blend we can see the property isn’t on the Data Pane:
We can use the browsable attribute to hide away inherited properties as well as our own defined properties.
EditorBrowsableAttribute (Blend)
I’ve marked this property as Blend only, but that’s just from my personal guesses as to the Visual Studio Silverlight Designer. This might be supported in some fashion in VS, but I’d suggest looking into it before using it there.
Editor Browsable Advanced state allows you in Blend to hide properties in a category unless the user decided he’d like to view those.
Basically, Hiding away seldom used properties to keep that category clear.
Let’s open up the Metadata file in the expression blend metadata project and add the following design-time:
AddMemberAttributes(typeof (myControl), "MyIntProperty",
new EditorBrowsableAttribute(EditorBrowsableState.Advanced));
Here’s the Blend display for that category:
We can see there’s a little arrow hidden on the Category:
If we click it, It’ll open up and reveal all Advanced properties in that category:
If the user loses focus from our control and selects it again, the category would show us as closed again:
So the assumption here is “we’ll hide it from you until you need it specifically for something”.
Where is this attribute used for by Silverlight Framework properties? A lot of places.
Here’s the “Layout” category in Silverlight:

And if we click it:
We can see why we would want to use this attribute to avoid an explosion of properties in the same category.
TypeConverterAttribute (Shared)
TypeConverters don’t work the same in blend as they do in Visual Studio for the full .Net framework.
Originally, they were meant to provide value transitions from the UI to the underlying model and back. That doesn’t really work in Blend 2 SP1 today.
Which if fine, because it does a few other nifty stuff.
One thing TypeConverters do in Blend (and I’m guessing Visual Studio Silverlight Designer) is support standard values. Let’s see that in action.
Let’s add another property to our class of Type object called “MyObject”.
public class myControl : Control
{
public string MyStringProperty
{
…
}
public int MyIntProperty
{
…
}
public object MyObjectProperty
{
get { return (object)GetValue(MyObjectPropertyProperty); }
set { SetValue(MyObjectPropertyProperty, value); }
}
public static readonly DependencyProperty MyObjectPropertyProperty =
DependencyProperty.Register("MyObjectProperty", typeof(object), typeof(myControl), null);
}
Here’s the default design time for that property:
We’ll start off by creating this simple TypeConverter:
public class myTypeConverter : TypeConverter
{
public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
{
return true;
}
public override TypeConverter.StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
{
return new StandardValuesCollection(new string[] { "Hello" ,"World", "foo", "bar"});
}
}
All this type converter does is write some code that suggests possible values for any property using this TypeConverter.
Next in the shared design-time metadata we’ll add a reference to this TypeConverter:
AddMemberAttributes(typeof(myControl), "MyObjectProperty",
new CategoryAttribute("My Category"),
new TypeConverterAttribute(typeof(myTypeConverter)));
And when we run this in Blend:
We get a list of those values suggested by code that we can select from.
Similarly we can use any of the built-in TypeConverters, like Boolean Converter:
AddMemberAttributes(typeof(myControl), "MyObjectProperty",
new CategoryAttribute("My Category"),
new TypeConverterAttribute(typeof(BooleanConverter)));
And here is how it looks like in Blend:
Some TypeConverters even give the tooling visual cues on how to deal with the current property.
A good example of that is ExpandleTypeConverter shown here:
AddMemberAttributes(typeof(myControl), "MyObjectProperty",
new CategoryAttribute("My Category"),
new TypeConverterAttribute(typeof(ExpandableObjectConverter)));
And here is how it looks like in Blend:
And clicking the “new” button causes the object dialog to pop-up:
PropertyOrderAttribute (Shared)
Property order attribute allows us to order properties in an existing category in before/after other properties.
We’ll need 3 properties for this demonstration, so I’m taking MyIntProperty which is “Advanced” out of the advanced area. (in the blend design time metadata file)
//AddMemberAttributes(typeof (myControl), "MyIntProperty",
// new EditorBrowsableAttribute(EditorBrowsableState.Advanced));
After we cancel out hiding the MyIntProperty, here’s our default property ordering:
It’s alphabetical order based.
First “MyIntProperty”, second “MyObjectProperty” and third “My String Property”.
We’ll start by saying that we want MyStringProperty to be the first property.
AddMemberAttributes(typeof(myControl), "MyStringProperty",
new DescriptionAttribute("I am a property"),
new DisplayNameAttribute("My String Property"),
new CategoryAttribute("My Category"),
new PropertyOrderAttribute(PropertyOrder.Early));
And here is how that looks like in Blend:
I kinda snuck in that PropertyOrder class. Let’s take a closer look a PropertyOrder static members.
OK, now we want to have MyIntProperty to show up last.
AddMemberAttributes(typeof(myControl), "MyIntProperty",
new CategoryAttribute("My Category"),
new PropertyOrderAttribute(PropertyOrder.Late));
And we can see in Blend that the int property is now the last one:
What happens now if we define MyObjectProperty and “My String Property” as both PropertyOrder.Early?
AddMemberAttributes(typeof(myControl), "MyObjectProperty",
new CategoryAttribute("My Category"),
new TypeConverterAttribute(typeof(ExpandableObjectConverter)),
new PropertyOrderAttribute(PropertyOrder.Early));
They both get sorted by property order first and than alphabetical order:
Since both “MyObjectProperty” and “My String Property” have equal property order, they get sorted out internally by alphabetical order.
Now, for exercise sake, I’d like for us to keep the current Property Ordering equality and sort those property manually internally.
We’ll try and move “My String Property” to show up before “MyObjectProperty”.
AddMemberAttributes(typeof(myControl), "MyObjectProperty",
new CategoryAttribute("My Category"),
new TypeConverterAttribute(typeof(ExpandableObjectConverter)),
new PropertyOrderAttribute(PropertyOrder.CreateAfter(PropertyOrder.Early)));
And here we can see that MyObjectProperty is ordered after “My String property”:
It’s important to note that PropertyOrder.CreateBefore/After methods return a PropertyOrder, so we could keep ordering manually with as much granularity as we’d like.
NumberRangeAttribute, NumberIncrementAttribute, NumberFormatAttribute (Blend)
It’s important to note that this attributes live in the Expression DLLs, and therefore can only be used in Blend.
Let’s have a look at a property we’ve all used that have these attributes declared on it: Opacity
The first thing we can notice is that there’s a ratio issue here. Opacity has a 100 time multiplier in display formatting.
Next we notice that there’s a ‘%’ symbol next to our multiplied number.
Now, we can start dragging with our mouse and increment and decrement the current value.
if we drag with our mouse pressed and the SHIFT key pressed we start seeing larger increments:
or we drag and the CTRL + SHIFT keys are pressed, we’ll see much smaller increments:
We can see two things here:
1. There’s a 3 types of increments/decrements – Small, Large and normal
2. There’s a range of number between 0-100
Couple that that with the formatting which we’ve previously seen, that’s basically what these properties do.
Let’s take a look at the constructors for these attributes:
NumberRangeAttribute allows us to specify the Minimum & Maximum for the drag operation.
NumberIncrementAttribute allows us to specify the small, default and large increments.
NumberFormatAttribute allows us to specify how to format the displayed text, what numeric precision we’d like to show and what is the display ratio we’d like to use.
In our blend metadata file, let’s add the following attributes for MyIntProperty:
We’d like to limit it’s underlying range between 1-100, specify increments of 0.5, 1 and 5, have a display ratio of x10 and format as “X%”.
We’ll start by limiting the range between 1-100:
AddMemberAttributes(typeof(myControl), "MyIntProperty",
new NumberRangesAttribute(null, 1, 100, null, null));
Add the 0.5, 1 and 5 increments for small, default and large increments (respectively):
AddMemberAttributes(typeof(myControl), "MyIntProperty",
new NumberRangesAttribute(null, 1, 100, null, null),
new NumberIncrementsAttribute(0.5, 1, 5));
And finish off by adding a ‘X%’ format with a display ratio of 10.
AddMemberAttributes(typeof(myControl), "MyIntProperty",
new NumberRangesAttribute(null, 1, 100, null, null),
new NumberIncrementsAttribute(0.5, 1, 5),
new NumberFormatAttribute("0'%'", null, 10));
Let’s run this sample and start dragging normally: :
And drag with the SHIFT key pressed:
Here’s the actual underlying value of MyIntProperty:
So we got everything we wanted: Range between 0-100, display ratio, formatting, and increments.
ToolboxBrowsableAttribute (Shared)
Say we have an assembly with a few controls and we’re adding that assembly to the Visual studio Silverlight Toolbox or the Asset Library in Blend.
If we’d like to hide one of these controls, we could use ToolboxBrowsableAttribute for that. A good example of other controls that do that are ComboBox that is visible but ComboBoxItem is hidden, Calender is visible by DayButton isn’t and so on.
This is the only design-time attribute that as of today works in both tools (Visual Studio 2008 SP1, Blend 2 SP1).
Let’s add another control and build.
public class myOtherControl : Control
{
}
In Blend we can see the new control in the asset gallery:
In Visual studio we’ll go to the “Choose Items” dialog, browse to the “SIlverlightControls.dll” and click OK.
Now, we’ve stated that we want to hide myOtherControl from these dialogs. So we’ll add a ToolboxBrowsableAttribute to it in the shared design-time metadata.
AddTypeAttributes(typeof(myOtherControl),
new ToolboxBrowsableAttribute(false));
After compiling and restarting blend, we can see that blend no longer shows the control in the Blend Asset Gallery:
And in Visual studio’s “Choose Items” dialog we won’t see myOtherControl anymore:
Inline Editors (Shared)
Editors are custom elements that are found in the Visual Studio Property window and the Blend Data Pane.
Here’s a few examples of well know editors:
We’ll put all of our custom editors in a custom project to hold them and avoid Silverlight dlls ambiguous references.
Naming convention and the very existence of this project are optional.
We’ll add the necessary design-time references.
We’ll also make sure to add reference from “SilverlightControls.Design.dll” to “SilverlightControls.Design.Editors.dll”.
We’ll start off by adding a ResourceDictionary in our Editors project called “EditorDictionary”.
In the EditorResources.xaml we’ll add the following DataTemplate:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<DataTemplate x:Key="ColorsComboBox">
<ComboBox Text="{Binding StringValue}" IsEditable="True">
<ComboBoxItem Background="Red">Red</ComboBoxItem>
<ComboBoxItem Background="Blue">Blue</ComboBoxItem>
<ComboBoxItem Background="Green">Green</ComboBoxItem>
<ComboBoxItem Background="Yellow">Yellow</ComboBoxItem>
<ComboBoxItem Background="Black">Black</ComboBoxItem>
</ComboBox>
</DataTemplate>
</ResourceDictionary>
It’s a straight forward DataTemplate.
We’ve got a ComboBox with a few ComboBoxItems. The only important thing is making sure there’s a b binding to either StringValue or Value which are sent to the DataTemplate by the design tool.
Next we’ll add a static class which will enable easy access to our Resource dictionary. (not mandatory, but I think it’s a good pattern)
public static class EditorDictionary
{
private static ResourceDictionary dictionary = (ResourceDictionary)Application.LoadComponent(new Uri("SilverlightControls.Design.Editos;component/EditorDictionary.xaml", UriKind.Relative));
public static DataTemplate ColorsComboBox
{
get
{
return dictionary["ColorsComboBox"] as DataTemplate;
}
}
}
All this class does is load the Resource dictionary and has a static property to return our DateTemplate.
Now let’s get to the interesting part, we’ll create a custom inline editor that shows the DataTemplate.
public class CustomInlineEditor : PropertyValueEditor
{
public CustomInlineEditor()
{
this.InlineEditorTemplate = EditorDictionary.ColorsComboBox;
}
}
Very straight forward. PropertyValueEditor has one interesting thing about it - the InlineEditorTemplate which we’ve set to the DataTemplate from the resource dictionary.
One last thing we have to do is say that MyControl.MyStringProperty has a custom inline editor.
AddMemberAttributes(typeof(myControl), "MyStringProperty",
new DescriptionAttribute("I am a property"),
new DisplayNameAttribute("My String Property"),
new CategoryAttribute("My Category"),
new PropertyOrderAttribute(PropertyOrder.Early),
PropertyValueEditor.CreateEditorAttribute(typeof(CustomInlineEditor)));
Now if we run this in Blend:
And if we choose “Green”.
The driving force behind the back-and-forth between the UI and the underlying XAML is the binding on the ComboBox to StringValue.
I think that the best takeaway from this exercise shouldn’t be “rainbow colored ComboBoxes are cool” rather “we can put any element as an inline editor”.
Property editors?
Now that we’ve seen how an Inline property editor looks like and how it works, we should get acquainted with the 3 types of Property editors.
Inline Property Editor – follows the “one line with a property name and editor” schema.

Extended Property Editor – has an inline editor, but can also have a popup or some additional area opened by user interaction.


Dialog property editor – An Inline property editor with a button that triggers a dialog editor.


Expended Property Editor
As we’ve seen, an expended property editor is just an inline editor that based on user interaction can grab more screen real estate. Let’s see how to build one.
We’ll start of by creating another DataTemplate in our EditorResources.xaml file for the InlineValueEditorTemplate:
xmlns:PropertyEditing="clr-namespace:Microsoft.Windows.Design.PropertyEditing;assembly=Microsoft.Windows.Design"
<DataTemplate x:Key="SimpleTextBox">
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding StringValue}" />
<PropertyEditing:EditModeSwitchButton />
</StackPanel>
</DataTemplate>
We can see it’s just a TextBox with a binding to StringValue we’ve seen before in the inline property editor.
Interesting to note is that there’s an “EditModeSwitchButton”.
This is a built-in extensibility button that toggles the extended template in & out of view. All that button does is hook-up to extensibility’s PropertyValueEditorCommands which take care of showing and hiding the extended view.
Now that we have our InlineValueEditorTemplate, we’ll add another DataTemplate for ExtendedValieEditorTemplate:
<DataTemplate x:Key="HelloWorldListBox">
<ListBox SelectedItem="{Binding StringValue}">
<ListBox.ItemsSource>
<x:Array Type="{x:Type System:String}">
<System:String>Hello</System:String>
<System:String>World</System:String>
<System:String>foo</System:String>
<System:String>bar</System:String>
</x:Array>
</ListBox.ItemsSource>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DataTemplate>
While this DataTemplate might look interesting, it’s really not. It’s just a bunch of strings shown up in a as TextBlock in a Listbox. The only interesting thing about it is that we bound the ListBox’s SelectedItem to StringValue.
Next, we’ll create the strongly typed & named resources for these two new templates in our EditorDictionary class:
public static DataTemplate SimpleTextBox
{
get
{
return dictionary["SimpleTextBox"] as DataTemplate;
}
}
public static DataTemplate HelloWorldListBox
{
get
{
return dictionary["HelloWorldListBox"] as DataTemplate;
}
}
And finally we’ll create an ExtendedValuePropertyEditor that uses our two new DataTemplates:
public class CustomExtendedEditor : ExtendedPropertyValueEditor
{
public CustomExtendedEditor()
{
this.InlineEditorTemplate = EditorDictionary.SimpleTextBox;
this.ExtendedEditorTemplate = EditorDictionary.HelloWorldListBox;
}
}
Last thing we’ll do is hook-up our MyStringProperty with the CustomExtendedEditor:
AddMemberAttributes(typeof(myControl), "MyStringProperty",
new DescriptionAttribute("I am a property"),
new DisplayNameAttribute("My String Property"),
new CategoryAttribute("My Category"),
new PropertyOrderAttribute(PropertyOrder.Early),
PropertyValueEditor.CreateEditorAttribute(typeof(CustomExtendedEditor)));
This is how it looks like in Blend:
One click on the button we’ll get our ExtendedEditorTemplate in a pop-up:
Another click we can see that our ExtendedEditorTemplate is has taken up more space in the data pane:
Selecting anyone of the items will cause the Textbox and the underlying property to update:
And one more click on the button will close the extended editor:
Dialog Property Editor
Instead of showing our Extended template under the existing inline editor, we’d like to have it show up in dialog window.
We’ll create a dialog editor with our existing resource dictionary.
public class CustomDialogEditor : DialogPropertyValueEditor
{
public CustomDialogEditor()
{
this.InlineEditorTemplate = EditorDictionary.SimpleTextBox;
this.DialogEditorTemplate = EditorDictionary.HelloWorldListBox;
}
}
We’ll need to hook this custom dialog editor up to MyStringProperty:
AddMemberAttributes(typeof(myControl), "MyStringProperty",
new DescriptionAttribute("I am a property"),
new DisplayNameAttribute("My String Property"),
new CategoryAttribute("My Category"),
new PropertyOrderAttribute(PropertyOrder.Early),
PropertyValueEditor.CreateEditorAttribute(typeof(CustomDialogEditor)));
When we run this in Blend it’ll look like so:
And changing the value in the dialog would cause the Textbox and underlying property value to change as well:
While Dialog property editors have a similar API to that of extended property editors there’s one main difference – they support transactions.
You can see the “OK” and “Cancel” buttons which basically just fire CommitTransaction or AbortTransaction commands defined in the design time extensiblity DLLs. So, if we click cancel after selecting “World”, the property will revert back to it’s original value of “foo”:
