WPF依赖属性的正确学习方法
前言
我在学习WPF的早期,对依赖属性理解一直都非常的不到位,其恶果就是,我每次在写依赖属性的时候,需要翻过去的代码来复制黏贴。
相信很多朋友有着和我相同的经历,所以这篇文章希望能帮助到那些刚刚开始学依赖属性的朋友。
那些[讨厌]的依赖属性的讲解文章
初学者肯定会面临一件事,就是百度,谷歌,或者MSDN来查看依赖属性的定义和使用,而这些文章虽然都写的很好,但,那是相对于已经学会使用依赖属性的朋友而言。
而对于初学者而言,说是误导都不过分。
比如,官网的这篇文章https://docs.microsoft.com/zh-cn/dotnet/framework/wpf/advanced/dependency-properties-overview
介绍依赖属性是这样。
public static readonly DependencyProperty IsSpinningProperty =
DependencyProperty.Register(
"IsSpinning", typeof(Boolean),
typeof(MyCode)
);
public bool IsSpinning
{
get { return (bool)GetValue(IsSpinningProperty); }
set { SetValue(IsSpinningProperty, value); }
}
他做了一个定义,然后告诉你,依赖属性的定义格式如此。
如果你是个初学者,你想不疑惑都很难。因为没人能把这种定义给背下来。
其结果就是,你要和我当初一样,每次定义依赖属性,都要去复制黏贴。但这并不是最大的恶果,最大的恶果是,因为太过复杂的定义,让你放弃了对他理解,就记住了依赖属性要复制黏贴,从而导致了,你丧失了对依赖属性灵活运用的能力。
正确的理解依赖属性
如何正确的理解依赖属性呢?
很简单,拆分一下就可以理解了。
现在我们来拆分依赖属性,首先拆分他的定义,将依赖和属性拆分。
我们先看属性,如下,我们定义了一个属性。
private bool _IsSpinning;
public bool IsSpinning
{
get { return _IsSpinning; }
set { _IsSpinning = value; }
}
然后我们使用DependencyProperty类定义一个对象,这个对象将作为IsSpinning属性的依赖,如下:
public static readonly DependencyProperty IsSpinningProperty
然后,我们在将这个依赖对象,注册到属性IsSpinning的所在类上,如下:
DependencyProperty.Register( "IsSpinning", typeof(bool), typeof(你的属性所在的类的名称));
从注册代码中,我们可以看到,他注册了三个信息:
1,当前DependencyProperty类定义的对象IsSpinningProperty,依赖于属性IsSpinning。
2,对象IsSpinningProperty的依赖类型与属性IsSpinning的类型一样都是bool。
3,对象IsSpinningProperty注册的类是声明属性IsSpinning的类,即,在其他类里,将看不到该依赖对象。
现在,我们做最后的操作,修改属性,将依赖对象IsSpinningProperty与属性IsSpinning绑定。
如何绑定呢?很简单,将我们属性定义里的【private bool _IsSpinning】替换为我们刚刚定义的依赖【IsSpinningProperty】即可。
public bool IsSpinning
{
get { return (bool)GetValue(IsSpinningProperty); }
set { SetValue(IsSpinningProperty, value); }
}
这里我们看到了,在给属性赋值和取值时,用到了GetValue和SetValue,他们俩是哪来的呢?
使用F12,我们跟踪进去,发现它们是类DependencyProperty里定义的方法,那么为什么我们在窗体里也可以用呢?
很简单,我们跟进一下Window的父类,发现最后的父类Visual继承了DependencyProperty,所以我们可以直接使用GetValue和SetValue来赋值和获取依赖对象的值。也就是只要是继承了类DependencyProperty的子类,都可以使用依赖属性。
完整版依赖属性定义代码:
public static readonly DependencyProperty IsSpinningProperty =
DependencyProperty.Register("IsSpinning", typeof(bool), typeof(DependecyUserControl));
public bool IsSpinning
{
get { return (bool)GetValue(IsSpinningProperty); }
set { SetValue(IsSpinningProperty, value); }
}
到这里,依赖属性的拆分就完事了,现在,大家应该很清楚依赖属性到底是什么了吧。
PS:有没有人曾经告诉你,依赖属性的命名必须是 属性名+Property,然后你还信以为真了。哈哈。
依赖属性的简单应用
现在让我们来自定义一个带依赖属性的系统控件来加深记忆。
public class KButton : Button
{
public static readonly DependencyProperty ForeImageProperty;
public static readonly DependencyProperty BackImageProperty;
public static readonly DependencyProperty MouseOverBackColorProperty;
public static readonly DependencyProperty StretchProperty;
static KButton()
{
ForeImageProperty = DependencyProperty.Register("ForeImage", typeof(string), typeof(KButton),null);
ForeImageProperty = DependencyProperty.Register("BackImage", typeof(string), typeof(KButton),null);
MouseOverBackColorProperty = DependencyProperty.Register("MouseOverBackColor", typeof(Brush), typeof(KButton), null);
StretchProperty = DependencyProperty.Register("Stretch", typeof(Stretch), typeof(KButton), null); DefaultStyleKeyProperty.OverrideMetadata(typeof(KButton), new FrameworkPropertyMetadata(typeof(KButton)));//使KButton去读取KButton类型的样式,而不是去读取Button的样式
} public string ForeImage
{
get { return (string)GetValue(ForeImageProperty); }
set { SetValue(ForeImageProperty, value); }
}
public string BackImage
{
get { return (string)GetValue(BackImageProperty); }
set { SetValue(BackImageProperty, value); }
}
public Brush MouseOverBackColor
{
get { return (Brush)GetValue(MouseOverBackColorProperty); }
set { SetValue(MouseOverBackColorProperty, value); }
}
public Stretch Stretch
{
get { return (Stretch)GetValue(StretchProperty); }
set { SetValue(StretchProperty, value); }
}
}
如上述代码所示,我们定义了一个继承至Button的类KButton。
在KButtion中,我们定义了四个依赖属性:
ForeImageProperty:按钮的前景图片。
BackImageProperty:按钮的背景图片。
MouseOverBackColorProperty:按钮在鼠标经过时的颜色。
StretchProperty:按钮图片的拉伸模式。
代码非常简洁,除了四个依赖属性之外,什么也没有;现在我们去定义Kbutton类型的样式。
为了演示方便,我直接将样式定义在了App.xaml文件内。
<Style TargetType="{x:Type local:KButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<DockPanel Name="dpCon" Width="{Binding Width, RelativeSource={x:Static RelativeSource.TemplatedParent}}"
Height="{Binding Height, RelativeSource={x:Static RelativeSource.TemplatedParent}}"
Background="{Binding Background, RelativeSource={x:Static RelativeSource.TemplatedParent}}"
ToolTip="{Binding ToolTip, RelativeSource={x:Static RelativeSource.TemplatedParent}}"
>
<DockPanel DockPanel.Dock="Top" Name="dpBtn">
<DockPanel.Background>
<ImageBrush ImageSource="{Binding ForeImage, RelativeSource={x:Static RelativeSource.TemplatedParent}}" Stretch="{Binding Stretch,RelativeSource={x:Static RelativeSource.TemplatedParent}}"/>
</DockPanel.Background>
<TextBlock FontSize="15" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="#f9fcff" Text="{Binding Content, RelativeSource={x:Static RelativeSource.TemplatedParent}}"></TextBlock>
</DockPanel>
</DockPanel>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding IsMouseOver,RelativeSource={x:Static RelativeSource.Self}}" Value="True">
<Setter Property="Background" TargetName="dpBtn">
<Setter.Value>
<ImageBrush ImageSource="{Binding BackImage, RelativeSource={x:Static RelativeSource.TemplatedParent}}" Stretch="{Binding Stretch,RelativeSource={x:Static RelativeSource.TemplatedParent}}"/>
</Setter.Value>
</Setter>
<Setter Property="Background" TargetName="dpCon" Value="{Binding MouseOverBackColor, RelativeSource={x:Static RelativeSource.TemplatedParent}}"></Setter>
</DataTrigger>
<DataTrigger Binding="{Binding BackImage,RelativeSource={x:Static RelativeSource.Self},Mode=TwoWay}" Value="{x:Null}">
<Setter Property="Background" TargetName="dpBtn">
<Setter.Value>
<ImageBrush ImageSource="{Binding ForeImage, RelativeSource={x:Static RelativeSource.TemplatedParent}}" Stretch="{Binding Stretch,RelativeSource={x:Static RelativeSource.TemplatedParent}}"/>
</Setter.Value>
</Setter>
</DataTrigger>
<Trigger Property="IsEnabled" Value="true"/>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Foreground" Value="Gray"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
样式代码如上所示,也非常简单,就是定义了一个模板,然后在模板里摆放好按钮背景图和按钮文字的位置。然后将我们之前定义好的依赖属性绑定到对应的值上。
其中需要注意的是,在模板中绑定自定义依赖属性,是使用RelativeSource.TemplatedParent的,如{Binding ForeImage, RelativeSource={x:Static RelativeSource.TemplatedParent}}。
而在模板的数据事件DataTrigger中,绑定依赖属性的模式却是分两种的。
第一种,绑定数据事件DataTrigger的条件时,使用RelativeSource.Self,如{Binding IsMouseOver,RelativeSource={x:Static RelativeSource.Self}}。
第二种,条件成立,触发模板变化时,使用RelativeSource.TemplatedParent,如{Binding BackImage, RelativeSource={x:Static RelativeSource.TemplatedParent}}。
----------------------------------------------------------------------------------------------------
现在我们使用下我们制作好的自定义控件,代码如下所示:
<DockPanel>
<StackPanel>
<local:KButton Height="50" Width="50" Stretch="None" ForeImage="/Image/关闭.png" BackImage="/Image/关闭退出.png" Background="Gray" MouseOverBackColor="Brown"/>
<local:KButton Height="50" Width="50" Margin="0,10,0,0" Stretch="None" ForeImage="/Image/关闭.png" Background="Gray" MouseOverBackColor="Brown"/>
<local:KButton Height="100" Width="100" Margin="0,10,0,0" Content="篮子" Stretch="Fill" ForeImage="/Image/篮子.png" Background="Gray" MouseOverBackColor="Brown"/>
</StackPanel>
</DockPanel>
界面效果如下:

