Wednesday, June 10, 2009

Design time Experience for your WPF/Silverlight controls

It is always a good practice to ship your WPF/Silverlight controls with its design time experience. Design time experience can be extended with the help of design assemblies supplies by Microsoft

Steps :

Create your WPF/Silverlight control

Create a visual studio class library project and name it MyControls

Subclass a control of your choice and add one dependency property. I did that for a button control in my sample. I used this control to demonstrate the property Editor in design time extensibility

public class MyButton : Button
{

public double MyHeight
{
get { return (double)GetValue(MyHeightProperty); }
set { SetValue(MyHeightProperty, value); }
}

// Using a DependencyProperty as the backing store for MyHeight. This enables animation, styling, binding, etc...
public static readonly DependencyProperty MyHeightProperty =
DependencyProperty.Register("MyHeight", typeof(double), typeof(MyButton), new UIPropertyMetadata(0d));


}



Create a second class as above to demonstrate the category Editor concept in design time Extensibility . My second class is as follows




public class MyTextBox : TextBox
{
public double MyWidth
{
get { return (double)GetValue(MyWidthProperty); }
set { SetValue(MyWidthProperty, value); }
}

// Using a DependencyProperty as the backing store for MyWidth. This enables animation, styling, binding, etc...
public static readonly DependencyProperty MyWidthProperty =
DependencyProperty.Register("MyWidth", typeof(double), typeof(MyTextBox), new UIPropertyMetadata(20d));
}



Create the Design time project for the control



Create a class library project named MyControls.design and add reference to the following assemblies



Microsoft.Windows.Design.dll



MyControls.dll



Create a class which inherit from the IRegisterMetadata interface and write necessary code to update the metadatstore (a container of custom design-time attributes).




public class DesignMetaDataMain : IRegisterMetadata
{
#region IRegisterMetadata Members
public void Register()
{
AttributeTableBuilder builder = new AttributeTableBuilder();
new MyButtonMetadata().AddMetadata(builder);
new MyTextBoxMetadata().AddMetadata(builder);
MetadataStore.AddAttributeTable(builder.CreateTable());
}
#endregion
}




public class MyButtonMetadata
{
public void AddMetadata(AttributeTableBuilder tableBuilder)
{
//Property Editor
tableBuilder.AddCustomAttributes(
typeof(MyButton),
MyButton.MyHeightProperty,
new MyButtonCategoryAttribute(),
new EditorAttribute(typeof(TextExtendedEditor),
typeof(TextExtendedEditor)));
}
}

internal class MyButtonCategoryAttribute : CategoryAttribute
{
protected override string GetLocalizedString(string value)
{
return "MyButton";
}
}




public class MyTextBoxMetadata
{
public void AddMetadata(AttributeTableBuilder tableBuilder)
{
tableBuilder.AddCustomAttributes(typeof(MyTextBox), MyTextBox.MyWidthProperty, new MyTextBoxCategoryAttribute());
tableBuilder.AddCustomAttributes(typeof(MyTextBox), new EditorAttribute(typeof(MyTextBoxCategoryEditor), typeof(MyTextBoxCategoryEditor)));
}
}
internal class MyTextBoxCategoryAttribute : CategoryAttribute
{
protected override string GetLocalizedString(string value)
{
return "MyTextBox";
}
}



Category Editor definition




public class MyTextBoxCategoryEditor : CategoryEditor
{

public override bool ConsumesProperty(PropertyEntry property)
{
return true;
}

public override System.Windows.DataTemplate EditorTemplate
{
get
{
try
{
Resources myresourcedictionary = new Resources();
return myresourcedictionary["myTextBoxEditorTemplate"] as DataTemplate;

}
catch (Exception ex)
{

MessageBox.Show(ex.Message);
}
return null;
}
}

public override object GetImage(System.Windows.Size desiredSize)
{
return null;
}

public override string TargetCategory
{
get
{
return "MyTextBox";
}
}
}



Property Editor definition




public class TextExtendedEditor : ExtendedPropertyValueEditor
{
private Resources res = new Resources();

public TextExtendedEditor()
{
this.InlineEditorTemplate = res["myButtonEditorTemplate"] as DataTemplate;
}

}



Resource Dictionary code




<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:PropertyEditing="clr-namespace:Microsoft.Windows.Design.PropertyEditing;assembly=Microsoft.Windows.Design"
x:Class="MyControls.Design.Resources" >
<DataTemplate x:Key="myButtonEditorTemplate">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Button Grid.Column="0"
Content="{Binding StringValue}" Click="Button_Click"/>
<TextBox Text="{Binding StringValue}"
Grid.Column="1" />
<!--<PropertyEditing:EditModeSwitchButton Grid.Column="1" />-->
</Grid>
</DataTemplate>
<DataTemplate x:Key="myTextBoxEditorTemplate">
<Expander>
<ItemsControl ItemsSource="{Binding Path=Properties}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="Left"
Width="0.4*"
MinWidth="100"
MaxWidth="180" />
<ColumnDefinition SharedSizeGroup="Middle"
Width="0.6*" />
<ColumnDefinition SharedSizeGroup="Right"
Width="12" />
</Grid.ColumnDefinitions>
<PropertyEditing:PropertyContainer Grid.ColumnSpan="3"
PropertyEntry="{Binding}" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Expander>
</DataTemplate>
</ResourceDictionary>



Build this project and copy the assembly to the bin\debug\design folder of the MyControls project. The design folder will not be there by default..you need to create a folder with that name. For deployment scenarios you must create a design folder on the location of the mycontrols.dll and place the design dll there. 



Consume the control in a project



Create a WPF application and use the two controls created in project MyControls




<Window x:Class="WPFHost.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyControls;assembly=MyControls"
Title="Window1" Height="300" Width="300">
<StackPanel>
<local:MyButton MyHeight="100" Content="MyButton"/>
<local:MyTextBox MyWidth="200"
Text="MyTextBox"
/>
</StackPanel>
</Window>



You are done and you should be able to see a custom property editor in case of the myTextBox control and a custom property editor for myButton as shown below(Note that the dependency property we added for the controls are shown in a separate bucket in the property pages


Design time in Cider











Design time in Blend










No comments:

Post a Comment