Page view counter

Silverlight Design Time Extensibility

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:
image image  


What are the advantages of this approach?

  1. Decoupling design time code from runtime code helps create better code. (Separation of concerns, POCO, single responsibility and what not)
  2. Changing design time properties based on tooling.  This approach allows supporting different design time attributes based on which tool they are run at.
  3. Design time changes do no require recompiling the runtime assemblies.
  4. Adding design time properties can be done by someone who isn’t the runtime assemblies author.
  5. 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:

image

Not confusing at all, Is it? Simply speaking, it’s just the reference model.

We’ll create 3 new projects.

image

*.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:

image

Add a reference to the Shared design time DLLs:

image

And add a reference between the Blend design-time project to blend specific assemblies:

image

 

 

Setup

1. Add a Silverlight Class Library that will contain our Controls. We’ll call it SilverlightControls.

image

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.

image

 

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.
image

image

(Silverlight 2 Reference directory – defaults on my machine to c:\Program Files\Microsoft SDKs\Silverlight\v2.0\Reference Assemblies)

image

And here are our references: 

image

 

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”.
image

 

6. Add a reference from “SilverlightControls.VisualStudio.Design” to the “SilverlightControls” project, the Microsoft design time assemblies and the Silverlight System.Windows.dll.
image

image
image

And here are our references:

image

 

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”.
image

 

8. Add a reference from “SilverlightControls.Expression.Design” to the “SilverlightControls” project, the Microsoft design time assemblies and Blend specific dlls.
image

image

(From the Blend application directory, default on my machine is: c:\Program Files\Microsoft Expression\Blend 2\)
 image

And here are our references:

image

 

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:

image

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"

image

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. 

image

2. Add a reference to our “SilverlightControls.dll”.

image

image

 

 

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:

  1. Design time DLLs are in the directory as the runtime DLL.
  2. 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.

image

image

 

 

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:

image

 

 

 

CategoryAttribute (shared)

Each property has a category by default. Blend features the following categories for our control:

image

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:

image

 

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"));

image

 

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:

image

 

 

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:

image

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:

image

    AddMemberAttributes(typeof (myControl), "MyIntProperty",

            new EditorBrowsableAttribute(EditorBrowsableState.Advanced));

 

Here’s the Blend display for that category:

image

We can see there’s a little arrow hidden on the Category:

image

If we click it, It’ll open up and reveal all Advanced properties in that category:

image

If the user loses focus from our control and selects it again, the category would show us as closed again:

image

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:

image

And if we click it:

image

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:

image

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:

image

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:

image

 

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:

image

And clicking the “new” button causes the object dialog to pop-up:

image

image

 

 

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:

image

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:

image

 

I kinda snuck in that PropertyOrder class. Let’s take a closer look a PropertyOrder static members.

image

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:

image

 

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:

image 

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”:

image

 

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

image

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.

image image

image

if we drag with our mouse pressed and the SHIFT key pressed we start seeing larger increments:

image image

or we drag and the CTRL + SHIFT keys are pressed, we’ll see much smaller increments:

image image

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:

image

NumberRangeAttribute allows us to specify the Minimum & Maximum for the drag operation.

 

image

NumberIncrementAttribute allows us to specify the small, default and large increments.

 

image

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: :

image image

And drag with the SHIFT key pressed:

image image

Here’s the actual underlying value of MyIntProperty:

image

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:

image 

In Visual studio we’ll go to the “Choose Items” dialog, browse to the “SIlverlightControls.dll” and click OK.

image image

image

 

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:

image

And in Visual studio’s “Choose Items” dialog we won’t see myOtherControl anymore:

image image

 

 

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:

image

image

image

We’ll put all of our custom editors in a custom project to hold them and avoid Silverlight dlls ambiguous references.

image

Naming convention and the very existence of this project are optional.

We’ll add the necessary design-time references.

image

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”.

image

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:

image

And if we choose “Green”.

image 

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.
clip_image002 

clip_image004

 

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

clip_image008 

clip_image010

 

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

clip_image016

 

 

 

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:

image

One click on the button we’ll get our ExtendedEditorTemplate in a pop-up:

image

Another click we can see that our ExtendedEditorTemplate is has taken up more space in the data pane:

image

Selecting anyone of the items will cause the Textbox and the underlying property to update:

image image

And one more click on the button will close the extended editor:

image

 

 

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:

image

image

And changing the value in the dialog would cause the Textbox and underlying property value to change as well:

 

image 

 

image 

 

 

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”:
image

image

 

 

Hopefully you’ve learned from this tutorial, there’s a lot more knocks and crannies, but this should definitely get you on your way,

-- Justin Angel

Microsoft Silverlight Toolkit Program Manager

Published Monday, November 17, 2008 3:56 PM by JustinAngel

Comments

# Silverlight designer extensibility

I haven't even started writing the docs for SL designer extensibility, and here's Justin Angel with a

Tuesday, November 18, 2008 3:38 PM by Jim Galasyn's Learning Curve

# Silverlight Cream for November 18, 2008 -- #431

In this issue: Justin Angel, Shemesh, Albert Eyal, Steve, Ruurd Boeke, Mike Snow, Mehdi Slaoui Andaloussi

Tuesday, November 18, 2008 4:51 PM by Community Blogs

# 2008 November 19 - Links for today &laquo; My (almost) Daily Links

Pingback from  2008 November 19 - Links for today &laquo; My (almost) Daily Links

Wednesday, November 19, 2008 4:21 AM by 2008 November 19 - Links for today « My (almost) Daily Links

