Walkthrough: Creating a Category Editor

The extensibility model for the WPF Designer for Visual Studio allows you to create custom editors for categories of properties known as category editors. Category editors allow you to provide a custom user interface that allows users to edit related properties that belong to a single category, such as text-related properties. In this walkthrough, you will build a category editor that allows users to edit the text-related properties of a control.

In this walkthrough, you perform the following tasks:

  • Create a WPF custom control project.

  • Create a category editor that can be used to edit the text-related properties of that control.

  • Create a class that inherits from CategoryEditor that represents the category editor for the control.

  • Create a class that implements the IProvideAttributeTable interface to register your new extended editor.

  • Test the category editor at design time.

Prerequisites

You need the following components to complete this walkthrough:

  • Visual Studio 2010.

Creating the Custom Control

The first step is to create the project for the custom control. The control is a simple button with small amount of design-time code, which uses the GetIsInDesignMode method to implement a design-time behavior.

To create the custom control

  1. Create a new WPF Custom Control Library project in Visual C# named CustomControlLibrary.

    The code for CustomControl1 opens in the Code Editor.

  2. In the Code Editor for CustomControl1, replace the code in the CustomControlLibrary namespace with the following code:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Documents;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Imaging;
    using System.Windows.Navigation;
    using System.Windows.Shapes;
    
    namespace CustomControlLibrary
    {
        public class CustomControl1 : Button
        {
            public CustomControl1()
            {
                if (System.ComponentModel.DesignerProperties.GetIsInDesignMode(this))
                {
                    Content = "In design mode";
                }
            }
        }
    }
    
  3. Set the project's output path to "bin\".

  4. Build the solution.

Creating a Class to Encapsulate Property Information

The category editor that you will create requires some information about fonts and associated properties, so you will create a class that encapsulates that information. This class will be used as a data source by your category editor.

To create a class that encapsulates font property information

  1. Add a new WPF Custom Control Library project in Visual C# named CustomControlLibrary.Design to the solution.

    The code for CustomControl1 opens in the Code Editor.

  2. In Solution Explorer, delete the CustomControl1 file from the CustomControlLibrary.Design project.

  3. In Solution Explorer, delete the Themes folder from the CustomControlLibrary.Design project.

  4. Add a reference to the following WPF Designer assemblies.

    • Microsoft.Windows.Design.Extensibility

    • Microsoft.Windows.Design.Interaction

  5. Add a reference to the CustomControlLibrary project.

  6. Set the project's output path to "..\CustomControlLibrary\bin\". This keeps the control's assembly and the metadata assembly in the same folder, which enables metadata discovery for designers.

  7. Add a new class named FontList to the CustomControlLibrary.Design project.

  8. In the Code Editor for FontList, replace the automatically generated code with the following code.

    using System;
    using System.Linq;
    using System.Collections.Generic;
    using System.Text;
    using System.Windows.Media;
    using System.Collections.ObjectModel;
    using System.Windows;
    using System.Windows.Data;
    using System.Globalization;
    
    namespace CustomControlLibrary.Design
    {
        public class FontList : ObservableCollection<FontFamily>
        {
            public FontList()
            {
                foreach (FontFamily ff in Fonts.SystemFontFamilies)
                {
                    Add(ff);
                }
            }
        }
    
        public class FontSizeList : ObservableCollection<double>
        {
            public FontSizeList()
            {
                Add(8);
                Add(9);
                Add(10);
                Add(11);
                Add(12);
                Add(14);
                Add(16);
                Add(18);
                Add(20);
            }
        }
    
        public class FontStyleConverter : IValueConverter
        {
            public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
            {
                FontStyle fs = (FontStyle)value;
                return fs == FontStyles.Italic;
            }
    
            public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
            {
                if (value != null)
                {
                    bool isSet = (bool)value;
    
                    if (isSet)
                    {
                        return FontStyles.Italic;
                    }
                }
    
                return FontStyles.Normal;
            }
        }
    
        public class FontWeightConverter : IValueConverter
        {
            public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
            {
                FontWeight fs = (FontWeight)value;
                return fs == FontWeights.Bold;
            }
    
            public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
            {
                if (value != null)
                {
                    bool isSet = (bool)value;
    
                    if (isSet)
                    {
                        return FontWeights.Bold;
                    }
                }
    
                return FontWeights.Normal;
            }
        }
    
    }
    

Creating the Template for the Category Editor

The category editor will be created by using a XAML data template. This will be a simple user interface that binds to several text-related properties.

