.NET|--WPF|--笔记合集|--依赖项属性|--4.依赖项属性值优先级

前言


前几篇笔记讲到了依赖项属性的定义,注册等.
接下来就该是依赖项属性的实战了.

如果依赖项属性是一个主机的话,
前几个步骤还在于组装这个主机, 组装好了之后, 就要开始使用了, 
是骡子是马, 拉出来遛遛.

但是一般任何事物在使用之前, 都有一些注意事项, 
如果不了解这些注意事项, 就会导致在使用的过程中, 
出现自己无法控制的情况.

比如, 我想给TextBlock的Text属性设置一个值, 但是最终发现, 
我的值也设置了, 但是冠张戴李了, 我设置的是一个"张", 结果最终显示的是"李".

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


还不是为了性能...

依赖项属性优先级

ccc2cc8be8ca275cd2c974837cb843b9.png


对比了从官网中和<<WPF编程宝典>>中, 依赖项属性的优先级, 
会发现, <<WPF编程宝典>>明显的偷工减料.

关键是样式那里, 只有一个来自主题样式和来自项目样式,
我刚开始还有点懵逼, <<WPF编程宝典>>的"2.来自项目样式的值"这一等级,
应该对应官网中的哪一个等级?

对应"5.隐式样式", 还是"6.样式触发器", 还是"7.模板触发器",,,

最终我觉得, 不管官网的[5,6,7,8]反正都是项目中设置的, 
所以我就直接让<<WPF编程宝典>>中的"2.来自项目样式的值"对应官网中的等级[5,6,7,8]了...

官方文档

下面列出了在将运行时值分配给依赖属性时,属性系统所使用的最终优先级顺序。 最高优先级最先列出。

1. 属性系统强制
有关强制转换的详细信息,请参阅强制转换和动画

2. 活动动画或具有 Hold 行为的动画。
若要拥有实用效果,动画必须拥有比基值(无动画)更高的优先级,即使基值进行了本地设置也是如此。 有关详细信息,请参阅强制转换和动画

3. 本地值。
可以通过“包装器”属性设置本地值,这相当于在 XAML 中设置特性或属性元素,或者使用特定实例的属性调用 SetValue API。 通过绑定或资源设置的本地值会具有与直接设置的值相同的优先级。

4. TemplatedParent 模板属性值
如果元素是通过模板创建(ControlTemplateDataTemplate)创建的,则具有 TemplatedParent。 有关详细信息,请参阅 TemplatedParent。 在通过 TemplatedParent 指定的模板中,优先级顺序为:

  1. 触发器。

  2. 属性集(通常通过 XAML 特性进行设置)。

5. 隐式样式
仅应用于 Style 属性。 Style 值是具有与元素类型匹配的 TargetType 值的任何样式资源。 样式资源必须存在于页面或应用程序中。 对隐式样式资源的查找不会扩展到主题中的样式资源。

6. 样式触发器
样式触发器是显式或隐式样式中的触发器。 样式必须存在于页面或应用程序中。 默认样式中的触发器的优先级较低。

7. 模板触发器
模板触发器是直接应用的模板或样式中的模板中的触发器。 样式必须存在于页面或应用程序中。

8. 样式资源库值
样式资源库值是样式中通过 Setter 应用的值。 样式必须存在于页面或应用程序中。

9. 默认样式,也称为主题样式
有关详细信息,请参阅默认(主题)样式。 在默认样式中,优先级顺序如下:

  1. 活动触发器。

  2. 资源库。

10. 继承
子元素的某些依赖属性从父元素继承其值。 因此,可能不需要在整个应用程序中对每个元素设置属性值。 有关详细信息,请参阅属性值继承

11. 依赖属性元数据中的默认值
依赖属性可以在该属性的属性系统注册期间设置默认值。
继承依赖属性的派生类可以选择按照类型重写依赖属性元数据(包括默认值)。
有关详细信息,请参阅依赖属性元数据
对于继承的属性,父元素的默认值优先于子元素的默认值。
因此,如果未设置可继承属性,则会使用根或父级的默认值,而不是子元素的默认值。

<<WPF编程宝典>>
# 我觉得这句话很好"依赖项属性因该行为的命,依赖项属性依赖于多个属性提供者",要把这句话加到前言那篇笔记中去!