# Silverlight Cream for November 19, 2008 -- #432

In this issue: Bart Czernicki, Martin Mihaylov, Katrien De Graeve, Silverlight SDK, Arturo Toledo, crocusgirl

Wednesday, November 19, 2008 2:20 PM by Community Blogs

# Silverlight: Design-Time Extensibility

Just a link but what a link! If you're interested in design-time extensibility for Silverlight controls...

Thursday, November 20, 2008 1:40 PM by Mike Taulty's Blog

# 2008 November 21 - Links for today &laquo; My (almost) Daily Links

Pingback from  2008 November 21 - Links for today &laquo; My (almost) Daily Links

# Dew Drop - November 21, 2008 | Alvin Ashcraft's Morning Dew

Pingback from  Dew Drop - November 21, 2008 | Alvin Ashcraft's Morning Dew

# re: Silverlight Design Time Extensibility

Thank you Justin for writing all these great tutorials - especially the ones on the Silverlight Toolkit.

I really like the format - they are very clear and easy to follow.

Friday, November 21, 2008 10:53 AM by davidjjon77

# Silverlight design time extensibility

Justin Angel has an exhaustive blog post on Silverlight design time extensibility. You can get it here

Monday, November 24, 2008 5:49 PM by Silverlight SDK

# Silverlight Travel &raquo; Silverlight Design Time Extensibility

Pingback from  Silverlight Travel &raquo; Silverlight Design Time Extensibility

Tuesday, December 09, 2008 1:03 AM by Silverlight Travel » Silverlight Design Time Extensibility

# 翻译:SILVERLIGHT设计时扩展(注:内容超长,请用IE浏览)

只要有人谈到开发者与设计师在 Silverlight/WPF上协同工作时,他们就会谈论“设计,开发工作流程”这个问题。即使您是您自己的设计师,这工作也始终是永远存在于当你在“设计师”和“开发”之间切换“帽子”的过程中。我是一个使用工具创建用户界面的支持者。 我的生活让我不能理解为什么有人会选择非产能(non-productive) 和手写XAML的事情。你能找出的一个情况就是当你使用(Expression Blend

Wednesday, December 10, 2008 8:05 PM by 代震军

# Ning Zhang&#8217;s Blog &raquo; Design Time Feature Implementation in Silverlight Toolkit

Pingback from  Ning Zhang&#8217;s Blog &raquo; Design Time Feature Implementation in Silverlight Toolkit

# re: Silverlight Design Time Extensibility

Hi,

thanks for this very valuable post which allowed me to greatly improved my custom control design time experience. But unfortunately, I have still one issue though: each time I use one of the Blend-specific Number[Ranges|Increments|Format]Attribute, it ends up with the following Exception in Blend:"The component 'Microsoft.Expression.Framework.PropertyInspector.CategoryContainer' does not have a resource identified by the URI '/Microsoft.Expression.Framework;component/properties/categorycontainer.xaml'

I am using Blend 2 Sp1. Do you have any idea why I have this exception ?

Thanks

Patrick

Thursday, March 05, 2009 3:25 AM by pruzand

# re: Silverlight Design Time Extensibility

Thank you, Justin! This has been a tremendous help with my Expression Blend extensibility effort. Do you have any ideas how I can populate the 'CustomDialogEditor' with a list of all the objects on the Page being edited. I would like to write a dialog picker that selects a particular Shape already in the XAML. I think the CustomDialogEditor class will need to somehow find and load the XAML page being edited, then maybe call XamlReader.Load() to get the name of each Shape object. Sound about right?

Thank you,

John K.

Friday, March 06, 2009 12:59 AM by jkanalakis

# Dew Drop - March 20, 2009 | Alvin Ashcraft's Morning Dew

Pingback from  Dew Drop - March 20, 2009 | Alvin Ashcraft's Morning Dew

# re: Silverlight Design Time Extensibility

Justin,

This is a great article.  I am trying to reproduce the code in VB.NEt and am vaing a problem with the following line.

private void AddTypeAttributes(Type type, params Attribute[] attribs)

       {

           base.AddCallback(type, builder => builder.AddCustomAttributes(attribs));

       }

So far I have the following:

        Private Sub AddtypeAttributes(ByVal type As Type, ByVal ParamArray attribs() As Attribute)

           MyBase.AddCallback(type, AddressOf AddCustomAttributes(CType(Builder, Type)))

       End Sub

Which does not seem to work.  Can you provide the VB version of this line?

Thanks,

Mike Lockwood

Thursday, April 02, 2009 7:04 PM by Mustang65

# re: Silverlight Design Time Extensibility

Some of my classes implement "INotifyPropertyChanged", and as soon as I call AddTypeAttributes(typeof(myInotifyPropChangedClass)), it won't compile. I get the following error:

------------------

Error 1 The type 'System.ComponentModel.INotifyPropertyChanged' is defined in an assembly that is not referenced. You must add a reference to assembly 'System, Version=2.0.5.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e'.

------------------

However I can't both have a reference to the .NET's and Silverlight's System.dll.

How do I get around this issue?

Wednesday, May 20, 2009 3:33 PM by SharpGIS

# re: Silverlight Design Time Extensibility

PEOPLE - Take your design time question to the Silverlight.net/forums. Ping me at J@JustinAngel.Net and I promise I'll get to them.

Seriously, blog post comments are the worse place ever to answer questions.

Wednesday, May 20, 2009 3:40 PM by JustinAngel