一、概要

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---依赖属性(一)的更多相关文章

  1. WPF依赖属性详解

    WPF依赖属性详解 WPF 依赖属性 英文译为 Dependency Properties,是WPF引入的一种新类型的属性,在WPF中有着极为广泛的应用,在WPF中对于WPF Dependency P ...

  2. WPF自学入门(五)WPF依赖属性

    在.NET中有事件也有属性,WPF中加入了路由事件,也加入了依赖属性.最近在写项目时还不知道WPF依赖属性是干什么用的,在使用依赖项属性的时候我都以为是在用.NET中的属性,但是确实上不是的,通过阅读 ...

  3. WPF依赖属性值源(BaseValueSource)

    原文:WPF依赖属性值源(BaseValueSource)   WPF依赖属性提供一个机制,可以获取依赖属性提供值的来源 其以BaseValueSource枚举表示 1.Default public ...

  4. WPF依赖属性(续)(3)依赖属性存储

    原文:WPF依赖属性(续)(3)依赖属性存储          在之前的两篇,很多朋友参与了讨论,也说明各位对WPF/SL计数的热情,对DP系统各抒已见,当然也出现了一些分歧. 以下简称DP为依赖属性 ...

  5. WPF依赖属性(续)(1)

    原文:WPF依赖属性(续)(1)                 之前有写过几篇文章,详细地介绍了依赖属性的基本使用方法,如果你不想了解其内部实现机制的话,那么通过那两篇文章的介绍,足以应付平时的应用 ...

  6. WPF依赖属性(续)(2)依赖属性与附加属性的区别

    原文:WPF依赖属性(续)(2)依赖属性与附加属性的区别        接上篇,感谢各位的评论,都是认为依赖属性的设计并不是为了节省内存,从大的方面而讲是如此.样式,数据绑定,动画样样都离不开它.这篇 ...

  7. 监听WPF依赖属性

    原文:监听WPF依赖属性 当我们使用依赖属性的时候,有时需要监听它的变化,这在写自定义控件的时候十分有用, 下面介绍一种简单的方法.   如下使用DependencyPropertyDescripto ...

  8. WPF依赖属性的正确学习方法

    前言 我在学习WPF的早期,对依赖属性理解一直都非常的不到位,其恶果就是,我每次在写依赖属性的时候,需要翻过去的代码来复制黏贴. 相信很多朋友有着和我相同的经历,所以这篇文章希望能帮助到那些刚刚开始学 ...

  9. WPF 依赖属性前言

    WPF 依赖属性前言 ​ 在.net中,我们可以属性来获取或设置字段的值,不需要在编写额外的get和set方法,但这有一个前提,那就是需要在对象中拥有一个字段,才能在此字段的基础上获取或设置字段的值, ...

  10. WPF 依赖属性

    依赖属性,简单的说,在WPF控件应用过程中,界面上直接可以引用的属性 如:<Button Content="aaa"></Button> Content称为 ...

随机推荐

  1. [刘阳Java]_easyui-panel组件入门级_第3讲

    EasyUI中的panel组件在前面一节中我们简单告诉了大家代码如何写.这一节我们会从panel的入门级开始讲起走,重点包括它的事件监听,属性tool介绍 1. 事件监听-通过data-options ...

  2. SpringBoot自动装配-自定义Start

    SpringBoot自动装配 在没有使用SpringBoot之前,使用ssm时配置redis需要在XML中配置端口号,地址,账号密码,连接池等等,而使用了SpringBoot后只需要在applicat ...

  3. 使用vue-preview报错Cannot read property 'open' of undefined

    最近在做一个vue项目中时,需要使用vue-preview插件制作缩略图,首先在终端使用npm i vue-preview -S指令安装了vue-preview插件,然后在main.js中,导入并引用 ...

  4. debian9 python环境设置

    file /usr/bin/python which python2 which python3 mv /usr/bin/python /usr/bin/python_bk ln -s /usr/bi ...

  5. Guava - Map

    创建Map 通常在创建map时使用new HashMap<>();的方法,guava提供了一个简洁的方法 Maps.newHashMap(); List转换Map List<Solu ...

  6. tomcat日志详解

    1 tomcat 日志详解 1.1 tomcat 日志配置文件 tomcat 对应日志的配置文件:tomcat目录下的/conf/logging.properties. tomcat 的日志等级有:日 ...

  7. 15Java进阶 进程

    1 线程控制 t.join():让主线程进入线程池,等待t执行完才执行. t.sleep():让线程阻塞,休眠一段时间,休眠结束后进入就绪状态.不会释放锁. t.yield():让线程让出CPU,从运 ...

  8. vue知识点---element el-date-picker 插件默认时间属性default-value怎么赋值?

    参考网址: http://www.imooc.com/wenda/detail/509359 默认值,你放到 v-model里面就好. v-model="time" data(){ ...

  9. 关于XP系统因以下文件的损坏或丢失,WINDOWS无法启动:\windows\system32\config\system的解决思路实践

    故事背景:在合肥项目中,现场一台使用超过6年的工控机,在近段时间内出现上述标题中出现的系统文件丢失问题 ,该问题重启复现,无法通过传统进入安全模式或者最后一次正确配置等方式进行修复,只能通过将repa ...

  10. Vue3学习第一例:Vue3架构入门

    入门 Vue3的教程很少,官方网站实例不好整,另外由于Python的Django也掌握了,学习这个有些让人眼乱.Vue项目创建后,在public目录下面自动生成了一个index.htm,里面有个div ...