依赖项属性因该行为得名,本质上,依赖项属性依赖于多个属性提供者,每个提供者都有各自的优先级。
当从属性检索值时,WPF属性系统会通过一系列步骤获取最终值。
首先通过考虑以下因素(按优先级从低到高的顺序排列)来决定基本值(base value):

(1)默认值
(由 FrameworkPropertyMetadata 对象设置的值)。
(2)继承而来的值
(假设设置了 FrameworkPropertyMetadata.Inherits 标志,并为包含层次中的某个元素提供了值)。
(3)来自主题样式的值
(将在第18 章讨论)。
(4)来自项目样式的值
(将在第 11 章讨论)。
(5)本地值
(使用代码或XAMI 直接为对象设置的值)。

如上面的列表所示,可通过直接应用一个值来覆盖整个层次。
如果不这么做,属性值可由上面列表中的下一个可用项确定。

示例.属性系统强制 和 元数据默认值 和 本地值

可以再回想一下, 依赖属性注册, 注册时候有一个属性元数据参数"System.Windows.FrameworkPropertyMetadata",
而属性元数据实例化的时候, 又有一个可选参数"CoerceValueCallback"

所以, "依赖项属性注册"和"依赖项属性优先级",以及"依赖项属性回调"都是有关系的.
这也是学习过程中需要自己梳理的线, 
场景:
比如人的年龄设置, 普通人的年龄, 都是大于0和小于999的,
这个时候就可以设置, 通过属性系统强制, 可以强行设置一下人年龄的上限和下限.

源码解析--


为什么是个很危险的问题, 
现在知道这种优先级了, 编码中其实大部分情况就可以应对了,
但是如果这时候, 脑袋中突然想, 为什么会这样优先级, 怎么实现的? 
这就需要扒拉源码了...


# 线程校验

return this.Thread == Thread.CurrentThread;

System.Windows.DependencyObject

internal EffectiveValueEntry[] EffectiveValues
{
    [FriendAccessAllowed]
    get
    {
        return this._effectiveValues;
    }
}

System.Windows.Controls.UIElementCollection

        // Token: 0x06007751 RID: 30545 RVA: 0x009A9E3C File Offset: 0x009A9E3C
        protected void SetLogicalParent(UIElement element)
        {
            if (this._logicalParent != null)
            {
                this._logicalParent.AddLogicalChild(element);
            }
        }

插曲--发现巨硬中文官方文档上一个错别字

巨硬中文文档上写错字了,
"回调"写成"回叫"了...哈哈...
207b020db82ad710906b91af9e64a2c2.png

小细节--发现问题,就多打印一些细节...


在搞那个继承值的时候, 一直继承不上, 
就想到想要继承的条件 : 设置了 FrameworkPropertyMetadata.Inherits 标志,并为包含层次中的某个元素提供了值

↓↓↓就把这2个条件打印出来↓↓↓


// 打印出StackPanel的元数据, 主要看"Inherits"是否为"true"
CustomButton.BackgroundColorProperty.GetMetadata(typeof(System.Windows.Controls.StackPanel)).Dump("父节点StackPanel的元数据");

// 父节点StackPanel的值
parentPanel.GetValue(CustomButton.BackgroundColorProperty).Dump("父节点StackPanel的值");
		
// 子节点CustomButton的值
buttonWithInheritedValue.BackgroundColor.Dump("子节点CustomButton的值");


结尾


笔记, 笔记, 自己写的话, 废话可以多一些, 
毕竟整理笔记的时候, 思维确实很容易发散,
杂七杂八的想太多, 

但是如果笔记是想给别人看, 
字的数量还是尽可能少, 
示例和图片可以多一些...
( 我为了画一个好看的金字塔图形, 折腾了好大一会, 还是失败了嘛... 没有艺术细胞... )


我源码都贴上去了, 直接下载一个LINQPad, 都不用打开Visual Studio,
把源码复制进去, 自己改着改着, 调试着调试着, 再看看源码, 就知道咋回事了...

posted @ 2024-08-31 10:53  zh89233  阅读(20)  评论(0编辑  收藏  举报