(原创)2. WPF中的依赖属性之二
1 依赖属性1.1 官方依赖属性最终值选取过程WPF属性系统对依赖属性操作的基本步骤如下:
第一,确定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> 但,在Loaded事件处理函数中,有如下代码进行测试 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> 当我们把鼠标放在按钮上的时候,在调试窗口,可以打印出如下的信息
... ... 从上边可以看出,依赖属性的值当前是动画形成的值,但是这些值和直接从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进行通知。
(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |