WPF程序设计基础:属性系统
Windows Presentation Foundation (WPF) 提供了一组服务,这些服务可用于扩展公共语言运行库 (CLR) 属性的功能。这些服务通常统称为 WPF 属性系统。由 WPF 属性系统支持的属性称为依赖项属性。本概述介绍 WPF 属性系统以及依赖项属性的功能,这包括如何在可扩展应用程序标记语言 (XAML) 中和代码中使用现有的依赖项属性。本概述还介绍了依赖项属性所特有的方面(如依赖项属性元数据),并说明了如何在自定义类中创建自己的依赖项属性。
对于Silverlight 学习者,这些也都是同样适用的,并且将有很大的帮助。
先决条件
本主题假设您在 CLR 和面向对象的编程方面有一些基础知识。若要采用本主题中的示例,还应当了解 XAML 并知道如何编写 WPF 应用程序。
关于后者,你可以笔者早期学习文章:http://www.cnblogs.com/Dlonghow/archive/2008/09/01/1246786.html
(WPF程序设计 :第一章 应用程序和窗口(The Application and the Window) )
一、属性
依赖项属性和 CLR 属性
在 WPF 中,属性通常公开为公共语言运行库 (CLR) 属性。在基本级别,您可以在根本不知道这些属性实现为依赖项属性的情况下直接与它们交互。但是,您应当熟悉 WPF 属性系统的部分或全部功能,才能利用这些功能。
依赖项属性的用途在于提供一种方法来基于其他输入的值计算属性值。这些其他输入可以包括系统属性(如主题和用户首选项)、实时属性确定机制(如数据绑定和动画/演示图板)、重用模板(如资源和样式)或者通过与元素树中其他元素的父子关系来公开的值。另外,可以通过实现依赖项属性来提供独立验证、默认值、监视其他属性的更改的回调以及可以基于可能的运行时信息来强制指定属性值的系统。派生类还可以通过重写依赖项属性元数据(而不是重写现有属性的实际实现或者创建新属性)来更改现有属性的某些具体特征。
在 SDK 参考中,可以根据某个属性的托管引用页上是否存在“依赖项属性信息”部分来确定该属性是否为依赖项属性。“依赖项属性信息”部分包括一个指向该依赖项属性的 DependencyProperty 标识符字段的链接,还包括一个为该属性设置的元数据选项的列表、每个类的重写信息以及其他详细信息。
依赖项属性支持 CLR 属性
依赖项属性和 WPF 属性系统通过提供一个支持属性的类型来扩展属性功能,这是使用私有字段支持该属性的标准模式的替代实现方法。该类型的名称是 DependencyProperty。定义 WPF 属性系统的另一个重要类型是 DependencyObject。DependencyObject 定义可以注册和拥有依赖项属性的基类。
下面汇集了在本软件开发工具包 (SDK) 文档中,在讨论依赖项属性时所使用的术语:
依赖项属性:一个由 DependencyProperty 支持的属性。
依赖项属性标识符:一个 DependencyProperty 实例,在注册依赖项属性时作为返回值获得,之后将存储为一个类成员。在与 WPF 属性系统交互的许多 API 中,此标识符用作一个参数。
CLR“包装”:属性的实际 get 和 set 实现。这些实现通过在 GetValue 和 SetValue 调用中使用依赖项属性标识符来合并此标识符,从而使用 WPF 属性系统为属性提供支持。
下面的示例定义 IsSpinning 依赖项属性,并说明 DependencyProperty 标识符与它所支持的属性之间的关系。
{
public ccButton()
: base()
{ }
// 定义名称为Id,类型为string的属性
public static readonly DependencyProperty Property = IdDependencyProperty.Register("Id", typeof(string), typeof(ccButton), new PropertyMetadata(string.Empty));
public string Id
{
get
{
return (string)base.GetValue(IdProperty);
}
set
{
base.SetValue(IdProperty, value);
}
}
}
那么,你在脚本代码中就可以使用这个自定义控件及其属性了:
尤其要注意的是:属性以及支持它的 DependencyProperty 字段的命名约定非常重要。字段总是与属性同名,但其后面追加了 Property 后缀。
如果简单得或是想当然的定义为如下形式,是不符合 依赖项属性的要求,也将不能够得到有效使用。
public class ccButton_1 : Button
{
public ccButton_1()
: base()
{ }
public string Id
{
get;
set;
}
}
二、设置属性值
可以在代码或 XAML 中设置属性。
(1)、在 XAML 中设置属性值
下面的 XAML 示例将按钮的背景色指定为红色。
该示例演示了这样一种情况:在所生成的代码中,XAML 加载器将 XAML 属性的简单字符串值的类型转换为 WPF 类型(一种 Color,通过 SolidColorBrush)。
XAML 支持各种设置属性的语法格式。要对特定的属性使用哪种语法取决于该属性所使用的值类型以及其他因素(例如,是否存在类型转换器)。
作为非属性语法的示例,下面的 XAML 示例显示了另一种按钮背景。这一次不是设置简单的纯色,而是将背景设置为图像,用一个元素表示该图像并将该图像的源指定为嵌套元素的属性。这是属性元素语法的示例。
<Button.Background>
<ImageBrush ImageSource="Face.jpg"/>
</Button.Background>
</Button>
在代码中设置属性
在代码中设置依赖项属性值通常只是调用由 CLR“包装”公开的 set 实现。
myButton.Width=200.00;//200 200.0
获取属性值实质上也是在调用 get“包装”实现:
您还可以直接调用属性系统 API GetValue 和 SetValue。如果您使用的是现有属性,则上述操作通常不是必需的(使用包装会更方便,并能够更好地向开发人员工具公开属性)。但是在某些情况下适合直接调用 API。
还可以在 XAML 中设置属性,然后通过代码隐藏在代码中访问这些属性。(这句话很重要哦,就像前面定义的那个常常Button一样,你就可以Bingding上一个值,用于隐藏一个特定的值,以实现你得目的,如你可以在ListBox的ItemTemplate中用上这个Button,点击Button就可以得到它了,也就相当于得到整条记录的标识...)
三、由依赖项属性提供的属性功能
依赖项属性提供用来扩展属性功能的功能,这与字段支持的属性相反。每个这样的功能通常都表示或支持整套 WPF 功能中的特定功能:
资源
数据绑定
样式
动画
元数据重写
属性值继承
WPF 设计器集成
资源
依赖项属性值可以通过引用资源来设置。资源通常指定为页面根元素或应用程序的子元素,通过这些位置可以最方便地访问资源。下面的示例演示如何定义 SolidColorBrush 资源。
<SolidColorBrush x:Key="baseColor" Color="Blue"/>
</UserControl.Resources>
在定义了某个资源之后,可以引用该资源并使用它来提供属性值:
这个特定的资源称为 StaticResource 标记扩展(在 XAML 中,可以使用静态或动态资源(DynamicResource)引用)。若要使用动态资源引用,必须设置为依赖项属性,因此它是由 WPF 属性系统明确启用的动态资源引用用法。
说明:
资源被视为本地值,这意味着,如果您设置另一个本地值,该资源引用将被消除。
数据绑定
依赖项属性可以通过数据绑定来引用值。数据绑定通过特定的标记扩展语法(在 XAML 中)或 Binding 对象(在代码中)来工作。使用数据绑定,最终属性值的确定将延迟到运行时,在运行时,将从数据源获取属性值。
下面的示例在 XAML 中使用一个绑定,为 Button 设置 Content 属性。该绑定使用一个继承的数据上下文和一个 XmlDataProvider 数据源(未显示出来)。绑定本身通过数据源中的 XPath 指定所需的源属性。
<Button x:Name="hehe" Content="{Binding XPath=Team/@TeamName}"/>
说明:
绑定被视为本地值,这意味着,如果您设置另一个本地值,该绑定将被消除。
依赖项属性或 DependencyObject 类本身并不支持 INotifyPropertyChanged,以便为数据绑定操作生成有关 DependencyObject 源属性值变化的通知。
四、样式
样式和模板是使用依赖项属性的两个主要激发方案。在设置定义应用程序用户界面 (UI) 的属性时,样式尤其有用。样式在 XAML 中通常定义为资源。样式与属性系统交互,因为它们通常包含特定属性的“setter”,以及基于另一个属性的实时值更改属性值的“trigger”。
下面的示例创建一个非常简单的样式(该样式将在 Resources 字典中定义,未显示出来),然后将该样式直接应用于 Button 的 Style 属性。样式中的 setter 将带样式的 Button 的 Background 属性设置为 green。
<Setter Property="Background" Value="Green"/>
</Style>
使用该样式(Style)
五、动画
可以对依赖项属性进行动画处理。在应用和运行动画时,经过动画处理的值的操作优先级将高于该属性以其他方式具有的任何值(如本地值)。
下面的示例对 Button 属性的 Background 进行动画处理(在技术上,Background 是通过使用属性元素语法将空白 SolidColorBrush 指定为 Background 来进行动画处理的,之后,该 SolidColorBrush 的 Color 属性就是直接进行动画处理的属性)。
<Button.Background>
<SolidColorBrush x:Name="AnimBrush"/>
</Button.Background>
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Loaded">
<BeginStoryboard>
<Storyboard>
<ColorAnimation
Storyboard.TargetName="AnimBrush"
Storyboard.TargetProperty="{SolidColorBrush.Color}"
From="Red" To="Green" Duration="0:0;5"
AutoReverse="True" RepeatBehavior="Forever"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>
</Button>
六、元数据重写
在从最初注册依赖项属性的类派生时,可以通过重写依赖项属性的元数据来更改该属性的某些行为。对元数据的重写依赖于 DependencyProperty 标识符。重写元数据不需要重新实现属性。元数据的变化是由属性系统在本机处理的;对于所有从基类继承的属性,每个类都有可能基于每个类型保留元数据。
你可以试着重写依赖项属性 DefaultStyleKey 的元数据。重写这个特定的依赖项属性的元数据是某个实现模式的一部分,该模式创建可以使用主题中的默认样式的控件。
关于如何重写笔者也暂时没有体验过,暂时不得法。
七、属性值继承
元素可以从其在树中的父级继承依赖项属性的值。
说明:
属性值继承行为并未针对所有的依赖项属性在全局启用,因为继承的计算时间确实会对性能产生一定的影响。属性值继承通常只有在特定方案指出适合使用属性值继承时才对属性启用。可以通过在 SDK 参考中查看某个依赖项属性的“依赖项属性信息”部分,来确定该依赖项属性是否继承属性值。
下面的示例演示一个绑定,并设置指定绑定(在前面的绑定示例中未显示出来)的源的 DataContext 属性。DataContext 属性的值继承,因此子元素中的任何后续绑定都不必遵守在父级 StackPanel 元素中指定为 DataContext 的源。
<Button Content="{Binding XPath=name}"/>
</StackPanel>
WPF 设计器集成
如果自定义控件具有实现为依赖项属性的属性,则它将收到相应的 Visual Studio Windows Presentation Foundation (WPF) 设计器支持。一个示例就是能够在“属性”窗口中编辑直接依赖项属性和附加依赖项属性。
依赖项属性值优先级
当您获取依赖项属性的值时,可能会获得通过其他参与 WPF 属性系统且基于属性的任一输入而在该属性上设置的值。由于存在依赖项属性值优先级,使得属性获取值的方式的各种方案得以按可预测的方式交互。
下面示例在定义了一个全局样式(针对TextBlock,凡是用到的TextBlock,将默认使用该样式,而不用添加Style=... 代码)后,又定义了TextBlock的样式,继承自上一样式,同时定义了更多。你可以基于这两个样式,做个Demo看看效果。
<!--A Style that affects all TextBlocks-->
<Style TargetType="TextBlock">
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="FontFamily" Value="Comic Sans MS"/>
<Setter Property="FontSize" Value="14"/>
</Style>
<!--</Snippettb1>-->
<!--<Snippettb2>-->
<!--A Style that extends the previous TextBlock Style-->
<!--This is a "named style" with an x:Key of TitleText-->
<Style BasedOn="{StaticResource {x:Type TextBlock}}"
TargetType="TextBlock"
x:Key="TitleText">
<Setter Property="FontSize" Value="26"/>
<Setter Property="Foreground">
<Setter.Value>
<LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1">
<LinearGradientBrush.GradientStops>
<GradientStop Offset="0.0" Color="#90DDDD" />
<GradientStop Offset="1.0" Color="#5BFFFF" />
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Setter.Value>
</Setter>
</Style>
说明:
SDK 文档在讨论依赖项属性时有时会使用“本地值”或“本地设置的值”等术语。本地设置的值是指在代码中直接为对象实例设置的属性 (Property) 值,或者在 XAML 中设置为元素属性 (Attribute) 的属性 (Property) 值。
实际上,对于第一个按钮,该属性设置了两次,但是仅应用了一个值,即,具有最高优先级的值。本地设置的值具有最高优先级(对于正在运行的动画除外,但是在本示例中没有应用动画),因此,对于第一个按钮的背景将使用本地设置的值,而不使用样式 setter 值。第二个按钮没有本地值(而且没有其他比样式 setter 优先级更高的值),因此该按钮中的背景将来自样式 setter。
为什么存在依赖项属性优先级?
通常,您不会希望总是应用样式,而且不希望样式遮盖单个元素的哪怕一个本地设置值(否则,通常将很难使用样式或元素)。因此,来自样式的值的操作优先级将低于本地设置的值。
说明:
在 WPF 元素定义了许多非依赖项属性的属性。一般说来,只有在需要支持至少一个由属性系统启用的方案(数据绑定、样式、动画、默认值支持、继承、附加属性或失效)时,才将属性实现为依赖项属性。
了解有关依赖项属性的更多信息
附加属性是一种类型的属性,它支持 XAML 中的专用语法。附加属性通常与公共语言运行库 (CLR) 属性不具有 1:1 对应关系,而且不一定是依赖项属性。附加属性的典型用途是使子元素可以向其父元素报告属性值,即使父元素和子元素的类成员列表中均没有该属性也是如此。一个主要方案是,使子元素可以将其在 UI 中的表示方式通知给父级。
组件开发人员或应用程序开发人员可能希望创建自己的依赖项属性,以便实现数据绑定或样式支持之类的功能,或者实现对失效和强制指定值的支持。
通常,依赖项属性应当被视为公共属性,这些公共属性可以由任何具有实例访问权限的调用方访问,或至少可被这样的调用方发现。