• WPF依赖属性概述


    一、前言

    最近写的博文知识点都比较基础。
    因为写项目时总会遇到一些奇怪的问题,于是按我的性子,就会回过去学一遍。
    这归根到底还是自己基础太差,兜兜转转半天还得回来补习。
    这不,WPF都用了快两年了,又回来学习依赖属性了。

    原文地址


    二、依赖属性

    WPF提供了一组可用于扩展类型属性功能的服务,该服务统称为WPF属性系统。
    由WPF属性系统支持的属性称为依赖属性(dependency property)。

    本文介绍WPF属性系统和依赖属性的功能,包括如何在XAML和代码中使用现有的依赖属性。
    本概述还介绍了依赖属性特有的内容,例如依赖属性元数据、以及如何在自定义类中创建你自己的依赖属性。

    2.1 前置知识

    假设你已有.NET类型系统和面向对象编程的基本知识。
    那么,按照本文中的示例进行操作,有助于理解XAML并了解如何编写WPF应用程序。

    2.2 依赖属性与CLR属性

    WPF属性通常会公开为标准的.NET属性。

    我们一般把.NET中的属性称为CLR属性。这里的WPF属性指依赖属性。

    日常使用时,你可能会与这些属性进行交互,但你并不知道它们是作为依赖属性实现的。而熟悉WPF属性系统有助于你更好地去使用这些功能。(从这可以看出来,对外来讲,对使用者来讲,依赖属性其实和CLR属性一样,只是内部实现上有差别)

    依赖属性的目的是提供一种方式,基于其他输入值来计算属性值。包括:

    • 系统属性,例如主题或UI。
    • 即时属性确定机制,例如数据绑定和动画、故事板。
    • 多用模板,例如资源和样式。
    • 通过与元素树中的其他元素的父子关系获知的值。

    依赖属性嘛,就是有所依赖的属性,依赖的是别的值。通过别的值的更改,来更新计算自己的值。

    此外,依赖属性可提供:

    • 独立的验证。
    • 默认值。
    • 监视其他属性更改的回调。
    • 可根据运行时信息强制属性值的系统。

    派生类可以通过重写依赖属性的元数据来更改现有属性的某些特征,而不是重写现有属性的实际实现或创建新属性。

    在SDK参考文档中,你可以通过该属性的托管参考页上是否存在依赖属性信息段来辨别一个依赖属性。依赖属性信息段包含指向该依赖属性的 DependencyProperty标识符字段的链接。它还包括该属性的元数据选项列表,每个类的覆盖信息以及其他详细信息。

    2.3 依赖属性支持CLR属性

    依赖属性和WPF属性系统通过提供支持属性的类型来扩展属性功能,以作为使用私有字段支持属性的标准模式的替代方案。该类型的名称是 DependencyProperty 。定义WPF属性系统另一个重要类型是 DependencyObject ,它定义可注册和拥有依赖属性的基类。

    以下是一些常用术语:

    • 依赖属性 Dependency property ,这是由DependencyProperty支持的属性。
    • 依赖属性标识符 Dependency property identifier ,它是注册依赖属性时作为返回值获取的 DependencyProperty 实例,然后存储为类的静态成员。许多与WPF属性系统交互的API使用依赖属性标识符作为参数。
    • CLR包装器 CLR “wrapper” ,是属性的get和set实现。这些实现通过在GetValue和SetValue调用中使用依赖属性标识符来做合并。这样,WPF属性系统就为属性提供了支持。

    以下示例定义 IsSpinning依赖属性以展示 DependencyProperty 标识符与其支持属性的关系。

    public static readonly DependencyProperty IsSpinningProperty = DependencyProperty.Register("IsSpinning", typeof(bool), typeof(MainWindow));
    
    public bool IsSpinning
    {
    	get => (bool)GetValue(IsSpinningProperty);
    	Set => SetValue(IsSpinningProperty, value);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    属性的命名规则及其支持的 DependencyProperty 字段非常重要。字段的名称始终是属性的名称,并加上后缀 Property

    2.4 设置属性值

    在代码中和XAML中都可以设置属性。

    attribute和property都有属性的意思,
    property是面向对象的范畴,针对对象的属性特征;attribute是编程语言层面的,针对标签的属性特征,即标签中的 “名称-值” 对。为了避免混淆,后文把property直译为属性,attribute保留英文。

    2.4.1 在XAML中设置属性值

    下面的XAML示例将一个按钮的背景色设置为红色。XAML attribute的字符串值由WPF XAML解析器进行类型转换为WPF类型。在生成的代码中,WPF类型是由 SolidColorBrush 实现的Color。

    <Button Content="I am red" Background="Red"/>
    
    • 1

    XAML支持多种设置属性的语法形式。特定属性使用哪种语法取决于属性使用的值类型以及一些其他因素,例如是否存在类型转换器。

    下面的XAML示例展示了使用属性元素语法(property element syntax)来替换上述的attribute语法。XAML将按钮背景设置为图像而不是简单的纯色。嵌套元素代表图像,元素的attribute指定图像源。

    <Button Content="I have an image background">
    	<Button.Background>
    		<ImageBrush ImageSource="stripes.jpg"/>
    	Button.Background>
    Button>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    2.4.2 在代码中设置属性

    在代码中设置依赖属性值通常就是调用由包装器公开的 set 实现:

    Button myButton = new ();
    myButton.Width = 200.0;
    
    • 1
    • 2

    获取属性值本质上是调用 get 包装器的实现:

    double whatWidth = myButton.Width;
    
    • 1

    你也可以直接调用属性系统的API GetValue 和 SetValue。直接调用API适用于某些场景,但通常不适用于使用已有的属性。一般来讲,包装器更方便,并且为开发人员工具提供了更好的属性公开。

    属性也可以在XAML中设置,然后通过后置代码访问。

    2.5 依赖属性提供的属性功能

    与由字段支持的属性不同,依赖属性扩展了属性的功能。通常,支持以下功能:

    • 资源 Resources
    • 数据绑定 Data binding
    • 样式 Styles
    • 动画 Animations
    • 元数据重写 Metadata overrides
    • 属性值继承 Property value inheritance
    • WPF设计器继承 WPF Designer integration

    2.5.1 资源

    可以通过引用一个资源来设置属性值。
    资源通常被指定为页面根元素或应用程序的 Resources 属性值,因为这些位置提供了对资源的便捷访问。在该示例中,我们定义了 SolidColorBrush 资源:

    <StackPanel.Resources>
    	<SolidColorBrush x:Key="MyBrush" Color="Gold"/>
    StackPanel.Resources>
    
    • 1
    • 2
    • 3

    现在资源已被定义,我们可以引用该资源来为 Background 属性提供值:

    <Button Background="{DynamicResource MyBrush}" Content="I am gold"/>
    
    • 1

    在WPF XAML中,你可以使用静态或动态资源引用。该特定资源被称为DynamicResource。动态资源引用只能用于设置依赖属性,因此它是由WPF属性系统专门启用的动态资源引用用法。

    注意
    资源被视为本地值,这意味着如果你设置了另一个本地值,将会消除资源引用。

    2.5.2 数据绑定

    依赖属性可以通过数据绑定引用值。数据绑定通过XAML中的特定标记扩展语法或代码中的Binding对象进行工作。使用数据绑定,最终属性值的确定被推迟到运行时,那时会从数据源获取该值。

    下面示例通过使用XAML中声明的绑定设置Button的Content属性。该绑定使用继承的数据上下文和XmlDataProvider数据源(未显示)。绑定本身通过XPath指定数据源内的源属性。

    <Button Content="{Binding Source={StaticResource TestData}, XPath=test[1]/@text}"/>
    
    • 1

    注意
    资源被视为本地值,这意味着如果你设置了另一个本地值,将会消除资源引用。

    依赖属性或DependencyObject类不原生支持INotifyPropertyChanged来通知数据绑定操作的DependencyObject源属性值的更改。

    2.5.3 样式

    样式和模板是使用依赖属性的有力理由。样式对于设置定义应用程序UI的属性尤其有用。样式通常定义为XAML中的资源。样式与属性系统交互,因为它们通常包含特定属性的"setter",以及根据另一个属性的运行时值更改属性值的"trigger"。

    以下示例创建了一个简单样式,该样式将在Resources字典(未显示)内定义。然后该样式将直接应用于按钮的 Style 属性。样式中的 setter 将样式化的按钮的背景属性设置为绿色。

    <Style x:Key="GreenButtonStyle">
    	"Control.Background" Value="Green"/>
    Style>
    
    • 1
    • 2
    • 3
    <Button Style="{StaticResource GreenButtonStyle}" Content="I am green"/>
    
    • 1

    2.5.4 动画

    依赖属性可被动画化。当应用的动画运行时,动画值的优先级高于任何其他属性值(包括本地值)。

    以下示例为Button的Background属性设置动画。技术上来讲,属性元素语法将空白的 SolidColorBrush 设置为背景,并且 SolidColorBrush的Color属性被动画化。

    <Button Content="I am animated">
    	<Button.Background>
    		<SolidColorBrush x:Name="AnimBrush"/>
    	Button.Background>
    	<Button.Triggers>
    		<EventTrigger RoutedEvent="FrameworkElement.Loaded">
    			<BeginStoryboard>
    				<Storyboard>
    					<ColorAnimation>
    						Storyboard.TargetName="AnimBrush"
    						Storyboard.TargetProperty="(SolidColorBrush.Color)"
    						From="Blue" To="White" Duration="0:0:1"
    						AutoReverse="True" RepeatBehavior="Forever" />
    				Storyboard>
    			BeginStoryboard>
    		EventTrigger>
    	Button.Triggers>
    Button>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    2.5.5 元数据覆盖

    当你从最初注册依赖属性的类派生时,可以通过覆盖其元数据来更改依赖属性的特定行为。覆盖元数据依赖于 DependencyProperty 标识符,且不需要重新实现该属性。元数据更改由属性系统本地处理。每个类都有可能按类型保存从基类继承的所有属性的独立元数据。

    以下示例覆盖DefaultStyleKey依赖属性的元数据。覆盖该特定依赖属性的元数据是创建可以使用主题默认样式的控件的实现模式的一部分。

    public class SpinnerControl : ItemsControl
    {
    	static SpinnerControl() => DefaultStyleKeyProperty.OverrideMetadata(typeof(SpinnerControl), new FrameworkProertyMetadata(typeof(SpinnerControl)));
    }
    
    • 1
    • 2
    • 3
    • 4

    2.5.6 属性值继承

    元素可以从对象树中的父元素继承依赖属性的值。

    注意
    属性值继承行为并未对所有依赖属性全局启用,因为继承需要计算,计算会消耗时间,这会影响性能。
    属性值继承通常仅在表明适用性的场景中启用。你可以通过查看SDK参考中该依赖属性的"依赖属性信息"部分来检查该依赖项是否继承。

    以下示例展示了一个绑定,其中包含用于指定绑定源的 DataContext 属性。因此,子对象中的绑定不需要指定源,就能从父StackPanel对象中的 DataContext 取得继承值。或者,子对象可以直接在Binding中指定自己的 DataContext或Source ,不使用继承的值。

    <StackPanel Canvas.Top="50" DataContext="{Binding Source={StaticResource TestData}}">
    	<Button Content="{Binding XPath=test[2]/@text}"/>
    StackPanel>
    
    • 1
    • 2
    • 3

    2.5.7 WPF设计器集成

    具有依赖属性实现的属性的自定义控件与VS的WPF设计器很好地集成。一个例子是能够在 属性 窗口中编辑直接的和附加的依赖属性。

    2.6 依赖属性值优先级

    WPF属性系统中任何基于属性的输入都可以设置依赖属性的值。依赖属性值优先级的存在,以便在不同场景下,属性获取值以一种可预测的方式进行。

    注意
    在讨论依赖属性时,SDK文档有时会使用术语 “局部值(local value)” 或 “局部设置值(locally set value)”局部设置值是直接在代码中的对象实例上设置的属性值,或者在XAML中作为元素attribute值

    下面示例包含一个应用于任何按钮 Background 属性的样式,但指定了一个具有局部设置 Background 属性的按钮。从技术上讲,该按钮的 Background 属性设置了两次,尽管只会应用一个值——优先级最高的值。局部设置值具有最高优先级,除了运行中的动画外,但此处不存在动画。因此,第二个按钮使用 Background 属性的局部设置值,而不是样式设置器的值。第一个按钮没有局部值或优先级高于样式设置器的其他值,因此使用 Background 属性的样式设置器值。

    <StackPanel>
    	<StackPanel.Resources>
    		<Style x:Key="{x:Type Button}" TargetType="{x:Type Button}">
    			"Background" Value="Orange"/>
    		Style>
    	StackPanel.Resources>
    	<Button>I am styled orangeButton>
    	<Button Background="Pink">I am locally set to pink(not styled orange)Button>
    StackPanel>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    2.6.1 为什么存在依赖属性优先级?

    局部值优先于样式设置器值,后者支持元素属性的局部控制。

    注意
    WPF元素上定义的许多属性不是依赖属性,因为依赖属性通常仅在需要WPF属性系统的功能时才实现。这些功能包括数据绑定、样式、动画、默认值支持、继承、附加属性和失效。

    2.7 学习更多有关依赖属性的内容

    • 组件开发人员或应用程序开发人员可能希望创建自己的依赖属性来添加功能,例如数据绑定或样式支持,或者验证和值强制支持。有关信息,查看 “自定义依赖属性” 章节。
    • 将依赖属性视为公开属性,任何有权访问实例的调用者都可以访问和发现。更多信息,查阅 “依赖属性安全性” 章节。
    • 附加属性是一种XAML中支持专门语法的属性类型。附加属性通常与CLR属性没有1:1对应关系,且不一定是依赖属性。附加属性的主要目的是允许子元素向父元素报告属性值,即使父元素和子元素不包含该属性作为类成员列表的一部分。一种主要场景是使子元素能够通知父元素如何在UI中呈现它们。例如,Dock 和 Left。更多信息,查阅 “附加属性概述” 章节。

    三、结尾

    写这篇博客其实是源于一个问题,
    我重写了一个按钮的样式,主要是控件模板,并且添加了一个触发器(鼠标进入改变背景色)。
    我将新样式应用于两个按钮,一个按钮未显式设置Background,另一个按钮设置Background。
    结果,未显式设置背景色的按钮,鼠标移入后会变色,另一个则不会。
    现在看来,是依赖属性优先级的问题,局部设置值具有最高优先级。

  • 相关阅读:
    【毕业设计】 基于Stm32的家庭智能监控系统 - 单片机 图像识别 人体检测 AI
    【LeetCode热题100】--114.二叉树展开为链表
    获取sku详细信息 API 返回值说明
    基于servlet+jsp+mysql网上书店系统
    【课程总结】Day11(中):手势图像识别实战(Vgg16和ResNet)
    TFA-Amino modifier CPG 500;TFA氨基改性剂CPG 500
    PDA库存盘点,有效提高电子制造企业库存盘点效率
    Gson 字符串常用转换方式(集合转换为Json数组
    GoLand软件编码区中出现英文输入异常解决方法
    漫谈ERP和MES数据打通
  • 原文地址:https://blog.csdn.net/BadAyase/article/details/136450096