演练:创建类别编辑器

使用 适用于 Visual Studio 的 WPF 设计器的扩展性模型,可以为各个属性类别创建自定义编辑器,这种编辑器称为类别编辑器。 使用类别编辑器,您可以提供一个自定义用户界面,使用户能够编辑属于同一个类别的相关属性,例如文本相关属性。 在本演练中,将生成一个类别编辑器,使用户能够编辑控件的文本相关属性。

在本演练中,您将执行下列任务:

  • 创建 WPF 自定义控件项目。

  • 创建可用来编辑该控件的文本相关属性的类别编辑器。

  • 创建一个从 CategoryEditor 继承的类,该类表示控件的类别编辑器。

  • 创建一个用来实现 IProvideAttributeTable 接口的类,以注册新的扩展编辑器。

  • 在设计时测试类别编辑器。

系统必备

您需要以下组件来完成本演练:

  • Visual Studio 2010.

创建自定义控件

第一步是为自定义控件创建项目。 该控件是一个带有少量设计时代码的简单按钮,该按钮使用 GetIsInDesignMode 方法来实现设计时行为。

创建自定义控件

  1. 使用 Visual C# 创建一个名为 CustomControlLibrary 的新 WPF 自定义控件库项目。

    CustomControl1 的代码在“代码编辑器”中打开。

  2. 在 CustomControl1 的代码编辑器中,用下面的代码替换 CustomControlLibrary 命名空间中的代码:

    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. 将项目的输出路径设置为“bin\”。

  4. 生成解决方案。

创建封装属性信息的类

将要创建的类别编辑器需要一些有关字体和相关属性的信息,因此将创建一个封装这些信息的类。 类别编辑器将此类用作数据源。

创建封装字体属性信息的类

  1. 使用 Visual C# 向解决方案中添加一个名为 CustomControlLibrary.Design 的新 WPF 自定义控件库项目。

    CustomControl1 的代码在“代码编辑器”中打开。

  2. 在**“解决方案资源管理器”**中,将 CustomControl1 文件从 CustomControlLibrary.Design 项目中删除。

  3. 在“解决方案资源管理器”中,将 Themes 文件夹从 CustomControlLibrary.Design 项目中删除。

  4. 添加对以下 WPF 设计器程序集的引用。

    • Microsoft.Windows.Design.Extensibility

    • Microsoft.Windows.Design.Interaction

  5. 添加对 CustomControlLibrary 项目的引用。

  6. 将项目的输出路径设置为“.. \CustomControlLibrary\bin\”。 这样可使控件的程序集与元数据程序集位于同一文件夹中,从而可为设计器启用元数据发现。

  7. 向 CustomControlLibrary.Design 项目中添加一个名为 FontList 的新类。

  8. 在 FontList 的代码编辑器中,用下面的代码替换自动生成的代码。

    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;
            }
        }
    
    }
    

为类别编辑器创建模板

类别编辑器将使用 XAML 数据模板创建。 这将是一个绑定到多个文本相关属性的简单用户界面。

为类别编辑器创建模板

  1. 向 CustomControlLibrary.Design 项目中添加一个名为 EditorResources 的新类。

  2. 在 EditorResources 的代码编辑器中,用下面的代码替换自动生成的代码。

    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. 在**“项目”菜单中,单击“添加资源字典”**。

  4. 将该文件命名为 EditorResources.xaml 并单击**“添加”**。

  5. 在 EditorResources 的 XAML 视图中,用下面的 XAML 替换自动生成的 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. 生成解决方案。

封装模板并注册类别编辑器

在为类别编辑器创建了模板之后,现在必须创建一个继承自 CategoryEditor 的类以便将该模板用作自定义编辑器,而且必须注册新的类别编辑器。

封装并注册类别编辑器

  1. 向 CustomControlLibrary.Design 项目中添加一个名为 TextCategoryEditor 的新类。

  2. 在 TextCategoryEditor 的代码编辑器中,用下面的代码替换自动生成的代码。

    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. 向 CustomControlLibrary.Design 项目中添加一个名为 Metadata 的新类。

  4. 在 Metadata 的代码编辑器中,用下面的代码替换自动生成的代码。

    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. 生成解决方案。

测试类别编辑器

类别编辑器现在已经完成,可以开始使用了。 剩下的工作就是对其进行测试。 若要测试类别编辑器,需要向项目中添加一个 WPF 应用程序,向该 WPF 应用程序中添加自定义控件,并查看操作中的类别编辑器。

测试类别编辑器

  1. 使用 Visual C# 向解决方案中添加一个名为 DemoApplication 的新 WPF 应用程序项目。

    MainWindow.xaml 将在 WPF 设计器中打开。

  2. 添加对 CustomControlLibrary 项目的引用。

  3. 在 MainWindow.xaml 的 XAML 视图中,用下面的 XAML 替换自动生成的 XAML。 此 XAML 将添加对 CustomControlLibrary 命名空间的引用并添加 CustomControl1 自定义控件。 该按钮出现在“设计”视图中,并显示相应文本,表明该按钮处于设计模式下。 如果该按钮未出现,则可能需要单击设计器顶部的信息栏以重新加载视图。

        <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. 在设计视图中选择该控件。

  5. 在**“属性”窗口中,找到“文本”**类别。

    您应该看到一个用于指定文本属性的用户界面,并且它不同于其他控件。 可以从下拉列表中选择字体名称和字体大小。 可通过选择复选框来指定粗体和斜体。

  6. 对此类别中表示的属性进行更改。 请注意,它们的更改反映在控件中。

请参见

任务

演练:实现颜色编辑器

如何:创建值编辑器

其他资源

创建自定义编辑器

WPF 设计器扩展性