自定义用户控件中使用依赖属性
首先我们添加新项,然后选择用户控件。

然后,我们添加一个依赖属性HeaderTitle,同时设置当前控件的DataContext为自身—this.DataContext = this。
public string HeaderTitle
{
get { return (string)GetValue(HeaderTitleProperty); }
set { SetValue(HeaderTitleProperty, value); }
}
public static readonly DependencyProperty HeaderTitleProperty = DependencyProperty.Register("HeaderTitle", typeof(string), typeof(DependecyUserControl), null);
public DependecyUserControl()
{
this.DataContext = this;
InitializeComponent();
}
现在,我们在用户控件的Xaml页面添加一个TextBlock,并绑定他的Text为我们刚刚定义的HeaderTitle,代码如下所示。
<Grid>
<TextBlock Text = "{Binding HeaderTitle}" TextAlignment="Center"></TextBlock>
</Grid>
接着我们回到主窗体,引用这个用户控件,代码如下所示:
<local:DependecyUserControl Height = "30" HeaderTitle="我是Header" DockPanel.Dock="Top"></local:DependecyUserControl>
运行结果:

可以看到,我们成功在主页面设置了用户控件的依赖属性,并让他成功的绑定到了用户控件中的TextBlock的Text属性。也就是说,我们简单的实现了Header的Title动态设置。
结语
WPF拥有非常强大的自定义能力,而,正确的学会了依赖属性是体会到它强大的第一步。
----------------------------------------------------------------------------------------------------
到此WPF依赖属性的正确学习方法就已经讲解完成了。
代码已经传到Github上了,欢迎大家下载。
Github地址:https://github.com/kiba518/WpfDependency
----------------------------------------------------------------------------------------------------
注:此文章为原创,任何形式的转载都请联系作者获得授权并注明出处!
若您觉得这篇文章还不错,请点击下方的【推荐】,非常感谢!
https://www.cnblogs.com/kiba/p/11149147.html

