引言
我们可以轻易的编写一个附加属性类,增加任意类型的附加属性并编写一定的逻辑来处理附加值的变化通知。假如控件是我们自定义的一个label、button 、textbox等,自定义控件当然是其他基础类型元素的组合,如shape、line、rectangle、geometry等对齐进行了装饰和功能增强,我们不希望重新编写或更改较多的窗体xaml代码,可以使用附加属性继承的方式,将附加属性传递到子元素,通过更改其控件模板实现极大程度上控件外观的重塑。
1、控件模板概述
WPF 中的许多控件使用 ControlTemplate 定义控件的结构和外观,因为它可以将控件的外观和功能区分开来。 重新定义 ControlTemplate 可以极大地更改控件的外观。 例如,假设需要在标签或左上角显示一个小圆点,以不同颜色表示状态。 由于 Label 使用控件模板定义外观,因此很容易重新定义 ControlTemplate 以符合该控件的要求,从而使用Label来制作交通信号灯。
尽管有些时候可以使用 DataTemplate,但在某些时候,光有 DataTemplate 还不够。 DataTemplate 定义控件内容的外观。 当外观要求与默认外观存在很大差异,有必要重新定义 ControlTemplate。 一般情况下,DataTemplate 用于定义控件的内容(或数据),ControlTemplate 用于定义控件的构成方式。
参阅:控件创作概述 - WPF .NET Framework | Microsoft Learn
| UserControl | Xaml代码生成控件,无法使用 DataTemplate 或 ControlTemplate 来自定义其外观。 | 
| Control | 能通过 ControlTemplate 进行自定义,支持不同主题 | 
| FrameworkElement | 对控件的外观进行精确控制,而不仅仅是简单的元素组合提供的效果。可定义自己的呈现逻辑。重写 FrameworkElement 的 OnRender 方法,并提供显式定义组件视觉对象的 DrawingContext 操作。 | 
2、可继承附加属性编写与使用
这里主要使用到 FrameworkPropertyMetadata 类型
官方注解此类派生自 PropertyMetadata () UIPropertyMetadata ,是专门为WPF 框架级应用程序开发目的准备的,此类是对基类PropertyMetadata的补充 ,包含指定或报告 WPF 框架级属性系统行为(如属性继承、数据绑定和布局)的各种布尔属性。
重点关注它的枚举参数类型FrameworkPropertyMetadataOptions
| AffectsArrange | 2 |   更改此依赖属性的值会触发布局组合的排列过程。  | 
| AffectsMeasure | 1 |   更改此依赖属性的值会触发布局组合的测量过程。  | 
| AffectsParentArrange | 8 |   更改此依赖属性的值会触发父元素上的排列过程。  | 
| AffectsParentMeasure | 4 |   更改此依赖属性的值会触发父元素上的测量过程。  | 
| AffectsRender | 16 |   更改此依赖属性的值会触发呈现或布局组合的某一方面(不是测量或排列过程)。  | 
| BindsTwoWayByDefault | 256 |   此依赖属性上的数据绑定的 BindingMode 默认为 TwoWay。  | 
| Inherits | 32 |   此依赖属性的值将由子元素继承。容器控件嵌套  | 
| Journal | 1024 |   此依赖属性的值应由日记记录进程或在由统一资源标识符 (URI) 导航时进行保存或存储。  | 
| None | 0 |   未指定任何选项;依赖属性使用 WPF 属性系统的默认行为。  | 
| NotDataBindable | 128 |   不允许将数据绑定到此依赖属性。  | 
| OverridesInheritanceBehavior | 64 |   此依赖属性的值跨越分隔的树以实现属性值继承。  | 
| SubPropertiesDoNotAffectRender | 2048 |   此依赖属性值上的子属性不会影响呈现的任何方面。  | 
如果不用继承,在更改了控件样式之后,我们通过附加属性更改其外观,则需要逐项去修改附加属性的值。如下:
<StackPanel wh:StatusDotElement.DotBrush="Red" wh:StatusDotElement.Radius="5">
    <Label Style="{DynamicResource StatusDotLabel}" Content="label1" wh:StatusDotElement.DotBrush="Red"/>
    <Label Style="{DynamicResource StatusDotLabel}" Content="label2" wh:StatusDotElement.DotBrush="Red"/>
    <Label Style="{DynamicResource StatusDotLabel}" Content="label3" wh:StatusDotElement.DotBrush="Red"/>
    <Label Style="{DynamicResource StatusDotLabel}" Content="label4" wh:StatusDotElement.DotBrush="Red"/>
</StackPanel> 
这里重新定义了Label控件模板,附加属性只提供值进行绑定。在左上角添加了一个小圆点。
<ControlTemplate TargetType="Label">
    <hc:SimplePanel>
.....此处省略
        <Ellipse
            Margin="5"
            HorizontalAlignment="Left"
            VerticalAlignment="Top"
            Fill="{Binding Path=(attach:StatusDotElement.DotBrush), RelativeSource={RelativeSource TemplatedParent}}"
            Height="{Binding Path=(attach:StatusDotElement.Radius), RelativeSource={RelativeSource TemplatedParent}}"
            Width="{Binding Path=(attach:StatusDotElement.Radius), RelativeSource={RelativeSource TemplatedParent}}" />
    </hc:SimplePanel>
</ControlTemplate> 

使用了继承之后只需设置一次:
<StackPanel wh:StatusDotElement.DotBrush="Red" wh:StatusDotElement.Radius="5">
    <Label Style="{DynamicResource StatusDotLabel}" Content="label1"/>
    <Label Style="{DynamicResource StatusDotLabel}" Content="label2"/>
    <Label Style="{DynamicResource StatusDotLabel}" Content="label3"/>
    <Label Style="{DynamicResource StatusDotLabel}" Content="label4"/>
</StackPanel>









