js
Object.defineProperty
对象的定义与赋值
经常使用的定义与赋值方法obj.prop =value
或者obj['prop']=value
Object.defineProperty()语法说明Object.defineProperty()
的作用就是直接在一个对象上定义一个新属性,或者修改一个已经存在的属性
Object.defineProperty(obj, prop, desc)
- obj 需要定义属性的当前对象
- prop 当前需要定义的属性名
- desc 属性描述符
一般通过为对象的属性赋值的情况下,对象的属性可以修改也可以删除,但是通过Object.defineProperty()定义属性,通过描述符的设置可以进行更精准的控制对象属性。
属性的特性以及内部属性
javacript 有三种类型的属性
- 命名数据属性:拥有一个确定的值的属性。这也是最常见的属性
- 命名访问器属性:通过
getter
和setter
进行读取和赋值的属性 - 内部属性:由JavaScript引擎内部使用的属性,不能通过JavaScript代码直接访问到,不过可以通过一些方法间接的读取和设置。比如,每个对象都有一个内部属性
[[Prototype]]
,你不能直接访问这个属性,但可以通过Object.getPrototypeOf()
方法间接的读取到它的值。虽然内部属性通常用一个双吕括号包围的名称来表示,但实际上这并不是它们的名字,它们是一种抽象操作,是不可见的,根本没有上面两种属性有的那种字符串类型的属性
属性描述符
通过Object.defineProperty()为对象定义属性,有两种形式,且不能混合使用,分别为数据描述符,存取描述符,下面分别描述下两者的区别:
数据描述符 --特有的两个属性(value,writable)
let Person = {}
Object.defineProperty(Person, 'name', { value: 'jack', writable: true // 是否可以改变 })
注意,如果描述符中的某些属性被省略,会使用以下默认规则:
存取描述符 --是由一对 getter、setter 函数功能来描述的属性get
:一个给属性提供getter
的方法,如果没有getter
则为undefined
。该方法返回值被用作属性值。默认为undefined
。set
:一个给属性提供setter
的方法,如果没有setter
则为undefined
。该方法将接受唯一参数,并将该参数的新值分配给该属性。默认值为undefined
。
let Person = {} let temp = null Object.defineProperty(Person, 'name', { get: function () { return temp }, set: function (val) { temp = val } })
数据描述符和存取描述均具有以下描述符
- configrable 描述属性是否配置,以及可否删除
- enumerable 描述属性是否会出现在for in 或者 Object.keys()的遍历中
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>回顾Object.defineproperty方法</title>
</head>
<body>
<script type="text/javascript">
let number = 18
let person = {
name: '张三',
sex: '男',
}
Object.defineProperty(person, 'age', {
// value:18,
// enumerable:true, //控制属性是否可以枚举,默认值是false
// writable:true, //控制属性是否可以被修改,默认值是false
// configurable:true //控制属性是否可以被删除,默认值是false
//当有人读取person的age属性时,get函数(getter)就会被调用,且返回值就是age的值
get() {
console.log('有人读取age属性了')
return number
},
//当有人修改person的age属性时,set函数(setter)就会被调用,且会收到修改的具体值
set(value) {
console.log('有人修改了age属性,且值是', value)
number = value
}
})
// console.log(Object.keys(person))
person.age = 10
var a = person.age
console.log(person)
</script>
</body>
</html>
数据代理
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>何为数据代理</title>
</head>
<body>
<!-- 数据代理:通过一个对象代理对另一个对象中属性的操作(读/写)-->
<script type="text/javascript" >
let obj = {x:100}
let obj2 = {y:200}
Object.defineProperty(obj2,'x',{
get(){
return obj.x
},
set(value){
obj.x = value
}
})
console.log (obj2.x)
</script>
</body>
</html>
Vue3的响应式原理
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Document</title>
</head>
<body>
<script type="text/javascript" >
//源数据
let person = {
name:'张三',
age:18
}
//模拟Vue2中实现响应式
//#region
/* let p = {}
Object.defineProperty(p,'name',{
configurable:true,
get(){ //有人读取name时调用
return person.name
},
set(value){ //有人修改name时调用
console.log('有人修改了name属性,我发现了,我要去更新界面!')
person.name = value
}
})
Object.defineProperty(p,'age',{
get(){ //有人读取age时调用
return person.age
},
set(value){ //有人修改age时调用
console.log('有人修改了age属性,我发现了,我要去更新界面!')
person.age = value
}
}) */
//#endregion
//模拟Vue3中实现响应式
//#region
const p = new Proxy(person,{
//有人读取p的某个属性时调用
get(target,propName,a){
console.log(target)
let per= {
name:'张三',
age:18
}
var pp=per["name"]
console.log(`有人读取了p身上的${propName}属性`)
return Reflect.get(target,propName)
},
//有人修改p的某个属性、或给p追加某个属性时调用
set(target,propName,value){
console.log(`有人修改了p身上的${propName}属性,我要去更新界面了!`)
console.log(target===person)
target[propName]=value
//Reflect.set(target,propName,value)
},
//有人删除p的某个属性时调用
deleteProperty(target,propName){
console.log(`有人删除了p身上的${propName}属性,我要去更新界面了!`)
return Reflect.deleteProperty(target,propName)
}
})
//#endregion
let obj = {a:1,b:2}
//通过Object.defineProperty去操作
//#region
try {
Object.defineProperty(obj,'c',{
get(){
return 3
}
})
Object.defineProperty(obj,'c',{
get(){
return 4
}
})
} catch (error) {
console.log(error)
}
//#endregion
//通过Reflect.defineProperty去操作
//#region
/* const x1 = Reflect.defineProperty(obj,'c',{
get(){
return 3
}
})
console.log(x1)
const x2 = Reflect.defineProperty(obj,'c',{
get(){
return 4
}
})
if(x2){
console.log('某某某操作成功了!')
}else{
console.log('某某某操作失败了!')
} */
//#endregion
// console.log('@@@')
</script>
</body>
</html>
模拟一个数据监测
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Document</title>
</head>
<body>
<script type="text/javascript">
let data = {
name: 'gqk2',
name: 'gqk1',
address: '北京',
}
//创建一个监视的实例对象,用于监视data中属性的变化
const obs = new Observer(data)
console.log(obs, "obs")
//准备一个vm实例对象
let vm = {}
vm._data = data = obs
console.log(data, "data")
data.name = "gg"
function Observer(obj) {
//汇总对象中所有的属性形成一个数组
const keys = Object.keys(obj)
console.log(keys, "keys")
//遍历
keys.forEach((k) => {
console.log(k, "k")
console.log(this, "this")
Object.defineProperty(this, k, {
get() {
return obj[k]
},
set(val) {
console.log(`${k}被改了,我要去解析模板,生成虚拟DOM.....我要开始忙了`)
obj[k] = val
}
})
})
}
</script>
</body>
</html>
Vue中的数据代理
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Vue中的数据代理</title>
<!-- 引入Vue -->
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<!--
1.Vue中的数据代理:
通过vm对象来代理data对象中属性的操作(读/写)
2.Vue中数据代理的好处:
更加方便的操作data中的数据
3.基本原理:
通过Object.defineProperty()把data对象中所有属性添加到vm上。
为每一个添加到vm上的属性,都指定一个getter/setter。
在getter/setter内部去操作(读/写)data中对应的属性。
-->
<!-- 准备好一个容器-->
<div id="root">
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
const vm = new Vue({
el:'#root',
data:{
name:'gqk',
address:'gqk园'
}
})
console.log(vm)
setTimeout(console.log(vm),5000)
</script>
</html>
WPF属性
附加属性是说一个属性本来不属于某个对象,但由于某种需求而被后来附加上,也就是把对象放入一个特定环境后对象才具有的属性就称为附加属性,附加属性的作用就是将属性与数据类型解耦,让数据类型的设计更加灵活。
附加属性是特殊的依赖属性,用于非定义(不是C# 微软自己原来定义的属性,不是F12定义能查看到的)该属性的类 例如Grid面板的Row、Column,Canvas面板的Left、Right DockPanel面板的Dock都是附加属性。
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid ShowGridLines="True">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Button Grid.Row="1" Grid.Column="1" Content="OK" />
<Canvas>
<Slider x:Name="sliderX" Canvas.Top="10" Canvas.Left="10" Width="200"
Minimum="50" Maximum="200"/>
<Slider x:Name="sliderY" Canvas.Top="40" Canvas.Left="10" Width="200" Minimum="50"
Maximum="200"/>
<Ellipse Fill="Blue" Width="30" Height="30" Canvas.Left="{Binding
ElementName=sliderX,Path=Value}" Canvas.Top="{Binding
ElementName=sliderY,Path=Value}"/>
</Canvas>
</Grid>
</Window>
在FuJiaShuXing(附加属性).xaml.cs文件里面添加附件属性,附件属性的名称为MyAttachedFontSize,使用快捷方式创建附件属性:输入propa,连续按两下Tab键
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
//文献
namespace WenXian
{
/// <summary>
/// FuJiaShuXing.xaml 的交互逻辑 附加属性
/// </summary>
public partial class FuJiaShuXing : UserControl
{
public FuJiaShuXing()
{
InitializeComponent();
}
public static int GetMyAttachedFontSize(DependencyObject obj)
{
return (int)obj.GetValue(MyAttachedFontSizeProperty);
}
public static void SetMyAttachedFontSize(DependencyObject obj, int value)
{
obj.SetValue(MyAttachedFontSizeProperty, value);
}
public static readonly DependencyProperty MyAttachedFontSizeProperty =
DependencyProperty.RegisterAttached("MyAttachedFontSize", typeof(int), typeof(FuJiaShuXing),
new PropertyMetadata((s, e) =>
{
//获取FuJiaShuXing用户控件,s代表Textbox,
// FuJiaShuXing用户控件是TextBox的父级的父级的父级控件
var mdp = (((s as FrameworkElement).Parent as FrameworkElement).Parent
as FrameworkElement).Parent as FuJiaShuXing;
//更改用户控件的FontSize的值
if (mdp != null && e.NewValue != null)
{
var fontsize = 9;
int.TryParse(e.NewValue.ToString(), out fontsize);
mdp.FontSize = fontsize;
}
}));
}
}
XAML界面:
<UserControl x:Class="WenXian.FuJiaShuXing "
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:p="clr-namespace: WenXian"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<StackPanel>
<TextBox p:FuJiaShuXing.MyAttachedFontSize="{Binding Path=Text,
RelativeSource={RelativeSource Mode=Self}}" ></TextBox>
<TextBlock>通过附加属性修改FontSize的大小</TextBlock>
</StackPanel>
</Grid>
</UserControl>
主界面测试附件属性:
<Window x:Class="WenXian.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:p="clr-namespace:WenXian"
Title="MainWindow" Height="350" Width="525" WindowStartupLocation="CenterScreen">
<Grid >
<StackPanel>
<TextBlock>请输入字体的大小</TextBlock>
<p:FuJiaShuXing></p:FuJiaShuXing>
</StackPanel>
</Grid>
</Window>
附加属性-实现密码框的密码绑定-依赖属性的传递(继承)
正如绑定TextBox控件的Text属性一样, 我们希望能够将PasswordBox空间的Password属性进行绑定, 比如在MVVM模式中,这似乎是必须的, 但可惜的是, Password属性是不支持绑定的(不是依赖属性, 也没有实现INotifyPropertyChanged).
这可能是出于安全性的考虑. 但在我们的系统为了实现View层密码框中的密码与后台其它层之间的密码属性之间的绑定, 可以采取如下思路: 将密码框的密码和某一个缓冲区进行同步, 缓冲区在和后台进行绑定. 其中密码框与缓冲区之间的同步可采用事件进行通知, 并将缓冲区打造成依赖属性, 然后缓冲区就支持绑定了, 并给后台提供正确的密码.
缓冲区可以是哈希表或其他字典结构, 以便将密码框和缓冲区中的密码一 一对应起来, 也可以使AttachProperty(附加属性), 其实附加属性的机制也就是对缓存了的一个大字典进行操作。
public static class PasswordBoxBindingHelper
{
public static bool GetIsPasswordBindingEnabled(DependencyObject obj)
{
return (bool)obj.GetValue(IsPasswordBindingEnabledProperty);
}
public static void SetIsPasswordBindingEnabled(DependencyObject obj, bool value)
{
obj.SetValue(IsPasswordBindingEnabledProperty, value);
}
public static readonly DependencyProperty IsPasswordBindingEnabledProperty =
DependencyProperty.RegisterAttached("IsPasswordBindingEnabled", typeof(bool),
typeof(PasswordBoxBindingHelper),
new UIPropertyMetadata(false, OnIsPasswordBindingEnabledChanged));
private static void OnIsPasswordBindingEnabledChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var passwordBox = obj as PasswordBox;
if(passwordBox != null)
{
passwordBox.PasswordChanged -= PasswordBoxPasswordChanged;
if ((bool)e.NewValue)
{
passwordBox.PasswordChanged += PasswordBoxPasswordChanged;
}
}
}
//when the passwordBox's password changed, update the buffer
static void PasswordBoxPasswordChanged(object sender, RoutedEventArgs e)
{
var passwordBox = (PasswordBox) sender;
if (!String.Equals(GetBindedPassword(passwordBox),passwordBox.Password))
{
SetBindedPassword(passwordBox, passwordBox.Password);
}
}
public static string GetBindedPassword(DependencyObject obj)
{
return (string)obj.GetValue(BindedPasswordProperty);
}
public static void SetBindedPassword(DependencyObject obj, string value)
{
obj.SetValue(BindedPasswordProperty, value);
}
public static readonly DependencyProperty BindedPasswordProperty =
DependencyProperty.RegisterAttached("BindedPassword", typeof(string),
typeof(PasswordBoxBindingHelper),
new UIPropertyMetadata(string.Empty, OnBindedPasswordChanged));
//when the buffer changed, upate the passwordBox's password
private static void OnBindedPasswordChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var passwordBox = obj as PasswordBox;
if (passwordBox != null)
{
passwordBox.Password = e.NewValue == null ? string.Empty : e.NewValue.ToString();
}
}
}
在View层, 如下使用便可以了:
<PasswordBox Helpers:PasswordBoxBindingHelper.IsPasswordBindingEnabled="True"
Helpers:PasswordBoxBindingHelper.BindedPassword=
"{Binding Path=Password, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
另外, 在更改了密码框的密码后, 需要手动更新密码框插入符(CaretIndex)的位置, 可惜的是, 密码框并没有给我们提供这样的属性或方法(TextBox有, PasswordBox没有), 可以采用下面的方法来设置:
private static void SetPasswordBoxSelection(PasswordBox passwordBox, int start, int length)
{
var select = passwordBox.GetType().GetMethod("Select",
BindingFlags.Instance | BindingFlags.NonPublic);
select.Invoke(passwordBox, new object[] { start, length });
}
依赖属性的传递,在XAML逻辑树上, 内部的XAML元素,关联了外围XAML元素同名依赖属性值
<Window x:Class="Custom_DPInherited.DPInherited"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
FontSize="18"
Title="依赖属性的继承">
<StackPanel >
<Label Content="继承自Window的FontSize" />
<Label Content="显式设置FontSize"
TextElement.FontSize="36"/>
<StatusBar>Statusbar没有继承自Window的FontSize</StatusBar>
</StackPanel>
</Window>
在上面XAML代码中。Window.FontSize设置会影响所有内部子元素字体大小,这就是依赖属性的值传递。如第一个Label没有定义FontSize,所以它继承了Window.FontSize值。但一旦子元素提供了显式设置,这种继承就会被打断,所以Window.FontSize值对于第二个Label不再起作用。
但是,并不是所有元素都支持属性值继承的,如StatusBar、Tooptip和Menu控件。另外,StatusBar等控件截获了从父元素继承来的属性,使得该属性也不会影响StatusBar控件的子元素。例如,如果我们在StatusBar中添加一个Button。那么这个Button的FontSize属性也不会发生改变,其值为默认值
获取外围依赖属性值
定义自定义依赖属性时,可通过AddOwer方法可以使依赖属性,使用外围元素的依赖属性值。具体的实现代码如下所示:
public class CustomStackPanel : StackPanel
{
public static readonly DependencyProperty MinDateProperty;
static CustomStackPanel()
{
MinDateProperty = DependencyProperty.Register("MinDate", typeof(DateTime), typeof(CustomStackPanel), new FrameworkPropertyMetadata(DateTime.MinValue, FrameworkPropertyMetadataOptions.Inherits));
}
public DateTime MinDate
{
get { return (DateTime)GetValue(MinDateProperty); }
set { SetValue(MinDateProperty, value); }
}
}
public class CustomButton :Button
{
private static readonly DependencyProperty MinDateProperty;
static CustomButton()
{
// AddOwner方法指定依赖属性的所有者,从而实现依赖属性的传递,即CustomStackPanel的MinDate属性可以传递给CustomButton控件。
// 注意FrameworkPropertyMetadataOptions的值为Inherits 此依赖属性的值将由子元素继承。
MinDateProperty = CustomStackPanel.MinDateProperty.AddOwner(typeof(CustomButton), new FrameworkPropertyMetadata(DateTime.MinValue, FrameworkPropertyMetadataOptions.Inherits));
}
public DateTime MinDate
{
get { return (DateTime)GetValue(MinDateProperty); }
set { SetValue(MinDateProperty, value); }
}
}
<Window x:Class="Custom_DPInherited.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Custom_DPInherited"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Title="实现自定义依赖属性的值传递" Height="350" Width="525">
<Grid>
<local:CustomStackPanel x:Name="customStackPanle" MinDate="{x:Static sys:DateTime.Now}">
<!--CustomStackPanel的依赖属性-->
<ContentPresenter Content="{Binding Path=MinDate, ElementName=customStackPanle}"/>
<local:CustomButton Content="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=MinDate}" Height="25"/>
</local:CustomStackPanel>
</Grid>
</Window>
WPF 依赖属性详解
简介
当您开始使用 WPF 开发应用程序时,您很快就会遇到 DependencyProperties。它们看起来与普通的 .NET 属性非常相似,但背后的概念要复杂和强大得多。
主要区别在于,普通 .NET 属性的值是直接从类中的私有成员读取的,而 DependencyProperty 的值是在调用从 DependencyObject 继承的 GetValue() 方法时动态解析的。
当您设置依赖属性的值时,它不会存储在对象的字段中,而是存储在基类 DependencyObject 提供的键和值的字典中。条目的键是属性的名称,值是您要设置的值。
依赖属性的优点是
减少内存占用
当您认为 UI 控件的 90% 以上的属性通常保持其初始值时,为每个属性存储一个字段是一种巨大的消耗。依赖属性通过仅在实例中存储修改的属性来解决这些问题。默认值在依赖属性中存储一次。
值继承:
访问依赖项属性时,将使用值解析策略解析该值。如果未设置本地值,则依赖项属性将向上导航逻辑树,直到找到值。当您在根元素上设置FontSize时,它将应用于下面的所有文本块,除非您覆盖该值。
更改通知:
依赖属性具有内置的更改通知机制。通过在属性元数据中注册回调,您会在属性值更改时收到通知。这也被数据绑定使用。
值解析策略
每次访问依赖属性时,它都会按照从高到低的优先级在内部解析值。它检查本地值是否可用,如果不可用,如果自定义样式触发器处于活动状态,…并继续直到找到一个值。最后,默认值始终可用。
依赖属性 背后的魔力
每个 WPF 控件都将一组 DependencyProperties 注册到静态 DependencyProperty 类。它们中的每一个都包含一个键 - 每个类型必须是唯一的 - 和一个包含回调和默认值的元数据。
所有想要使用 DependencyProperties 的类型都必须从 DependencyObject 派生。这个基类定义了一个键值字典,其中包含依赖属性的本地值。条目的键是使用依赖属性定义的键。
当您通过其 .NET 属性包装器访问依赖属性时,它会在内部调用 GetValue(DependencyProperty) 来访问该值。此方法通过使用下面详细说明的值解析策略来解析值。如果本地值可用,它会直接从字典中读取它。如果没有设置值,则向上查找逻辑树并搜索继承的值。如果未找到值,则采用属性元数据中定义的默认值。这个序列有点简化,但它 显示主要概念。
![](https://i-blog.csdnimg.cn/blog_migrate/402294975019b286440c7aac463c31f2.png)
如何创建 DependencyProperty
要创建 DependencyProperty,请将类型为 DepdencyProperty 的静态字段添加到您的类型并调用 DependencyProperty.Register() 以创建依赖项属性的实例。 DependendyProperty 的名称必须始终以 …Property 结尾。这是 WPF 中的命名约定。
要使其可作为普通 .NET 属性访问,您需要添加一个属性包装器。这个包装器除了通过使用从 DependencyObject 继承的 GetValue() 和 SetValue() 方法并将 DependencyProperty 作为键传递来在内部获取和设置值外,什么都不做。
重要提示:不要向这些属性添加任何逻辑,因为只有在您从代码设置属性时才会调用它们。如果从 XAML 设置属性,则直接调用 SetValue() 方法。
如果您使用的是 Visual Studio,则可以键入 propdp 并点击 2x 选项卡来创建依赖项属性。
// Dependency Property
public static readonly DependencyProperty CurrentTimeProperty =
DependencyProperty.Register( "CurrentTime", typeof(DateTime),
typeof(MyClockControl), new FrameworkPropertyMetadata(DateTime.Now));
// .NET Property wrapper
public DateTime CurrentTime
{
get { return (DateTime)GetValue(CurrentTimeProperty); }
set { SetValue(CurrentTimeProperty, value); }
}
每个 DependencyProperty 都提供用于更改通知、值强制和验证的回调。这些回调在依赖属性上注册。
new FrameworkPropertyMetadata( DateTime.Now,
OnCurrentTimePropertyChanged,
OnCoerceCurrentTimeProperty ),
OnValidateCurrentTimeProperty );
值更改回调
更改通知回调是一个静态方法,每次 TimeProperty 的值更改时都会调用该方法。新值在 EventArgs 中传递,更改值的对象作为源传递。
private static void OnCurrentTimePropertyChanged(DependencyObject source,
DependencyPropertyChangedEventArgs e)
{
MyClockControl control = source as MyClockControl;
DateTime time = (DateTime)e.NewValue;
// Put some update logic here...
}
强制值回调
强制回调允许您在值超出边界时调整该值而不会引发异常。一个很好的例子是进度条,其值设置为低于最小值或高于最大值。在这种情况下,我们可以在允许的边界内强制该值。在以下示例中,我们将时间限制为过去。
private static object OnCoerceTimeProperty( DependencyObject sender, object data )
{
if ((DateTime)data > DateTime.Now )
{
data = DateTime.Now;
}
return data;
}
验证回调
在验证回调中,您检查设置值是否有效。如果返回 false,则会抛出 ArgumentException。在我们的示例需求中,数据是 DateTime 的实例。
private static bool OnValidateTimeProperty(object data)
{
return data is DateTime;
}
只读依赖属性
WPF 控件的某些依赖属性是只读的。它们通常用于报告控件的状态,例如 IsMouseOver 属性。为这个值提供一个 setter 是没有意义的。
也许您会问自己,为什么不使用普通的 .NET 属性?一个重要的原因是您不能在普通的 .NET 属性上设置触发器。
创建只读属性类似于创建常规 DependencyProperty。不是调用 DependencyProperty.Register(),而是调用 DependencyProperty.RegisterReadonly()。这会返回一个 DependencyPropertyKey。此密钥应存储在您的类的私有或受保护的静态只读字段中。该键使您可以访问从类中设置值并将其用作普通依赖属性。
第二件事是注册分配给 DependencyPropertyKey.DependencyProperty 的公共依赖属性。此属性是可以从外部访问的只读属性。
// Register the private key to set the value
private static readonly DependencyPropertyKey IsMouseOverPropertyKey =
DependencyProperty.RegisterReadOnly("IsMouseOver",
typeof(bool), typeof(MyClass),
new FrameworkPropertyMetadata(false));
// Register the public property to get the value
public static readonly DependencyProperty IsMouseoverProperty =
IsMouseOverPropertyKey.DependencyProperty;
// .NET Property wrapper
public int IsMouseOver
{
get { return (bool)GetValue(IsMouseoverProperty); }
private set { SetValue(IsMouseOverPropertyKey, value); }
}
附加属性
附加属性是一种特殊的 DependencyProperties。它们允许您将一个值附加到一个对该值一无所知的对象上。
这个概念的一个很好的例子是布局面板。每个布局面板需要不同的数据来对齐其子元素。 Canvas 需要 Top 和 Left,DockPanel 需要 Dock,等等。既然你可以写自己的布局面板,列表是无限的。所以你看,不可能在所有 WPF 控件上都拥有所有这些属性。
解决方案是附加属性。它们由在特定上下文中需要来自另一个控件的数据的控件定义。例如,由父布局面板对齐的元素。
若要设置附加属性的值,请在 XAML 中添加一个特性,并带有提供附加属性的元素的前缀。要设置在 Canvas 面板中对齐的按钮的 Canvas.Top 和 Canvas.Left 属性,您可以这样编写:
<Canvas>
<Button Canvas.Top="20" Canvas.Left="20" Content="Click me!"/>
</Canvas>
public static readonly DependencyProperty TopProperty =
DependencyProperty.RegisterAttached("Top",
typeof(double), typeof(Canvas),
new FrameworkPropertyMetadata(0d,
FrameworkPropertyMetadataOptions.Inherits));
public static void SetTop(UIElement element, double value)
{
element.SetValue(TopProperty, value);
}
public static double GetTop(UIElement element)
{
return (double)element.GetValue(TopProperty);
}
监听依赖属性变化
如果要侦听依赖项属性的更改,可以将定义该属性的类型子类化并覆盖属性元数据并传递 PropertyChangedCallback。但更简单的方法是通过调用 AddValueChanged() 获取 DependencyPropertyDescriptor 并连接回调
DependencyPropertyDescriptor textDescr = DependencyPropertyDescriptor.
FromProperty(TextBox.TextProperty, typeof(TextBox));
if (textDescr!= null)
{
textDescr.AddValueChanged(myTextBox, delegate
{
// Add your propery changed logic here...
});
}
如何清除本地值
因为 null 也是一个有效的本地值,所以有一个常量 DependencyProperty.UnsetValue 来描述一个未设置的值。
button1.ClearValue( Button.ContentProperty );