WPF自定义控件

参考自:http://www.cnblogs.com/zhouyinhui/archive/2007/12/01/979715.html

1. User ControlCustom Control

   在新建一个Project的时候,WPF提供了2种自定义控件的模板:WPF User Control LibraryWPF Custom Control LibraryUser Control就像是winform中的自定义控件方式,可以理解为将多个已有的控件拼凑起来,在后台代码中直接访问这些子元素,缺点是对模板和样式的支持不好。它自动的从System.Windows.Controls.UserControl继承。

   Custom Control,其开发出来的控件才真正具有WPF风格,其对模板样式有着很好的支持,这是因为打造CustomControl时做到了逻辑代码与外观相分离,即使换上一套完全不同的视觉树其同样能很好的工作,就像WPF内置的控件一样。
  
在使用Visual Studio打造控件时,UserControlCustomControl的差别就更加明显,在项目中添加一个UserControl时,我们会发现设计器为我们添加了一个XAML文件以及一个对应的.CS文件,然后你就可以像设计普通窗体一样设计该UserControl;如果我们是在项目中添加CustomControl,情况却不是这样,设计器会为我们生成一个.CS文件,该文件用于编写控件的后台逻辑,而控件的外观却定义在了软件的应用主题(Theme)中了(如果你没有为软件定义通用主题,其会自动生成一个通用主题themes\generic.xaml,然后主题中会自动为你的控件生成一个Style),并将通用主题与该控件关联了起来。这也就是CustomControl对样式的支持度比UserControl好的原因。

generic.xaml中我们需要定义该控件的默认主题,这就像是WPF内置的控件一样,他们有自己的默认的主题。然后我们需要做的工作就是在.cs文件中编写后台逻辑,在generic.xaml中编写默认的UI

CustomControl会从System.Windows.Controls.Control继承,如下:

    public class CustomControl1 : Control

    {

        static CustomControl1()

        {

            DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomControl1), new FrameworkPropertyMetadata(typeof(CustomControl1)));

        }

}

它总有一个Static的构造方法,并自动调用DefaultStyleKeyProperty.OverrideMetadata 方法。DefaultStyleKey是指当有theme style定义或应用的时候,用它来指定该控件的默认样式,为该metadatadefaultvalue指定为typeof(CustomControl1),这个值代表着,我们将在资源字典中查找一个键值为typeof(CustomControl1)Style来做为控件的默认样式.而这个样式刚好被我们定义在了Generic.xaml:

    <Style TargetType="{x:Type local:CustomControl1}">

        <Setter Property="Template">

            <Setter.Value>

                <ControlTemplate TargetType="{x:Type local:CustomControl1}">

                    <Border Background="{TemplateBinding Background}"

                            BorderBrush="{TemplateBinding BorderBrush}"

                            BorderThickness="{TemplateBinding BorderThickness}">

 

                    </Border>

                </ControlTemplate>

            </Setter.Value>

        </Setter>

    </Style>

这是大家可能有个疑问,上面XAML中的Style并没有指定Key值啊,而我们的控件要求的默认样式Key值为typeof(CustomControl1),并且资源字典中的元素肯定是要有Key的? 这是Style的基本知识了,在WPF中,为Style指定Key时有两种方式:一是明确指定Key,而是在没有明确指定Key的情况下指定TargetTypeWPF会自动地将其可Key设置为typeof(TargetType)

另外,如果不希望element或者control用默认的主题style,可以设置OverridesDefaultStyletrue

2. 使用TemplatePartAttribute特性

   TemplatePartAttribute
用在一个控件类的定义前面,控件的作者通过它来告诉template的作者在控件的模板中必须有TemplatePart中指定的类型和名称的元素,这个控件才能正常工作。当然该特性只是为了标识这一个行为,如果不写,也不会报错的。

   例如我们看WPF Toolkit中的Calendar类,定义如下:

    [TemplatePart(Name = Calendar.ElementRoot, Type = typeof(Panel))]

    [TemplatePart(Name = Calendar.ElementMonth, Type = typeof(CalendarItem))]

    public partial class Calendar : Control

    {

        #region Constants

 

        private const string ElementRoot = "PART_Root";

        private const string ElementMonth = "PART_CalendarItem";

}

 

   generci.xaml中可以看到如何使用:

<Setter Property="Template">

            <Setter.Value>

                <ControlTemplate TargetType="local:Calendar">

                    <StackPanel Name="PART_Root" HorizontalAlignment="Center">

                        <primitives:CalendarItem

                            Name="PART_CalendarItem"

                            Style="{TemplateBinding CalendarItemStyle}"

                            Background="{TemplateBinding Background}"

                            BorderBrush="{TemplateBinding BorderBrush}"

                            BorderThickness="{TemplateBinding BorderThickness}"                           

                            />

                    </StackPanel>

                </ControlTemplate>

            </Setter.Value>

        </Setter>
另外我们可以在OnApplyTemplate 通过GetTemplateChild方法来得到相应的元素,注意GetTemplateChild方法过时了,用System.Windows.FrameworkTemplate.FindName(System.String,System.Windows.FrameworkElement)代替它。

      public override void OnApplyTemplate()

        {

            if (_monthControl != null)

            {

                _monthControl.Owner = null;

            }

 

            base.OnApplyTemplate();

 

            _monthControl = GetTemplateChild(ElementMonth) as CalendarItem;

 

            if (_monthControl != null)

            {

                _monthControl.Owner = this;

            }

 

            this.CurrentDate = this.DisplayDate;

            UpdateCellItems();

        }

 

   为了实现效果随着用户操作系统主题改变而动态改变,你至少有两种方法来实现:(1)监听系统消息WM_THEMECHANGE,然后切控件界面.(2)将系统主题对应的Style放置在控件解决方案的themes文件夹下,比如与Vista Aero向对应的放在themes\Aero.NormalColor.xaml,与蓝色的Windows XP主题对应的放在themes\Luna.NormalColor.xaml,Window经典主题相对应的放在themes\Classic.xmal,相信大家已经看出规律:themes\主题名.颜色名.xaml,其中经典主题没有颜色名.这样当用户切换主题时我们的控件就会切换到对应的Style,如果我们没有提供用户当前的主题所对应的样式则调用themes\Generic.xaml

 

   另外需要说明的是AssemblyInfo中的ThemeInfoAttribute,结构大概如下:

[assembly:ThemeInfo(

    // Specifies the location of theme specific resources

    ResourceDictionaryLocation.SourceAssembly,

    // Specifies the location of non-theme specific resources:

ResourceDictionaryLocation.SourceAssembly)]

 

当一个Assembly被该特性标识后,我们可以称它为Themed assembly,注意ResourceDictionaryLocation有三个枚举值,NoneSourceAssemblyExternalAssemblyNone指不用themesSourceAssembly指定ResourceDictionaries存在于将要被themed的控件所在的Assembly中;ExternalAssemblyResourceDictionaries存在于其他程序集中,但不包括将要被themed的控件所在的Assembly

ThemeInfo的第一个参数指定和操作系统主题相关的ResourceDictionaries,如果为None则表示不随操作系统的主题变化而变化。第二个参数指generic主题所在位置,也就是generic.xaml,注意第一个参数的优先级高,也就是说如果没有找到和操作系统相应的主题,才会去找有没有generic.xaml

这里有一个例子,参考自http://www.cnblogs.com/yuxs/archive/2007/05/24/758863.html,是一个WPF 的日历控件,点击这里下载我的测试solution。

原文地址:https://www.cnblogs.com/bear831204/p/1360757.html