WPF---依赖属性(一)
一、概要
C#中属性是抽象模型的核心部分,而依赖属性是专门针对WPF的。
在WPF库实现中,依赖属性使用普通的C#属性进行了包装,使得我们可以通过和以前一样的方式来使用依赖属性。
依赖属性优点如下:
- 依赖属性加入了属性变化通知、限制、验证等功能。
- 节约内存:在WinForm中,每个UI控件的属性都赋予了初始值,这样每个相同的控件在内存中都会保存一份初始值。而WPF依赖属性很好地解决了这个问题,
它内部实现使用哈希表存储机制,对多个相同控件的相同属性的值都只保存一份。
- 支持多种提供对象:可以通过多种方式来设置依赖属性的值。
二、依赖属性的定义
定义一般遵循如下步骤:
- 定义一个类继承自DependencyObject类。
- 使用public static 声明一个DependencyProperty的变量,该变量就是真正的依赖属性。
- 在类型的静态构造函数中通过Register方法完成依赖属性的注册。
- 提供一个依赖属性的包装属性,通过这个属性来完成对依赖属性的读写操作。
参考代码如下:
1 public class DataSource : DependencyObject
2 {
3 static DataSource()
4 {
5 // Using a DependencyProperty as the backing store for Title. This enables animation, styling, binding, etc...
6 TitleProperty =
7 DependencyProperty.Register("Title", typeof(string), typeof(DataSource), new PropertyMetadata("DefaultTitle", new System.Windows.PropertyChangedCallback(PropertyChangedCall)));
8 }
9 public static readonly DependencyProperty TitleProperty;
10 public string Title
11 {
12 get { return (string)GetValue(TitleProperty); }
13 set { SetValue(TitleProperty, value); }
14 }
15
16 public static void PropertyChangedCall(DependencyObject d, DependencyPropertyChangedEventArgs e)
17 {
18
19 }
20
21 }
可以使用如下快捷方式生成依赖属性:
在VS中输入“propdp”然后连续按两次Tab键。
三、依赖属性的优先级
WPF允许在多个地方设置依赖属性的值,那么自然就涉及到依赖属性获取值的优先级问题。
以下面代码片段来看下优先级
<Ellipse Grid.Column="1" Margin="20" Fill="Pink">
<Ellipse.Style>
<Style TargetType="{x:Type Ellipse}">
<Setter Property="Fill" Value="Red"></Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Fill" Value="Green"></Setter>
</Trigger>
</Style.Triggers>
</Style> </Ellipse.Style>
</Ellipse>
我们分别在不同的三处地方设置了Ellipse的Fill属性,分别是Pink、Red和Green,运行后,我们可以发现椭圆是被Pink填充的。
如果我们把 <Ellipse Grid.Column="1" Margin="20" Fill="Pink">中的Fill="Pink"去掉,在运行程序,则会发现椭圆是被Red填充的,当鼠标移到椭圆上的时候,颜色会变成Green。
WPF中,整体优先级参见下图:
四、依赖属性的继承
依赖属性是可以被继承的,即父元素的相关设置会自动传递给所有的子元素。参见如下示例代码:
1 <Window x:Class="DependencyAttr.MainWindow"
2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4 xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
5 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
6 xmlns:local="clr-namespace:DependencyAttr"
7 mc:Ignorable="d"
8 Title="MainWindow" Height="350" Width="525" FontSize="18">
9 <Grid ShowGridLines="True">
10 <Grid.RowDefinitions>
11 <RowDefinition/>
12 <RowDefinition/>
13 </Grid.RowDefinitions>
14 <Grid.ColumnDefinitions>
15 <ColumnDefinition/>
16 <ColumnDefinition/>
17 </Grid.ColumnDefinitions>
18 <StackPanel Margin="20">
19 <Label Content="TestDependencyAttribute"></Label>
20 <TextBox Name="tbxTestDp"></TextBox>
21 <Button Height="30" Name="bindingBtn"></Button>
22 <Button Height="30" Name="bindingBtn2"></Button>
23 </StackPanel>
24 <Ellipse Grid.Column="1" Margin="20" Fill="Pink">
25 <Ellipse.Style>
26 <Style TargetType="{x:Type Ellipse}">
27 <Setter Property="Fill" Value="Red"></Setter>
28 <Style.Triggers>
29 <Trigger Property="IsMouseOver" Value="True">
30 <Setter Property="Fill" Value="Green"></Setter>
31 </Trigger>
32 </Style.Triggers>
33 </Style>
34
35 </Ellipse.Style>
36 </Ellipse>
37
38 <StackPanel Grid.Row="1">
39 <Label Content="继承窗体字体"></Label>
40 <Label Content="显示设置字体" FontSize="14"></Label>
41 <StatusBar>Statusbar没有继承窗体的字体</StatusBar>
42 </StackPanel>
43
44 </Grid>
45 </Window>
从上图中我们可以发现
Window.FontSize字体设置会影响其子元素的字体设置,这就是依赖属性的继承。如第一个Label没有定义FontSize,它继承了Window.FontSize值,第二个Label显示设置了
字体大小,这种继承就会被打断,所以Window.FontSize值对于第二个Label不再起作用。
StatusBar没有显式设置FontSize值,但它的字体大小也没有继承Window.FontSize的值,而是保持了系统的默认值。这是因为并不是所有元素都支持属性值继承的,如StatusBar、Tooptip和Menu控件。
上面介绍了依赖属性的继承,那我们如何把自定义的依赖属性设置为可被其他控件继承呢?
通过AddOwer方法可以设置依赖属性的继承。参考以下代码:
1 using System;
2 using System.Windows;
3 using System.Windows.Controls;
4
5 namespace DependencyAttrInherit
6 {
7 /// <summary>
8 /// Interaction logic for MainWindow.xaml
9 /// </summary>
10 public partial class MainWindow : Window
11 {
12 public MainWindow()
13 {
14 InitializeComponent();
15 }
16 }
17 public class CustomStackPanel:StackPanel
18 {
19
20
21 public DateTime MinDate
22 {
23 get { return (DateTime)GetValue(MinDateProperty); }
24 set { SetValue(MinDateProperty, value); }
25 }
26
27 // Using a DependencyProperty as the backing store for MinDate. This enables animation, styling, binding, etc...
28 public static readonly DependencyProperty MinDateProperty =
29 DependencyProperty.Register("MinDate", typeof(DateTime), typeof(CustomStackPanel), new FrameworkPropertyMetadata(DateTime.MinValue, FrameworkPropertyMetadataOptions.Inherits));
30 }
31 public class CustomButton:Button
32 {
33
34 static CustomButton()
35 {
36 // AddOwner方法指定依赖属性的所有者,从而实现依赖属性的继承,即CustomStackPanel的MinDate属性被CustomButton控件继承。
37 // 注意FrameworkPropertyMetadataOptions的值为Inherits
38 MinDateProperty = CustomStackPanel.MinDateProperty.AddOwner(typeof(CustomButton), new FrameworkPropertyMetadata(DateTime.MinValue, FrameworkPropertyMetadataOptions.Inherits));
39 }
40 public DateTime MinDate
41 {
42 get { return (DateTime)GetValue(MinDateProperty); }
43 set { SetValue(MinDateProperty, value); }
44 }
45
46 private static readonly DependencyProperty MinDateProperty;
47
48
49
50
51 }
52 }
1 <Window x:Class="DependencyAttrInherit.MainWindow"
2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4 xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
5 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
6 xmlns:local="clr-namespace:DependencyAttrInherit"
7 xmlns:sys="clr-namespace:System;assembly=mscorlib"
8 mc:Ignorable="d"
9 Title="MainWindow" Height="350" Width="525">
10 <Grid>
11 <local:CustomStackPanel x:Name="customStackPanel" MinDate="{x:Static sys:DateTime.Now}">
12 <ContentPresenter Content="{Binding Path=MinDate, ElementName=customStackPanel}"></ContentPresenter>
13 <local:CustomButton Content="{Binding RelativeSource={RelativeSource Mode=Self}, Path=MinDate}" Height="30"></local:CustomButton>
14 </local:CustomStackPanel>
15 </Grid>
16 </Window>
运行结果如下:
五、依赖属性的监听
我们可以使用DependencyPropertyDescriptor类来对依赖属性进行监听。
以下代码片段会对TextBox的Text属性进行监听,当Text属性发生变化的时候,就会调用函数tbxTestDp_TextChanged
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent(); DependencyPropertyDescriptor descriptor = DependencyPropertyDescriptor.FromProperty(TextBox.TextProperty, typeof(TextBox));
descriptor.AddValueChanged(tbxTestDp, tbxTestDp_TextChanged);
} public void tbxTestDp_TextChanged(object sender, EventArgs e)
{
Debug.WriteLine($"Sender {sender.ToString()}");
}
}
六、依赖属性的验证
对于传统的CLR属性,可以在属性的设置器中进行属性值的验证,不满足条件的值可以抛出异常。
但对于依赖属性来说,这种方法不合适,因为依赖属性通过SetValue方法来直接设置其值的。
WPF中,可以使用以下方法对依赖属性的值进行验证。
- ValidateValueCallback:该回调函数可以接受或拒绝新值。该值可作为DependencyProperty.Register方法的一个参数。
- CoerceValueCallback:该回调函数可将新值强制修改为可被接受的值。该回调函数可作为PropertyMetadata构造函数参数进行传递。
参考代码如下:
1 public partial class MainWindow : Window
2 {
3 public MainWindow()
4 {
5 try
6 {
7 InitializeComponent();
8 SimpleDPClass sdp = new SimpleDPClass();
9 sdp.SimpleDP = 2;
10 //rectangle.Fill = new ImageBrush() { ImageSource = new BitmapImage(new Uri("pack://application:,,,/img/123.png")) };
11 }
12 catch(Exception e)
13 {
14 MessageBox.Show(e.Message);
15 }
16
17 }
18 }
19
20 public class SimpleDPClass : DependencyObject
21 {
22 public static readonly DependencyProperty SimpleDPProperty =
23 DependencyProperty.Register("SimpleDP", typeof(double), typeof(SimpleDPClass),
24 new FrameworkPropertyMetadata((double)0.0, FrameworkPropertyMetadataOptions.None,
25 new PropertyChangedCallback(OnValueChanged),
26 new CoerceValueCallback(CoerceValue)),
27
28 new ValidateValueCallback(ValidateValue));
29
30 public double SimpleDP
31 {
32 get { return (double)GetValue(SimpleDPProperty); }
33 set { SetValue(SimpleDPProperty, value); }
34 }
35
36 private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
37 {
38 Debug.WriteLine($"OnValueChanged Called OldValue:{e.OldValue} NewValue:{e.NewValue}");
39 }
40
41 private static object CoerceValue(DependencyObject d, object value)
42 {
43 Debug.WriteLine($"CoerceValue Called IniValue:{value}");
44
45 value = 168;
46 Debug.WriteLine($"IniValue:{value} changed to NewValue:168 in CoerceValue");
47
48 return value;
49 }
50
51 private static bool ValidateValue(object value)
52 {
53 Debug.WriteLine($"ValidateValue Called IniValue:{value}");
54 if ((double)value > 167)
55 return false;
56 return true;
57 }
58 }
注:本文参考https://www.cnblogs.com/zhili/p/WPFDependencyProperty.html
WPF---依赖属性(一)的更多相关文章
- WPF依赖属性详解
WPF依赖属性详解 WPF 依赖属性 英文译为 Dependency Properties,是WPF引入的一种新类型的属性,在WPF中有着极为广泛的应用,在WPF中对于WPF Dependency P ...
- WPF自学入门(五)WPF依赖属性
在.NET中有事件也有属性,WPF中加入了路由事件,也加入了依赖属性.最近在写项目时还不知道WPF依赖属性是干什么用的,在使用依赖项属性的时候我都以为是在用.NET中的属性,但是确实上不是的,通过阅读 ...
- WPF依赖属性值源(BaseValueSource)
原文:WPF依赖属性值源(BaseValueSource) WPF依赖属性提供一个机制,可以获取依赖属性提供值的来源 其以BaseValueSource枚举表示 1.Default public ...
- WPF依赖属性(续)(3)依赖属性存储
原文:WPF依赖属性(续)(3)依赖属性存储 在之前的两篇,很多朋友参与了讨论,也说明各位对WPF/SL计数的热情,对DP系统各抒已见,当然也出现了一些分歧. 以下简称DP为依赖属性 ...
- WPF依赖属性(续)(1)
原文:WPF依赖属性(续)(1) 之前有写过几篇文章,详细地介绍了依赖属性的基本使用方法,如果你不想了解其内部实现机制的话,那么通过那两篇文章的介绍,足以应付平时的应用 ...
- WPF依赖属性(续)(2)依赖属性与附加属性的区别
原文:WPF依赖属性(续)(2)依赖属性与附加属性的区别 接上篇,感谢各位的评论,都是认为依赖属性的设计并不是为了节省内存,从大的方面而讲是如此.样式,数据绑定,动画样样都离不开它.这篇 ...
- 监听WPF依赖属性
原文:监听WPF依赖属性 当我们使用依赖属性的时候,有时需要监听它的变化,这在写自定义控件的时候十分有用, 下面介绍一种简单的方法. 如下使用DependencyPropertyDescripto ...
- WPF依赖属性的正确学习方法
前言 我在学习WPF的早期,对依赖属性理解一直都非常的不到位,其恶果就是,我每次在写依赖属性的时候,需要翻过去的代码来复制黏贴. 相信很多朋友有着和我相同的经历,所以这篇文章希望能帮助到那些刚刚开始学 ...
- WPF 依赖属性前言
WPF 依赖属性前言 在.net中,我们可以属性来获取或设置字段的值,不需要在编写额外的get和set方法,但这有一个前提,那就是需要在对象中拥有一个字段,才能在此字段的基础上获取或设置字段的值, ...
- WPF 依赖属性
依赖属性,简单的说,在WPF控件应用过程中,界面上直接可以引用的属性 如:<Button Content="aaa"></Button> Content称为 ...
随机推荐
- SHELL 变量引用
shell变量的引用非常重要,运用技巧灵活多变 变量的引用主要包含四类:双引号引用.单引号引用.反引号引用.反斜线引用 " " 双引号 屏蔽除美元符号$.反引号( ` )和反斜线( ...
- python根据正则表达式生成指定规律的网址
import os def file_name(file_dir): for root, dirs, files in os.walk(file_dir): print(root) #当前目录路径 p ...
- Scrapy框架安装与使用(基于windows系统)
"人生苦短,我用python".最近了解到一个很好的Spider框架--Scrapy,自己就按着官方文档装了一下,出了些问题,在这里记录一下,免得忘记. Scrapy的安装是基于T ...
- java集合(2)-Collection与Iterator接口
1 package com.j1803.collectionOfIterator; 2 import java.util.ArrayList; 3 import java.util.Collectio ...
- CF1330B题解
题意: 给定一个长为 \(n\) 序列 \(a\) ,问是否能分成两个排列,并输出方案 (排列:从 \(1-n\) 中选取不同的 \(n\) 个元素组成的序列) 思路: 观察数据范围可以猜出,这题 \ ...
- Java安全之XStream 漏洞分析
Java安全之XStream 漏洞分析 0x00 前言 好久没写漏洞分析文章了,最近感觉在审代码的时候,XStream 组件出现的频率比较高,借此来学习一波XStream的漏洞分析. 0x01 XSt ...
- JavaScript学习笔记:你必须要懂的原生JS(一)
1.原始类型有哪几种?null是对象吗?原始数据类型和复杂数据类型存储有什么区别? 原始类型有6种,分别是undefined,null,bool,string,number,symbol(ES6新增) ...
- 【LeetCode】404. 左叶子之和
404. 左叶子之和 知识点:二叉树 题目描述 计算给定二叉树的所有左叶子之和.. 示例 3 / \ 9 20 / \ 15 7 在这个二叉树中,有两个左叶子,分别是 9 和 15,所以返回 24 解 ...
- 第七篇--如何改变vs2017版的背景
改变背景 C:\Users\zsunny\AppData\Local\Microsoft\VisualStudio\15.0_9709afbe\Extensions\o0g0c52k.3od\Imag ...
- Python - 可变和不可变对象
前置知识 在 Python 中,一切皆为对象 Python 中不存在值传递,一切传递的都是对象的引用,也可以认为是传址 有哪些可变对象,哪些不可变对象? 不可变对象:字符串.元组.数字(int.flo ...