To create the template for the category editor

  1. Add a new class named EditorResources to the CustomControlLibrary.Design project.

  2. In the Code Editor for EditorResources, replace the automatically generated code with the following code.

    namespace CustomControlLibrary.Design
    {
        using System;
        using System.Collections.Generic;
        using System.Text;
        using System.Windows;
        public partial class EditorResources : ResourceDictionary
        {
            public EditorResources()
                : base()
            {
                InitializeComponent();
            }
        }
    }
    
  3. From the Project menu, click Add Resource Dictionary.

  4. Name the file EditorResources.xaml and click Add.

  5. In XAML view for EditorResources, replace the automatically generated XAML with the following XAML.

        <ResourceDictionary
        xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:PropertyEditing="clr-namespace:Microsoft.Windows.Design.PropertyEditing;assembly=Microsoft.Windows.Design.Interaction"
        xmlns:Local="clr-namespace:CustomControlLibrary.Design"
        xmlns:Media="clr-namespace:System.Windows.Media;assembly=PresentationCore"
        xmlns:sys="clr-namespace:System;assembly=mscorlib"
        x:Class="CustomControlLibrary.Design.EditorResources">
        <Local:FontList x:Key="FontFamilyList"/>
        <Local:FontSizeList x:Key="FontSizeList"/>
        <Local:FontStyleConverter x:Key="FontStyleConverter"/>
        <Local:FontWeightConverter x:Key="FontWeightConverter"/>
        <DataTemplate x:Key="TextCategoryEditorTemplate">
            <StackPanel Margin="5">
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="1*"/>
                        <ColumnDefinition Width="50"/>
                    </Grid.ColumnDefinitions>
                    <ComboBox 
                        Grid.Column="0"
                        Margin="2"
                        ItemsSource="{Binding Source={StaticResource FontFamilyList}}" 
                        SelectedItem="{Binding [FontFamily].PropertyValue.Value}"/>
                    <ComboBox 
                        Grid.Column="1"
                        Margin="2"
                        ItemsSource="{Binding Source={StaticResource FontSizeList}}"
                        SelectedItem="{Binding [FontSize].PropertyValue.Value}"/>
                </Grid>
                <StackPanel Orientation="Horizontal">
                    <CheckBox 
                        Margin="2"
                        Content="Bold"
                        IsChecked="{Binding Path=[FontWeight].PropertyValue.Value, Converter={StaticResource FontWeightConverter}}"/>
                    <CheckBox 
                        Margin="2"
                        Content="Italic"
                        IsChecked="{Binding Path=[FontStyle].PropertyValue.Value, Converter={StaticResource FontStyleConverter}}"/>
                </StackPanel>
            </StackPanel>
        </DataTemplate>
    </ResourceDictionary>
    
  6. Build the solution.

Encapsulating the Template and Registering the Category Editor

Now that you have created the template for your category editor, you must create a class that inherits CategoryEditor to use the template as a custom editor, and you must register the new category editor.

To encapsulate and register your category editor

  1. Add a new class named TextCategoryEditor to the CustomControlLibrary.Design project.

  2. In the Code Editor for TextCategoryEditor, replace the automatically generated code with the following code.

    namespace CustomControlLibrary.Design
    {
        using System;
        using System.Collections.Generic;
        using System.Text;
        using System.Windows;
        using System.Windows.Controls;
        using System.Windows.Data;
        using Microsoft.Windows.Design.PropertyEditing;
    
        public class TextCategoryEditor : CategoryEditor
        {
    
            private EditorResources res = new EditorResources();
            public TextCategoryEditor()
            {
            }
    
            public override bool ConsumesProperty(PropertyEntry property)
            {
                return true;
            }
    
            public override DataTemplate EditorTemplate
            {
                get
                {
                    return res["TextCategoryEditorTemplate"] as DataTemplate;
                }
            }
    
            public override object GetImage(Size desiredSize)
            {
                return null;
            }
    
            public override string TargetCategory
            {
                get { return "Text"; }
            }
        }
    }
    
  3. Add a new class named Metadata to the CustomControlLibrary.Design project.

  4. In the Code Editor for Metadata, replace the automatically generated code with the following code.

    using System;
    using System.Collections.Generic;
    using System.Text;
    using Microsoft.Windows.Design.Metadata;
    using System.ComponentModel;
    using Microsoft.Windows.Design.PropertyEditing;
    using System.Windows.Media;
    using System.Windows.Controls;
    using System.Windows;
    using CustomControlLibrary;
    
    // The ProvideMetadata assembly-level attribute indicates to designers
    // that this assembly contains a class that provides an attribute table. 
    [assembly: ProvideMetadata(typeof(CustomControlLibrary.Design.Metadata))]
    
    namespace CustomControlLibrary.Design
    {
        // Container for any general design-time metadata to initialize.
        // Designers look for a type in the design-time assembly that 
        // implements IProvideAttributeTable. If found, designers instantiate 
        // this class and access its AttributeTable property automatically.
        internal class Metadata : IProvideAttributeTable
        {
            // Accessed by the designer to register any design-time metadata.
            public AttributeTable AttributeTable
            {
                get
                {
                    AttributeTableBuilder builder = new AttributeTableBuilder();
                    builder.AddCustomAttributes
                        (typeof(CustomControl1),
                        new EditorAttribute(
                            typeof(TextCategoryEditor), 
                            typeof(TextCategoryEditor)));
                    return builder.CreateTable();
                }
            }
        }
    }
    
  5. Build the solution.

Testing the Category Editor

Your category editor is now complete and ready to use. All that remains is to test it. To test the category editor, you will add a WPF application to your project, add the custom control to your WPF application, and view the category editor in action.

To test the category editor

  1. Add a new WPF Application project in Visual C# named DemoApplication to the solution.

    MainWindow.xaml opens in the WPF Designer.

  2. Add a reference to the CustomControlLibrary project.

  3. In XAML view for MainWindow.xaml, replace the automatically generated XAML with the following XAML. This XAML adds a reference to the CustomControlLibrary namespace and adds the CustomControl1 custom control. The button appears in Design view with text indicating that it is in design mode. If the button does not appear, you might have to click the Information bar at the top of the designer to reload the view.

        <Window x:Class="DemoApplication.MainWindow"
        xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
        Title="Window1" Height="300" Width="300" xmlns:my="clr-namespace:CustomControlLibrary;assembly=CustomControlLibrary">
        <Grid>
            <my:CustomControl1 Margin="30,30,30,30" Name="customControl11"></my:CustomControl1>
        </Grid>
    </Window>
    
  4. In Design view, select the control.

  5. In the Properties window, find the Text category.

    You should see a user interface for specifying Text properties that is different than other controls. You can select a font name and a font size from drop-down lists. You can specify bold and italic by selecting check boxes.

  6. Make changes to the properties represented in this category. Note that they changes are reflected in the control.

See Also

Tasks

Walkthrough: Implementing a Color Editor

How to: Create a Value Editor

Other Resources

Creating Custom Editors

WPF Designer Extensibility