WPF依赖属性的正确学习方法的更多相关文章
- WPF自学入门(五)WPF依赖属性
在.NET中有事件也有属性,WPF中加入了路由事件,也加入了依赖属性.最近在写项目时还不知道WPF依赖属性是干什么用的,在使用依赖项属性的时候我都以为是在用.NET中的属性,但是确实上不是的,通过阅读 ...
- WPF依赖属性详解
WPF依赖属性详解 WPF 依赖属性 英文译为 Dependency Properties,是WPF引入的一种新类型的属性,在WPF中有着极为广泛的应用,在WPF中对于WPF Dependency P ...
- 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 依赖属性前言 在.net中,我们可以属性来获取或设置字段的值,不需要在编写额外的get和set方法,但这有一个前提,那就是需要在对象中拥有一个字段,才能在此字段的基础上获取或设置字段的值, ...
- WPF依赖属性
原文:http://www.cnblogs.com/xiongpq/archive/2010/06/29/1767905.html 概述: Windows Presentation Foundatio ...
随机推荐
- Qt+QZXing编写识别二维码的程序
本人最近在用Qt编写程序,需要用编写二维码识别功能.在网上搜寻一番,找到了QZXing.配置过程中确实出了一大把汗,这里我写这篇文章记录配置方法,替后人省一把汗吧!我的开发环境:MSVC2010 + ...
- android核心系列--2,关于任务栈(task)
一,任务 任务是由界面组件组成的一个栈,这些界面组件可以来自多个进程,多个应用,为共同完成一项任务而存在,比如写邮件时会用到邮件应用和联系人应用中的界面组件,这些界面组件在同一个任务中运行. 二,界面 ...
- 30442数据操纵语言DML
5.5 SQL的数据操纵功能 5.5.1 数据插入 使用CREATE语句创建的数据表还只是一个“空壳”,表中没有任何数据.利用SQL语言提供的INSERT语句可以完成向数据表插入数据的任务. INSE ...
- java集合的方法及使用详解
一.java集合的分类及相互之间的关系 Collection接口:向下提供了List和Set两个子接口 |------List接口:存储有序的,存储元素可以重复 |------ArrayList(主要 ...
- asp.net core系列 66 Dapper介绍--Micro-ORM
一.概述 目前对于.net的数据访问ORM工具很多,EF和EF Core是一个重量级的框架.最近在搭建新的项目架构,来学习一下轻量级的数据访问ORM工具Dapper.Dapper支持SQL Serve ...
- 你必须了解的java内存管理机制(三)-垃圾标记
本文在个人技术博客不同步发布,详情可用力戳 亦可扫描屏幕右侧二维码关注个人公众号,公众号内有个人联系方式,等你来撩... 相关链接(注:文章讲解JVM以Hotspot虚拟机为例,jdk版本为1.8) ...
- Java Collection秋招复习
抽象类和接口的区别 我们先来看一下抽象类 * @auther draymonder */ public abstract class AbstractClassTest { private int T ...
- 曹工说Tomcat3:深入理解 Tomcat Digester
一.前言 我写博客主要靠自己实战,理论知识不是很强,要全面介绍Tomcat Digester,还是需要一定的理论功底.翻阅了一些介绍 Digester 的书籍.博客,发现不是很系统,最后发现还是官方文 ...
- T4生成实体和简单的CRUD操作
主要跟大家交流下T4,我这里针对的是mysql,我本人比较喜欢用mysql,所以语法针对mysql,所以你要准备mysql的DLL了,同理sqlserver差不多,有兴趣可以自己写写,首先网上找了一个 ...
- 安装win7和linux [ubuntu14]双系统
想体验一把ubuntu18.10最新桌面版的快感,但是windows上面的数据又删除不得,所以百度了一下,win7和linux双系统的安装教程. 一.首先在win7上创建新的分区 https://ji ...