首页 > 代码库 > [Aaronyang] 写给自己的WPF4.5 笔记[2依赖属性]
[Aaronyang] 写给自己的WPF4.5 笔记[2依赖属性]
人生的意义不在于拿一手好牌,而在于打好一手坏牌 --Aaronyang的博客(www.ayjs.net)-www.8mi.me
=============时隔两年后再看WPF==========
因为以前的经验,所以继承FrameworkElement,我就简写继承FWE ,继承UIElement就写继承UIE
后面重头戏就是blend中的开发,不想写的千篇一律。如果期待,左侧有关注按钮。
个人感觉,下面的这张图标比较重要,它或许有些帮助。我看东西只看分析出原理,你就可以拓三返一。
Tip: 只能为依赖对象(继承自DependencyObject的类)添加依赖属性。放心的事,wpf大部分都间接继承了。依赖属性提高的不仅仅是性能。
插曲:如果想更进一步了解.net framework,这里有.net framework4.5.2的最新源码web版,可以参考:查看
老实说:书中的例子,讲的也不太好,看的也好累,真搞不懂,这么早就遇到这么难理解的知识。没办法了,咬着牙尽量最好的让聪明的你更好理解。
因为这章知识感觉用视频的方式比图文的方式感觉更好讲一些
一回生======aaronyang====www.8mi.me====www.ayjs.net====
1. 依赖项属性-依赖属性
例如:Button的 Margin属性
使用方法:Register()
简单用法:
Register(String, Type, Type) 使用指定的属性名称、属性类型和属性所在对象的类型。
Register(String, Type, Type, PropertyMetadata) 使用指定的属性名称、属性类型、属性所在对象的类型和属性元数据注册依赖项属性。
Register(String, Type, Type, PropertyMetadata, ValidateValueCallback) 使用指定的属性名称、属性类型、属性所在对象的类型、属性元数据和属性的值验证回调来注册依赖项属性。
2. 附加的依赖项属性-附加属性
例如:Grid的 Row和Column属性,在容器内的元素代码上可以使用 Grid.Row或Grid.Column
使用方法:RegisterAttached()
3.两个常见的依赖属性行为: 更改通知和动态值识别。OnPropertyChangedCallback()以后应该会经常遇到,至少我是。比如我修改text属性,则可视化界面立即变了。
除了绑定的行为,让一个元素变了,指定元素受到变化,还有触发器的知识,可以解决,这个后面单独讲。
4. 为什么要掌握这个知识点,参考:查看
1. 希望可在样式中设置属性。
2. 希望属性支持数据绑定。
3. 希望可使用动态资源引用设置属性。
4. 希望从元素树中的父元素自动继承属性值。
5. 希望属性可进行动画处理。
6. 希望属性系统在属性系统、环境或用户执行的操作或者读取并使用样式更改了属性以前的值时报告。
7. 希望使用已建立的、WPF 进程也使用的元数据约定,例如报告更改属性值时是否要求布局系统重新编写元素的可视化对象。
这是在别人文章里面找到的,说实话这些需求我都遇到过,当时也解决了。
我觉得水印框这个例子是最适合依赖属性理解的。也就是拓展UIElement或者FrameworkElement的,其中有个特殊的PasswordBox,就是他的PasswordBox控件的Password属性不是依赖属性,无法直接进行数据绑定,也就不太适合MVVM模式使用了,但是有办法解决的,还有个问题,PasswordBox不可以继承!!这个后面遇到再说吧。(自己写控件,为了让别人更少的后台写代码,只在xaml中写就在xaml中是很好的)
在WPF体系中,只有定义属性为依赖项属性,这个属性才支持样式设置,数据绑定,继承,动画和默认值
学习方法:遇到新知识,一般都是对着MSDN看着学的,这里有个WPF控件说明,微软的文档:点击查看
遇到问题,当你无助的时候,可能它能帮助你。例如TextBox类,你可以Ctrl+F,然后输入TextBox,我不能什么都讲的,自己也需要学会自学。
我们点击TextBox,然后点击Margin,你会发现它讲的比任何文章都清楚。
好吧,我决定写个例子,边写边讲好点
二回熟======aaronyang====www.8mi.me====www.ayjs.net====
v1.0水印框(不支持样式和动画,因为这里还没讲到该知识点,后期会拓展)
第一步:增加第一个 依赖属性 水印文字WaterText,根据依赖项约定,定义属性名称时候要加 Property
新建一个普通的文件夹Control,然后新建一个继承TextBox的类
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Windows.Controls;namespace AYControl.Control{ public class WaterTextBox : TextBox { }}
第二步,定义个依赖属性,然后注册依赖属性
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Windows;using System.Windows.Controls;namespace AYControl.Control{ public class WaterTextBox : TextBox { //定义默认值,也可以不写,直接注册中指定 //FrameworkPropertyMetadata meta = new FrameworkPropertyMetadata(""); //定义个拓展的 依赖属性 public static readonly DependencyProperty WTextProperty = DependencyProperty.Register("WText", //属性名称 typeof(string), //属性类型 typeof(WaterTextBox), //该属性所有者,即将该属性注册到那个类上 new PropertyMetadata("")); //属性默认值 public string WText { get { return (string)GetValue(WTextProperty); } set { SetValue(WTextProperty, value); } } }}
OK,第一次使用,新建一个xaml,我们在顶部引入命名空间:xmlns:ay="clr-namespace:AYControl.Control",这里的ay自己随便取的
下面使用,运行的时候发现并没有报错,这里自定义了一个依赖属性,这样,在样式和模板等其他场景中就可以使用了,由于这个属性是依赖属性,所以只能在自己的元素上使用,如果是附加属性,就可以在其他控件上使用了
<Window x:Class="blend1.Control" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ay="clr-namespace:AYControl.Control" Title="自定义控件使用" Height="300" Width="300"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="44*"/> <RowDefinition Height="225*"/> </Grid.RowDefinitions> <ay:WaterTextBox WText="aaronyang" Width="150" Height="30"></ay:WaterTextBox> </Grid></Window>
第三步:复习定义一个 附加属性
水印文本框,就是在Textbox上方增加了一个Textblock元素,它的值等于 WaterTextBox的WText值,并且要判断文本的值是否为空,如果空,就显示TextBlock,不为空就隐藏TextBlock。竟然是Textblock就可以有样式了,我们定义一个样式类型的 附加属性(暂时没什么用)
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Windows;using System.Windows.Controls;namespace AYControl.Control{ //定义可以接受一个样式,默认名称为WaterTextStyle,样式作用于TextBlock类型 [StyleTypedProperty(Property = "WaterTextStyle", StyleTargetType = typeof(TextBlock))] public class WaterTextBox : TextBox { static WaterTextBox() { //无外观的控件需要在静态构造函数中设置DefaultStyleKeyProperty,这样它会去在themes/generic.xaml的文件获取默认的样式 // DefaultStyleKeyProperty.OverrideMetadata(typeof(WaterTextBox), new FrameworkPropertyMetadata(typeof(WaterTextBox))); } //定义默认值,也可以不写,直接注册中指定 //FrameworkPropertyMetadata meta = new FrameworkPropertyMetadata(""); //定义个拓展的 依赖属性 public static readonly DependencyProperty WTextProperty = DependencyProperty.Register("WText", //属性名称 typeof(string), //属性类型 typeof(WaterTextBox), //该属性所有者,即将该属性注册到那个类上 new PropertyMetadata("")); //属性默认值 /// <summary> /// 水印文本值 /// </summary> public string WText { get { return (string)GetValue(WTextProperty); } set { SetValue(WTextProperty, value); } } public static readonly DependencyProperty WaterTextStyleProperty = DependencyProperty.RegisterAttached("WaterTextStyle", typeof(Style), typeof(WaterTextBox)); /// <summary> /// 水印文本框样式 /// </summary> public Style WaterTextStyle { get { return (Style)GetValue(WaterTextStyleProperty); } set { SetValue(WaterTextStyleProperty, value); } } }}
因为涉及了Style的知识,就先写到这里了。整个功能的实现等到以后再说吧。
OK,此时我们可以在xaml中定义一个TextBox元素,使用附加属性
<Window x:Class="blend1.Control" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ay="clr-namespace:AYControl.Control" Title="自定义控件使用" Height="300" Width="300"> <Window.Resources> <Style TargetType="{x:Type ay:WaterTextBox}" x:Name="WaterTextStyle"> </Style> </Window.Resources> <Grid> <Grid.RowDefinitions> <RowDefinition Height="44*"/> <RowDefinition Height="225*"/> </Grid.RowDefinitions> <ay:WaterTextBox WText="aaronyang" Width="150" Height="30" Text="water文本框" ></ay:WaterTextBox> <TextBox Text="正常文本框" Width="150" Height="30" Grid.Row="1" ay:WaterTextBox.WaterTextStyle="{Binding WaterTextStyle}"/> </Grid></Window>
发现会报错,因为你还没有设置Get属性和Set属性,不然别的元素是无法设置的,接下来我们设置一下
public static readonly DependencyProperty WaterTextStyleProperty = DependencyProperty.RegisterAttached("WaterTextStyle", typeof(Style), typeof(WaterTextBox)); /// <summary> /// 水印文本框上方Textblock样式 /// </summary> public Style WaterTextStyle { get { return (Style)GetValue(WaterTextStyleProperty); } set { SetValue(WaterTextStyleProperty, value); } } public static Style GetWaterTextStyle(DependencyObject obj) { return (Style)obj.GetValue(WaterTextStyleProperty); } public static void SetWaterTextStyle(DependencyObject obj, Style value) { obj.SetValue(WaterTextStyleProperty, value); }
OK了,此时我们在运行程序,就不会报错了。
完整示例WaterTextBox.cs代码:
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Windows;using System.Windows.Controls;namespace AYControl.Control{ //定义可以接受一个样式,默认名称为WaterTextStyle,样式作用于TextBlock类型 [StyleTypedProperty(Property = "WaterTextStyle", StyleTargetType = typeof(TextBlock))] public class WaterTextBox : TextBox { static WaterTextBox() { //无外观的控件需要在静态构造函数中设置DefaultStyleKeyProperty,这样它会去在themes/generic.xaml的文件获取默认的样式 // DefaultStyleKeyProperty.OverrideMetadata(typeof(WaterTextBox), new FrameworkPropertyMetadata(typeof(WaterTextBox))); } //定义默认值,也可以不写,直接注册中指定 //FrameworkPropertyMetadata meta = new FrameworkPropertyMetadata(""); //定义个拓展的 依赖属性 public static readonly DependencyProperty WTextProperty = DependencyProperty.Register("WText", //属性名称 typeof(string), //属性类型 typeof(WaterTextBox), //该属性所有者,即将该属性注册到那个类上 new PropertyMetadata("")); //属性默认值 /// <summary> /// 水印文本值 /// </summary> public string WText { get { return (string)GetValue(WTextProperty); } set { SetValue(WTextProperty, value); } } //定义个拓展的 附加属性 public static readonly DependencyProperty WTextAttachProperty = DependencyProperty.RegisterAttached("WTextAttach", typeof(string), typeof(WaterTextBox)); /// <summary> /// 水印文本值-附加属性 /// </summary> public string WTextAttach { get { return (string)GetValue(WTextAttachProperty); } set { SetValue(WTextAttachProperty, value); } } public static string GetWTextAttach(DependencyObject obj) { return (string)obj.GetValue(WTextAttachProperty); } public static void SetWTextAttach(DependencyObject obj, string value) { obj.SetValue(WTextAttachProperty, value); } public static readonly DependencyProperty WaterTextStyleProperty = DependencyProperty.RegisterAttached("WaterTextStyle", typeof(Style), typeof(WaterTextBox)); /// <summary> /// 水印文本框上方Textblock样式 /// </summary> public Style WaterTextStyle { get { return (Style)GetValue(WaterTextStyleProperty); } set { SetValue(WaterTextStyleProperty, value); } } public static Style GetWaterTextStyle(DependencyObject obj) { return (Style)obj.GetValue(WaterTextStyleProperty); } public static void SetWaterTextStyle(DependencyObject obj, Style value) { obj.SetValue(WaterTextStyleProperty, value); } }}
OK,关于注册的第四个参数 PropertyMetadata的用法有很多,值得大家对着msdn操作一下
三拓展======aaronyang====www.8mi.me====www.ayjs.net====
1. 标记拓展知识
等同于
2. FrameworkMetadata常用于指定元素默认值
几个值,这里给个大致参考的用法
3. 清空属性值:myElement.ClearValue(FrameworkElement.XXXProperty)
4.依赖属性因为行为而出名--本质上属性依赖多个属性提供者,每个提供者都有优先级
优先级从大到小排列:本地值(使用代码或者XAML直接为对象设置的值) > 来自项目样式的值 > 来自主题样式的值 > 继承而来的值 > 默认值(FrameworkMetadata设置的)
5. 共享依赖属性(具体的例子还没找到,先了解吧,后面再补上,我也没怎么用过这个知识)
类名.XXXProperty = 系统定义好的,或者其他类的依赖属性YYProperty.AddOwner(typeof(类名))
说白了就是 A类中的依赖属性可以使用别人早定义好的依赖属性,例如TextBlock.FontFamily和Control.FontFamily属性指向的是TextElement.FontFamilyProperty依赖项属性。TextElement类在 静态构造函数注册该属性。而TextBlock类和Control类的静态构造函数只是调用DependencyProperty.AddOwner()方法重用该属性。
6.属性验证
ValidateValueCallback:接受还是拒绝这个属性值,可以定义个方法来验证,比如 某某字符串必须是在1-100之间的
CoerceValueCallback:将新值修改为更能被接受的值
OK,我们让WaterTextBox增加水印文本内容验证,比如包含"请"。下面的截图的new PropertyMetadata("")改成 new PropertyMetadata("请输入"),因为不包含请 默认初始值过不起,会报错
但是这只是验证,并且不能访问外面的属性,我们还可以使用FrameworkMetadata的CoerceValueCallback强制回调
FrameworkPropertyMetadata metadata = http://www.mamicode.com/new FrameworkPropertyMetadata(OnMaxPropertyChangedCallback, OnMaxCoerceValueCallback); public static void OnMaxPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e) { //定义值改变后的操作 } public static string OnMaxCoerceValueCallback(DependencyObject d, object baseValue) {
//可以获得本对象的其他属性,然后判断了。
//例如d是个范围类,在注册Maxmium依赖属性时候,有最大值和最小值判断,如果最大值(baseValue)小于最小值(d强制转换后.Minmium),则返回最小值 return "ceshi"; }
书上的已经没用了,我们可以F12查看FrameworkMetadata类
7. 继承DependencyObject,代码参考:查看
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading.Tasks;using System.Windows;namespace blend1{ public class SimpleDO:DependencyObject { public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(double), typeof(SimpleDO), new FrameworkPropertyMetadata((double)0.0, FrameworkPropertyMetadataOptions.None, new PropertyChangedCallback(OnValueChanged), new CoerceValueCallback(CoerceValue)), new ValidateValueCallback(IsValidateValue)); public double Value { get { return (double)GetValue(ValueProperty); } set { SetValue(ValueProperty, value); } } private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { Console.WriteLine("ValueChanged new value is {0}", e.NewValue); } private static object CoerceValue(DependencyObject d, object value) { Console.WriteLine("CoerceValue value is {0}", value); return value; } private static bool IsValidateValue(object value) { Console.WriteLine("ValidateValue value is {0}", value); return true; } }}
使用案例:
SimpleDO sDo = new SimpleDO();sDo.Value = 1;
效果:
ValidateValue value is 0
ValidateValue value is 0
ValidateValue value is 1
CoerceValue value is 1
ValueChanged new value is 1
从这里大概也可以看出注册完时候,属性值修改,方法执行的顺序了。
第一步肯定 Validate验证是否合法,不合法的话,后面的事件都不执行了,提高了性能。
valueChanged,肯定是属性值改变时触发的了
经过断点调试:SimpleDO sDo = new SimpleDO();
进行了2次 IsValidateValue方法,赋值时候,IsValidateValue -> CoerceValue -> OnValueChanged
如果代码写成
SimpleDO d = new SimpleDO();
d.Value = http://www.mamicode.com/1;
d.Value = http://www.mamicode.com/1;
进行了2次 IsValidateValue方法,赋值时候,IsValidateValue -> CoerceValue ,最后一个OnValueChanged不执行了,因为值没改变。
OK啦,就先写到这里 WPF4.5 aaronyang 我的博客网址:www.8mi.me或者www.ayjs.net
网站还没备案,可能访问有点慢,也没怎么更新官网,但后期可能会直接在那里写了。希望大家多多支持哦!
======安徽六安 杨洋=========www.ayjs.net==========aaronyang================www.8mi.me==========
[Aaronyang] 写给自己的WPF4.5 笔记[2依赖属性]