1 依赖属性
1.1 官方依赖属性最终值选取过程
第一,确定Base Value,对同一个属性的赋值可能发生在很多地方。还用Button的宽度来进行举例,可能在Style或者Trigger中对其进行赋值,也可能在xaml中进行赋值(等同与在代码中赋值),这个Base Value就要确定这些值中优先级最高的值,把它作为Base Value;
<Window.Resources>
<Style TargetType="Button">
<Setter Property="Width" Value="100"/>
<Setter Property="Height" Value="30"/>
<Style.Triggers>
<Trigger Property="IsPressed" Value="False">
<Setter Property="Width" Value="3000"/>
</Trigger>
</Style.Triggers>
</Style>
<loc:WidthConvertr x:Key="WidthConverter"/>
</Window.Resources>
<Grid>
<Button Name="ButtonTest"
FontSize="13"
Background="Blue"
>ButtonSubSub</Button>
</Grid>
在代码中查看ValueSource截图为图1
看到BaseValueSource是StyleTrigger类型的,BaseValue在选取的规则中,都应当按照如下的优先级别进行选取,从上到下是依次增大的,可以看到StyleTrigger比Style要高,这和上边的xaml中示例中的相同,如果把Trigger去掉,那么BaseValueSource是Style了。如果xaml中什么都没有设定,在代码中也没有赋值,那么,BaseValueSource就会是Default类型;
public enum BaseValueSource { Unknown = 0,Default = 1,Inherited = 2,DefaultStyle = 3,DefaultStyleTrigger = 4,Style = 5,TemplateTrigger = 6,StyleTrigger = 7,ImplicitStyleReference = 8,ParentTemplate = 9,ParentTemplateTrigger = 10,Local = 11,}
第二,获取绑定源中的值。这个步骤容易产生理解上的混乱,如果在第一步中,设置了LocalValue,即1.1中阐述的Binding或者在xaml中赋值,并且如果是Binding的话,就会经过第二步的计算,将其转化成一个实际的值;如果不是,就直接判断以下的情况;
第三,获取动画值。如果当前属性正在作动画,那么因动画而产生的值会优于前面获得的值,这个也就是WPF中常说的动画优先;
第四,对最终值进行强制值约定。利用在FrameworkPropertyMetadata中传入了CoerceValueCallback,进行数据的逻辑限制,例如边界限制,特殊值限制等。
第五,对最终值进行最后验证。利用在Register的时候传入了ValidateValueCallback;
参考http://www.cnblogs.com/Zhouyongh/archive/2009/10/20/1586278.html
1.2 LocalValue中的直接赋值和BindingExpress
依赖属性中的LocalValue,只能设置通过在xaml中赋值或者直接在代码中进行赋值或者通过Binding对赋值行绑定,但三种方式不能同时在依赖属性中进行存在,也就是说,这三种给依赖属性提供值的方式,在运行时,最后调用者覆盖之前为LocalValue的赋值,并且发生作用。
例如:先编写一个宽度的数据源
public class WidhtHeightBindingedClass : INotifyPropertyChanged { private double _height; public double Height { get { return _height; } set { _height = value; Notify("Height"); } } private double _width; public double Width { get { return _width; } set { _width = value; Notify("Width"); } } public event PropertyChangedEventHandler PropertyChanged; private void Notify(string proerptyName) { var handler = this.PropertyChanged; if (handler != null) { handler(this,new PropertyChangedEventArgs(proerptyName)); } } }我们可以在xaml中,设置Button的宽度,
<Grid> <Button Name="ButtonTest" FontSize="13" Background="Blue" Width="400" >ButtonSubSub</Button> </Grid>
private void Window_Loaded(object sender,RoutedEventArgs e) { //@Test 1: valueLocalValue中,BaseValueSource = Local; IsAnnimated = false; IsCoerced = false; IsExpression = false //@Test 1: LocalValue = 400 object valueLocalValue = ButtonTest.ReadLocalValue(FrameworkElement.WidthProperty);// ValueSource valueSource = DependencyPropertyHelper.GetValueSource( ButtonTest,FrameworkElement.WidthProperty); //@Test 2: afterBindingValueSource.BaseValueSource = Local; // IsAnnimated = false; // IsCoerced = false; // IsExpression = true //@Test 2: afterBindingLocalValue: BindingExpression 是一个BindingExpression类型,不再是值; // 其中DataSource= WidhtHeightBindingedClass; Binding binding = new Binding("Width"); binding.Source = this.WidthHeightSource; BindingOperations.SetBinding(ButtonTest,WidthProperty,binding); object afterBindingLocalValue = ButtonTest.ReadLocalValue(FrameworkElement.WidthProperty);// ValueSource afterBindingValueSource = DependencyPropertyHelper.GetValueSource(ButtonTest,FrameworkElement.WidthProperty); BindingOperations.ClearBinding(ButtonTest,WidthProperty); //@Test 3: afterUnBindingValueSource.BaseValueSource = Default; // IsAnnimated = false; // IsCoerced = false; // IsExpression = false; //@Test 3: afterUnBindingLocalValue:DependecyProperty.UnsetValue object afterUnBindingLocalValue = ButtonTest.ReadLocalValue(FrameworkElement.WidthProperty);// ValueSource afterUnBindingValueSource = DependencyPropertyHelper.GetValueSource(ButtonTest,FrameworkElement.WidthProperty); } }
如果,从Test1,2,3中,可以看出,在xaml赋值(等同于后台代码直接赋值)和用Binding来绑定值,只能保存一个值。
1.3 Annimate,BaseValue与Coerce,Validate的区别
动画值要比在BaseValue和Binding获取的值具有更高的优先权(注意,实际上Binding值和BaseValue中的LocalValue是具有相同高的优先级,在具体要采用Binding获取的值还是LocalValue,要看运行时,采用最后一次设置的值,具体可以参考1.2中的例子,很明显的证明这点),当处于动画设置状态的时候,属性的最终值就被设定为动画值;和BaseValue和Binding获取的值一样,动画值也要通过Coerce和Validate的验证,最终获得属性的最终值。
有如下的示例,定义MyButton,继承于Button,并且在MyButton中定义依赖属性MyDefinedProperty,定义Trigger,当鼠标在MyButton上的时候,开始动画,可以看到打印出来的信息,每个值都要经过Coerce和Validate的验证。
public partial class MyButton : Button { public MyButton() { InitializeComponent(); } public static readonly DependencyProperty MyDefinedProperty = DependencyProperty.Register("MyDefinded",typeof(double),typeof(MyButton),new FrameworkPropertyMetadata(default(double),FrameworkPropertyMetadataOptions.None,new PropertyChangedCallback(OnValueChanged),new CoerceValueCallback(CoerceValue)),new ValidateValueCallback(IsValidateValue) ); private static void OnValueChanged(DependencyObject d,DependencyPropertyChangedEventArgs e) { Console.WriteLine("{0} OnValueChanged new value is {1}",d.GetType().ToString(),e.NewValue); } private static object CoerceValue(DependencyObject d,object value) { Console.WriteLine("{0} CoerceValue value is {1}",value); return value; } private static bool IsValidateValue(object value) { Console.WriteLine("MyButton ValidateValue value is {1}",value); return true; } public double MyDefinded { get { return (double)GetValue(MyDefinedProperty); } set { SetValue(MyDefinedProperty,value); } } }
同样,在xaml中定义此类型的Button,并且,定义Trigger,在鼠标移动到按钮上的时候,设置动画,如下代码
<Window x:Class="LocalBaseBindingExpress.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" xmlns:loc="clr-namespace:LocalBaseBindingExpress" Loaded="Window_Loaded"> <Window.Resources> <Style TargetType="loc:MyButton"> <Setter Property="Width" Value="100"/> <Setter Property="Height" Value="30"/> <Style.Triggers> <EventTrigger RoutedEvent="Button.MouseEnter" > <BeginStoryboard> <Storyboard> <DoubleAnimation Storyboard.TargetProperty="(loc:MyButton.MyDefinded)" To="1000" Duration="0:1:5"/> </Storyboard> </BeginStoryboard> </EventTrigger> </Style.Triggers> </Style> <loc:WidthConvertr x:Key="WidthConverter"/> </Window.Resources> <Grid> <loc:MyButton x:Name="ButtonTest" FontSize="13" Background="Blue" Click="ButtonTest_Click" >ButtonSubSub </loc:MyButton> </Grid> </Window>
当我们把鼠标放在按钮上的时候,在调试窗口,可以打印出如下的信息
MyButton ValidateValue value is 65.3535369230769
LocalBaseBindingExpress.MyButton CoerceValue value is 65.3535369230769
LocalBaseBindingExpress.MyButton OnValueChanged new value is 65.3535369230769
MyButton ValidateValue value is 65.3571092307692
LocalBaseBindingExpress.MyButton CoerceValue value is 65.3571092307692
LocalBaseBindingExpress.MyButton OnValueChanged new value is 65.3571092307692
MyButton ValidateValue value is 65.8233815384615
LocalBaseBindingExpress.MyButton CoerceValue value is 65.8233815384615
LocalBaseBindingExpress.MyButton OnValueChanged new value is 65.8233815384615
MyButton ValidateValue value is 66.3182046153846
LocalBaseBindingExpress.MyButton CoerceValue value is 66.3182046153846
LocalBaseBindingExpress.MyButton OnValueChanged new value is 66.3182046153846
... ...
从上边可以看出,依赖属性的值当前是动画形成的值,但是这些值和直接从BaseValue中或者Binding中获取的值一样,都要通过Validate检查和Coerce的检查,然后获得依赖属性的最终值。
1.4 经过试验后,自己理解的依赖属性最终值获取的过程
从上边的几个实验来看,依赖属性值的最终的确认,可以用如下的图来描述,下图来自http://www.cnblogs.com/KnightsWarrior/archive/2010/08/27/1809739.html
但是个人认为,可能下图更符合我的思维方式,其中ValueSource的一系列标识量都是为了在程序中获取Value的时候,速度更加快而设计的。依赖属性的最终值,确定要经过以下三步:
1. 确定BaseValue或者Binding值,暂且称呼这个阶段是确定值来源周期;
2. 对确定来源后的值进行验证,验证有两步Coerce和Validate验证;
3. 最终值确定后的后期处理,相当于一个观察值模式,对所有监听者通过OnValueChanged进行通知。