WPF进阶技巧和实战08-依赖属性与绑定01
依赖项属性
定义依赖项属性
注意:只能为依赖对象(继承自DependencyObject的类)添加依赖项属性。WPF中的元素基本上都继承自DependencyObject类。
- 静态字段
- 名称约定(属性末尾加上Property)
- Readonly(只能在静态构造函数中进行设置)
public static readonly DependencyProperty ScalingRatioProperty =
DependencyProperty.Register("ScalingRatio",
typeof(double),
typeof(FundusCanvas),
new FrameworkPropertyMetadata(1.0));
注册依赖项属性
为了确保DependencyProperty对象不被直接实例化,使能使用静态的DependencyProperty.Register方法创建依赖项属性实例,而且创建后不能被改变,所以使用只读属性。创建过程中需要提供的几个要素:
- 属性名(ScalingRatio)
- 属性使用的数据类型(double)
- 拥有该属性的类型(FundusCanvas)
- 一个具有附加属性设置的FrameworkPropertyMetadata对象(可选)
- 一个用于验证属性的回调函数(可选)
使用FrameworkPropertyMetadata对象和属性验证回调可以更加丰富依赖项属性,创建依赖项属性的附加功能。FrameworkPropertyMetadata类的所有属性:
名称 | 说明 |
---|---|
AffectsArrange AffectsMeasure AffectsParentArrange AffectsParentMeasure |
如果为true,依赖项属性会影响在布局操作的测量过程和排列过程中如何放置相邻的元素或者父元素。如果属性值发生变化,那么布局容器需要重新执行测量步骤以确定新的布局 |
AffectsRender | 如果为true,依赖项属性会对元素的绘制方式造成一定的影响,要求重新绘制元素 |
BindsTwoWayByDefault | 如果为true,默认情况下,依赖项属性将使用双向数据绑定而不是单向数据绑定。不过,当创建数据绑定时,可以明确指定所需的绑定行为 |
Inherits | 如果为true,就通过元素树传播该依赖项属性值,并且可以被嵌套的元素继承。例如:Font是可继承的依赖项属性,如果在更高层次的元素中为Font属性设置了值,那么该属性值就会被嵌套的元素继承,除非手动设置来覆盖继承来的值 |
IsAnimationProhibited | 如果为true,就不能将依赖项属性用于动画 |
IaNoDataBindable | 如果为true,就不能使用绑定表达式设置依赖项属性 |
Journal | 如果为true,基于页面的应用程序中,依赖项属性将被保存到日志中(浏览过的页面历史记录) |
SubPropertiesDoNotAffectRender | 如果为true,并且对象的某个子属性(属性的属性)发生了变化,WPF将不会重新渲染该对象 |
DefaultUpdateSourceTrigger | 当该属性用于绑定表达式时,该属性用于为Binding.UpdateSourceTrigger属性的默认值。UpdateSourceTrigger属性决定了数据绑定值在何时应用自身的变化。当创建绑定时,可手动设置UpdateSourceTrigger属性 |
DefaultValue | 该属性用于为依赖项属性设置默认值 |
CoerceValueCallback | 该属性提供一个回调函数,用于验证依赖项属性之前尝试“纠正”属性值 |
PropertyChangedCallback | 该属性提供一个回调函数,当依赖项属性的值发生变化时调用该函数 |
添加属性包装器
public FundusEditMode EditMode
{
get => (FundusEditMode)GetValue(EditModeProperty);
set => SetValue(EditModeProperty, value);
}
当创建属性封装器时,应当值包含对SetValue和GetValue方法的调用,不应当添加任何验证属性的额外代码,引发事件的代码等。WPF提供了用于进行这些工作的地方-使用依赖项属性回调函数。应该通过前面介绍的ValidateValueCallback回调函数进行验证操作。
依赖项属性遵循严格的优先规则来确定他们的当前值。即使没有直接设置依赖项属性,也可能已经有了值(可能是由数据绑定、样式、或者动画提供的,也可能是继承来的),不过,主要直接设置了属性值,设置的属性值就会覆盖所有其他的影响。
可能希望删除本地值设置,并像从来没有设置过那样确定属性值。显然不能够通过设置新值来实现,反而需要使用方法ClearValue()。
使用依赖项属性
WPF的许多功能都需要使用依赖项属性,这些功能都是通过每个依赖项属性都支持的两个关键行为进行工作的———更改通知和动态值识别。
- 更改通知
当属性值发生变化时,依赖项属性不会自动引发事件以通知属性值发生了变化。相反,他们会触发受保护的名为OnPropertyChangedCallback()的方法。该方法通过两个WPF服务(数据绑定和触发器)传递信息,并调用PropertyChangedCallback回调函数。也就是说,当属性变化时,如果希望进行相应,有两种选择--可使用属性创建绑定,也可以编写能够自动改变其他属性或者开始动画的触发器。但是依赖项属性没有提供一种通用的方法触发一些代码,进而对属性的变化进行相应。
- 动态值识别
本质上,依赖项属性依赖于多个属性提供者,每个提供者都有各自的优先级。当从属性检索值事,WPF属性系统会通过一系列步骤获取最终值。首先通过考虑以下因素(优先级从低到高)来决定基本值。
- 默认值(有FrameworkPropertyMetadata对象设置的值)
- 继承而来的值(假设设置了FrameworkPropertyMetadata.Inherits标志,并为包含层次中的某个元素提供了值)
- 来自主题样式的值
- 来自项目样式的值
- 本地值(使用代码或者XAML直接为对象设置的值)
可通过直接应用一个值来覆盖整个层次,否则属性值可通过上面5中可用项来确定。
WPF按照上面的5种情况去确定依赖项属性的基本值。但是基本值未必就是最后从属性中检索到的值。这是因为WPF还需要考虑其他几个可能改变属性值的提供者:
- 确定基本值(按照上述步骤)
- 如果属性是使用表达式设置的,就对表达式进行求值(数据绑定或者资源)
- 如果属性是动画,就应用动画
- 运行CoerceValueCallback回调函数来修正属性值
共享依赖项属性
尽管一些类具有不同的继承层次,但他们会共享同一依赖项属性。
public static readonly DependencyProperty FrontContentProperty =
FlipPanel.FrontContentProperty.AddOwner(typeof(FlipPanel2));
可以使用相同的技术创建自己的自定义类(假定继承的父类没有提供属性,否则直接重写即可),还可以使用重载的AddOwner方法来提供验证回调函数以及应用依赖项属性的新方法的FrameworkPropertyMetadata对象。
附加的依赖项属性
public static readonly DependencyProperty FrontContent1Property =
DependencyProperty.RegisterAttached("FrontContent", typeof(object), typeof(FlipPanel2), null);
附加属性是一种依赖项属性,由WPF属性管理器管理,不同之处在于附加属性被应用到的类并非定义附加属性的那个类。例如Grid.Row属性是应用在Grid中定义,但是应用于其他控件。
属性验证
WPF提供了两种方法类阻止非法值:
- ValidateValueCallback:该回调函数可接受或者拒绝新值。通常,该回调函数用于捕获违反属性约定的明显错误。可作为DependencyProperty.Register方法的一个参数提供该回调函数。
- CoerceValueCallback:该回调函数可将新值修改为更能够被接受的值。该回调函数通常用于处理为相同对象设置的依赖项属性值相互冲突的问题。这些值本身是合法的,但是同时应用时他们是不相容的。当创建FrameworkPropertyMetadata对象时,提供一个参数作为回调函数。
应用程序试图设置依赖项属性时,所有这些内容的作用过程:
- 首先,CoerceValueCallback方法有机会修改提供的值(通常,使提供的值和其他属性相容),或者返回DependencyProperty.UnsetValue,这会拒绝修改
- 接下来激活ValidateValueCallback方法,该方法返回true以接受一个值作为合法值,或者返回false拒绝值。与CoerceValueCallback不同,本方法不能访问设置属性的实际对象,这意味着不能检查其他属性值。
- 最后,如果前两个阶段获得成功,就会触发PropertyChangedCallback方法
验证回调(ValidateValueCallback)
可使用回调函数加强验证,验证通常应被添加到属性过程的设置部分。提供的回调函数必须指向一个接受对象参数并返回bool值的方法,如果返回true来接受对象是合法的,否则返回false拒绝对象。
对于验证回调函数有一个限制,他们必须是静态方法而且无法访问正在被验证的对象,所有能够获得信息就是刚刚应用的数值。
public static readonly DependencyProperty CornerRadiusProperty =
DependencyProperty.Register("CornerRadius", typeof(CornerRadius),
typeof(FlipPanel), null, new ValidateValueCallback(CornerRadiusValidate));
private static bool CornerRadiusValidate(object value)
{
CornerRadius cornerRadius = (CornerRadius)value;
if (cornerRadius.TopLeft == double.NaN ||
cornerRadius.TopRight == double.NaN ||
cornerRadius.BottomLeft == double.NaN ||
cornerRadius.BottomRight == double.NaN)
return false;
return true;
}
强制回调(CoerceValueCallback)
通过设置CoerceValueCallback回调函数处理相互关联的属性。设置本属性的时候,需要处理好本属性和其他属性之间的关系,根据处理结果返回响应的值。
public CornerRadius CornerRadius
{
get => (CornerRadius)GetValue(CornerRadiusProperty);
set => SetValue(CornerRadiusProperty, value);
}
public static readonly DependencyProperty CornerRadiusProperty =
DependencyProperty.Register("CornerRadius", typeof(CornerRadius),
typeof(FlipPanel), new FrameworkPropertyMetadata(new CornerRadius(5, 5, 5, 5),
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
propertyChangedCallback: CornerRadiusChangedCallback,
coerceValueCallback: CornerRadiusCoerceValueCallback),
new ValidateValueCallback(CornerRadiusValidate));
private static bool CornerRadiusValidate(object value)
{
CornerRadius cornerRadius = (CornerRadius)value;
if (cornerRadius.TopLeft == double.NaN ||
cornerRadius.TopRight == double.NaN ||
cornerRadius.BottomLeft == double.NaN ||
cornerRadius.BottomRight == double.NaN)
return false;
return true;
}
private static object CornerRadiusCoerceValueCallback(DependencyObject d, object value)
{
CornerRadius cornerRadius = (CornerRadius)value;
if (cornerRadius.TopLeft == double.NaN ||
cornerRadius.TopRight == double.NaN ||
cornerRadius.BottomLeft == double.NaN ||
cornerRadius.BottomRight == double.NaN)
return new CornerRadius(5, 5, 5, 5);
return cornerRadius;
}
private static void CornerRadiusChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
}
WPF进阶技巧和实战08-依赖属性与绑定01的更多相关文章
- WPF进阶技巧和实战08-依赖属性与绑定02
将元素绑定在一起 数据绑定最简单的形式是:源对象是WPF元素而且源属性是依赖项属性.依赖项属性内置了更改通知支持,当源对象中改变依赖项属性时,会立即更新目标对象的绑定属性. 元素绑定到元素也是经常使用 ...
- WPF进阶技巧和实战08-依赖属性与绑定03
数据提供者 在大多数的代码中,都是通过设置元素的DataContext属性或者列表控件的ItemsSource属性,从而提供顶级的数据源.当数据对象是通过另一个类构造时,可以有其他选择. 一种是作为窗 ...
- WPF进阶技巧和实战03-控件(3-文本控件及列表控件)
系列文章链接 WPF进阶技巧和实战01-小技巧 WPF进阶技巧和实战02-布局 WPF进阶技巧和实战03-控件(1-控件及内容控件) WPF进阶技巧和实战03-控件(2-特殊容器) WPF进阶技巧和实 ...
- WPF进阶技巧和实战03-控件(4-基于范围的控件及日期控件)
系列文章链接 WPF进阶技巧和实战01-小技巧 WPF进阶技巧和实战02-布局 WPF进阶技巧和实战03-控件(1-控件及内容控件) WPF进阶技巧和实战03-控件(2-特殊容器) WPF进阶技巧和实 ...
- WPF进阶技巧和实战09-事件(1-路由事件、鼠标键盘输入)
理解路由事件 当有意义的事情发生时,有对象(WPF的元素)发送的用于通知代码的消息,就是事件的核心思想.WPF通过事件路由的概念增强了.NET事件模型.事件由允许源自某个元素的事件由另一个元素引发.例 ...
- WPF进阶技巧和实战03-控件(5-列表、树、网格04)
ListView控件 ListView继承自简单的没有特色的ListBox,增加了对基于列显示的支持,并增加了快速切换视图或显示模式的能力,而不需要重新绑定数据以重新构建列表. ListView类继承 ...
- WPF进阶技巧和实战07--自定义元素01
完善和扩展标准控件的方法: 样式:可使用样式方便地重用控件属性的集合,甚至可以使用触发器应用效果 内容控件:所有继承自ContentControl类的控件都支持嵌套的内容.使用内容控件,可以快速创建聚 ...
- WPF进阶技巧和实战03-控件(5-列表、树、网格01)
列表控件 ItemsControl为列表项控件定义了基本功能,下图是ItemsControl的继承关系: 在继承自ItemsControl类的层次结构中,还显示了项封装器(MenuItem.TreeV ...
- WPF进阶技巧和实战09-事件(2-多点触控)
多点触控输入 多点触控输入和传统的基于比的输入的区别是多点触控识别手势,用户可以移动多根手指以执行常见的操作,放大,旋转,拖动等. 多点触控的输入层次 WPF允许使用键盘和鼠标的高层次输入(例如单击和 ...
随机推荐
- ManagementEventWatcher throws ManagementException with call to Stop()
参考网址:https://stackoverflow.com/questions/46100105/managementeventwatcher-throws-managementexception- ...
- .Net Core 踩坑记录--无法逐步调试类库文件
前提 新建类库 在新项目中引用该类库 将类库对应的.PDB文件 拷贝至新项目的bin文件夹下 结果 无法进行跟踪调试 狗带 分析与解决 1: 打开 工具-->选项-->调试 2: 常规-- ...
- vue项目打包 部署nginx服务器 访问远程接口 本地json 跨域问题
本文建立在你已经在windows7上已经配好了nginx的前提下进行!!! 如果没有请移步至:https://www.cnblogs.com/jack1208-rose0203/p/5739765.h ...
- 迭代器 与 foreach 的区别
迭代器的常见运用--Eg:有一组数据 需要对每个符合条件的数据 进行记录 static void Main() { int[] s = new int[] { 1, 2, 8 }; foreach ( ...
- 关于Mybatis中表中字段名和POJO中字段名不同的解决方法
项目结构: POJO中: package com.domain; /** * @author mzy * 定义orders表对应的实体类 */ public class Order { /** * C ...
- Python - 面向对象编程 - 实例方法、静态方法、类方法
实例方法 在类中定义的方法默认都是实例方法,前面几篇文章已经大量使用到实例方法 实例方法栗子 class PoloBlog: def __init__(self, name, age): print( ...
- webpack编译后的代码如何在浏览器执行
浏览器是无法直接使用模块之间的commonjs或es6,webpack在打包时做了什么处理,才能让浏览器能够执行呢,往下看吧. 使用commonjs语法 先看下写的代码, app.js minus.j ...
- 自己封装一个Object.freeze()方法
1.遍历所有属性和方法 2.修改遍历到的属性的描述 3.Object.seal() Object.defineProperty(Object,'freezePolyfill',{ value:func ...
- Ubuntu下安装Python3(与旧Python2版本共存)
官网下载Python3的源码 进行配置,在源码目录运行如下命令. ./configure --prefix=/usr/local/python3 --enable-shared 进行编译,在源码目录运 ...
- unexpected end of file while looking for precompiled header. Did you forget to add '#include "stdafx.h"' to your source 解决办法
Project -> Properties -> C/C++ -> Precompiled Headers -> Precompiled Header -> 选择Not ...