WPF中的依赖属性和附加属性
重混江湖后的第一篇文章,竟然有些手生......(惶恐+惭愧)ing,怕是套路也要有些变化了-_-
一.属性
刚着手开始学习C#的时候,不明白为什么会有属性这个东西,不是已经有了字段了吗,你说属性里面有get和set方法对数据进行了封装,可以通过对方法的访问限定来控制该属性是否可以被赋值,但是不也有readonly这个关键字可以用来修饰字段吗,你又说可以通过在get或set方法里面对数据进行一系列的操作来对数据进行限制,可以将数据的获取和赋值分开进行不同的处理,好吧这个我是服气的。
你可以这样子写(写prop再按两次Tab就自动生成了):
-
public int Age { get; set; } //则就是个很普通的属性了
-
public int Age { get; } //可以不写set方法,等同于通过访问限定符private来对set方法进行修饰
还可以这样写:
-
private int _age;
-
public int Age
-
{
-
get { return _age; }
-
//这就比单纯的字段对数据的处理要来得自由
-
private set
-
{
-
if (value < 0 || value > 100)
-
return;
-
_age = value;
-
}
-
}
那下面要切入正题了——>
二.依赖属性
呐,名字里都有个属性说明它就是个属性,只是这个属性在方法里封装处理的并不是普通的字段,而是类型为DependencyProperty的一个对象,我们可以先来看它的普通写法(propdp按一次Tab自动生成,好方便的说):
-
public int MyProperty
-
{
-
get { return (int)GetValue(MyPropertyProperty); }
-
set { SetValue(MyPropertyProperty, value); }
-
}
-
-
//下面的注释是自动生成的,可以看到这个对象就是用于MyProperty的封装数据,它可以作用于动画,样式,绑定等等......
-
// Using a DependencyProperty as the backing store for MyProperty. This enables animation, styling, binding, etc...
-
public static readonly DependencyProperty MyPropertyProperty =
-
DependencyProperty.Register("MyProperty", typeof(int), typeof(ownerclass), new PropertyMetadata(0));
上面的MyProperty属性这里不做解释,看一下MyPropertyProperty,这里要说明的是依赖属性的命名约定为以Property作为后缀,该对象使用了DependencyProperty类里面的Register静态方法的返回值:
可以看到Register方法有三个重载,前三个参数
- String:表示所要对其进行封装的属性的名称;
- 第一个Type:表示该属性所属类型;
- 第二个Type:表示该属性所有者的类型,也就是在哪个类里面定义的这个依赖属性;
这个对象里面包含有依赖属性的元数据相关的信息,元数据也就是一些用来描述类型的信息,这里包含例如属性默认值,通知回调和验证回调的引用,还有一些框架性特征(布局、数据绑定等),其中参数:
- Object:表示要对该属性指定的默认值,例如string类型可以指定default(string);
- PropertyChangedCallback:从名字就可以看出,这是一个在属性值被更改时所调用的回调函数,常用于绑定数据变更时处理;
- CoerceValueCallback:Coerce意思是胁迫、强制,也就是这个方法用于在对属性值进行改变的时候,对赋值进行检查,强制对值进行赋值,返回值为Object类型,这个才是要赋给属性的值;
public delegate bool ValidateValueCallback(object value)
传入的参数value为Object类型,也就是要进行检查的属性值,这里会不会有疑问,既然已经有了CoerceValueCallback方法可以对值进行检查之后强制改变要进行的赋值,为什么还要有这个合法值得检查?因为CoerceValueCallback方法无论如何检查,返回值为Object,都会给属性值进行一个赋值,然后会调用PropertyChangedCallback方法,但是在进行CoerceValueCallback方法的调用之前,就会调用ValidateValueCallback方法对值进行检查,可以看到该方法的返回值为一个bool类型,当值满足条件时返回true,这时就会去调用CoerceValueCallback对值进行进一步的处理,如果不满足条件的话返回false,则本次赋值失败告终,根本就没有后续CoerceValueCallback什么事,也就是ValidateValueCallback方法是值更新前检查方法,对值进行的最初的拦截检查,可以看如下图:所以,在对依赖属性进行注册的时候可以根据需要选择不同的Register方法和Parameter构造器,实现需要的方法来对数据进行处理;
这里的公共区是一个全局字典,里面用来存放所有的依赖属性,其中:
- Key是关键词,这个是在注册方法里面由所给的属性名和所属类进行哈希异或得到的哈希值;
- value很好理解,就是每一个属性所有值;
- index这个东西,因为每个依赖属性都是静态的,当进行注册之后相同的依赖属性只会在字典中保存一份,如果一个值更改那么其他所拥有者的值也会相应的进行更改,这显然不是我们所期望的,所以,真正进行存储的时候,全局字典中只会存储注册时候给的初始值,而对于其他通过继承方式得到该依赖属性并对其进行赋值的时候,其实是存储在一个单独的链表中,链表的存储对象中才存放每一个拥有者所赋的不同的值,哎呀看图吧:
上图中的index是相同的,每一次进行值操作的时候,会现在全局的字典中通过Key值拿到对应的Property数据,获取其中的index,然后通过在链表中进行对应的index查找,如果找到了就使用链表中的值,如果没有就使用本身的默认值;
那,如果我并不想要父类的那个依赖属性,我想把它变成自己的,也换一个默认值怎么办?重载咯:
把Type换成自己的类,重新指定PropertyMetadata就好了,这其实是在字典中重新注册了一个依赖属性,也可以通过这个方法重新写该属性的元数据;
那,我另外的一个类也想使用那个依赖属性,就只能通过继承的方式吗,我只是想用一个属性而已啊,要按其他不需要的也都继承下来吗?没这个必要啊:
通过DependencyProperty的AddOwner方法,就可以把那个属性变为自己可以使用的了,同样也可以更改其默认值,也是在字典中重新注册了一个依赖属性;
差不多吧,主要要说的就是这些,剩下的依赖属性的使用,比如在动画啊,样式或者绑定什么的都是另外的话了,哦还有优先级什么的,后续有需要再补充吧。
三.附加属性
简单讲,附加属性就是依赖属性,但它存在的意义是什么呢,先看写法:
-
public static readonly DependencyProperty MyPropertyProperty = DependencyProperty.RegisterAttached(
-
"MyProperty",
-
typeof(Boolean),
-
typeof(MyClass),
-
new FrameworkPropertyMetadata());
-
public static void SetMyProperty(UIElement element, Boolean value)
-
{
-
element.SetValue(MyPropertyProperty, value);
-
}
-
public static Boolean GetMyProperty(UIElement element)
-
{
-
return (Boolean)element.GetValue(MyPropertyProperty);
-
}
但是可以看到在两个方法中有传入参数UIElement,所以有人说“依赖属性是给自己用的,附加属性是给别人用的”,这句话是没错的,在使用附加属性的时候回将使用者的信息作为参数传入进来,所以会有一些布局控件例如Grid,Canvas等有附加属性,Grid.SetRow和Canvas.SetLeft都是直接调用了静态的方法,通过传入的参数来告知属性所有者是谁用了我的属性,把它设为了什么值,这样在布局的时候我就知道要把谁往哪里放,这也是依赖属性和附加属性的一个区别。而且像这样单独将两个方法暴露出来也是为了方便他人使用。
至于其他方面,其实都是一样的。
四.栗子时间
前面讲了那么多,都不如直接上个栗子看的实在(我也实在是写累了):
假设这里有个需求,需要在TextBox里面填写年龄,默认值为成年18岁,但是不能超过100岁,也不能低于18岁,也就是有如下需求:
- 有默认值
- 有上下限
- 有值检查
- 有错误提醒
-
public partial class MainWindow
-
{
-
public MainWindow()
-
{
-
InitializeComponent();
-
-
SetTextBinding();
-
}
-
-
private void SetTextBinding()
-
{
-
var binding = new Binding
-
{
-
Source = this,
-
Path = new PropertyPath("MyAge")
-
};
-
TestTextBox.SetBinding(TextBox.TextProperty, binding);
-
}
-
-
public int MyAge
-
{
-
get { return (int)GetValue(MyAgeProperty); }
-
set { SetValue(MyAgeProperty, value); }
-
}
-
-
//这里注册一个MyAge的依赖属性用于前台绑定,指定默认值为18
-
public static readonly DependencyProperty MyAgeProperty =
-
DependencyProperty.Register("MyAge", typeof(int), typeof(MainWindow),
-
new PropertyMetadata(18, PropertyChangedCallback, CoerceValueCallback), ValidateValueCallback);
-
-
//当赋值成功的时候就改变TextBox的边框颜色
-
private static void PropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
-
{
-
var window = dependencyObject as Window;
-
var textBox = window?.FindName("TestTextBox") as TextBox;
-
if (textBox != null) textBox.BorderBrush = Brushes.Cyan;
-
}
-
-
//当进行赋值的时候,如果值不在18~100之间,强制将值恢复为18,表示该值不合要求
-
private static object CoerceValueCallback(DependencyObject dependencyObject, object baseValue)
-
{
-
var value = (int)baseValue;
-
return value < 18 ? 18 : value;
-
}
-
-
//在开始赋值前对值进行检查,如果值超过100就弹个错误框显示
-
private static bool ValidateValueCallback(object o)
-
{
-
var value = (int)o;
-
if (value > 100)
-
MessageBox.Show("年龄有点大哈");
-
return value < 100;
-
}
-
-
//当儿童选项被勾选的时候去更改属性的元数据
-
private void ChildCheckBox_OnChecked(object sender, RoutedEventArgs e)
-
{
-
ChildAge.Instance.ChangeMetadata(sender);
-
}
-
-
//当取消勾选的时候,应该恢复为原来的属性值绑定才能正确显示
-
private void ChildCheckBox_OnUnchecked(object sender, RoutedEventArgs e)
-
{
-
SetTextBinding();
-
}
-
}
-
-
//重新定义一个Child类继承MainWindow
-
public class ChildAge : MainWindow
-
{
-
public static ChildAge Instance
-
{
-
get { return _instance ?? (_instance = new ChildAge()); }
-
}
-
-
private static ChildAge _instance;
-
-
private static Window _containerWindow;
-
public void ChangeMetadata(object sender)
-
{
-
var item = sender as CheckBox;
-
if (item == null) return;
-
-
//更改属性元数据,默认值为1,对应的值处理检查方法也需重新定义
-
MyAgeProperty.OverrideMetadata(typeof(ChildAge),
-
new PropertyMetadata(1, ChildAgePropertyChangedCallback, ChildCoerceValueCallback));
-
-
///////////这里因为前台数据是和后台绑定的,因此如果对应的属性改变了,前台绑定的源也应该随之改变
-
_containerWindow = GetWindow(item);
-
if (_containerWindow != null)
-
{
-
var binding = new Binding
-
{
-
Source = this,
-
Path = new PropertyPath("MyAge")
-
};
-
var textBox = _containerWindow.FindName("TestTextBox") as TextBox;
-
textBox?.SetBinding(TextBox.TextProperty, binding);
-
}
-
}
-
-
private static object ChildCoerceValueCallback(DependencyObject dependencyObject, object baseValue)
-
{
-
var value = (int)baseValue;
-
return (value < 1 || value >= 18) ? 1 : value;
-
}
-
-
private static void ChildAgePropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
-
{
-
var textBox = _containerWindow?.FindName("TestTextBox") as TextBox;
-
if (textBox != null) textBox.BorderBrush = Brushes.DarkGreen;
-
}
-
}
出处:https://blog.csdn.net/miss_bread/article/details/78295463
关注我】。(●'◡'●)
如果,您希望更容易地发现我的新博客,不妨点击一下绿色通道的【因为,我的写作热情也离不开您的肯定与支持,感谢您的阅读,我是【Jack_孟】!
本文来自博客园,作者:jack_Meng,转载请注明原文链接:https://www.cnblogs.com/mq0036/p/12598338.html
【免责声明】本文来自源于网络,如涉及版权或侵权问题,请及时联系我们,我们将第一时间删除或更改!
posted on 2020-03-30 14:17 jack_Meng 阅读(1843) 评论(0) 编辑 收藏 举报