WPF的依赖属性
使用依赖属性:
public class SimpleDO : DependencyObject { public static readonly DependencyProperty IsActiveProperty = DependencyProperty.Register("IsActive", typeof(bool), typeof(SimpleDO), new PropertyMetadata((bool)false)); public bool IsActive { get { return (bool)GetValue(IsActiveProperty); } set { SetValue(IsActiveProperty, value); } } }
可以这样验证:
1 SimpleDO sDo = new SimpleDO(); 2 sDo.IsActive = true;
处理DP的规则:
按照优先级高低从左往右排列:
Validate <- Coerce <- Apply Animation <- Evaluate(If an Expression) <- Determine Base Value
更具体一点是,当我们访问一个DP,它会按照以下顺序给我们相应的值(从高到低)

一个简单的例子,知道什么时候执行Validate 和 CoerceValue:
public class SimpleDO : DependencyObject { public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(double), typeof(SimpleDO), new FrameworkPropertyMetadata((double)0.0, FrameworkPropertyMetadataOptions.None, new PropertyChangedCallback(OnValueChanged), new CoerceValueCallback(CoerceValue)), new ValidateValueCallback(IsValidateValue)); public double Value { get { return (double)GetValue(ValueProperty); } set { SetValue(ValueProperty, value); } } private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { Console.WriteLine("ValueChanged new value is {0}", e.NewValue); } private static object CoerceValue(DependencyObject d, object value) { Console.WriteLine("CoerceValue value is {0}", value); value = 4.0; return value; } private static bool IsValidateValue(object value) { Console.WriteLine("ValidateValue value is {0}", value); return true; } }
按照如下方式调用:
1 SimpleDO sDo = new SimpleDO();
2 sDo.Value = 1;
我们从输出结果可以看出,先执行 Validate 然后是 CoerceValue, 最后是ValueChanged, 在前面的每一步都可以改变Value的值。
下面一段代码可以演示一下动画对DP值得影响:
public class SimpleDOUI : UIElement { public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(double), typeof(SimpleDOUI), new FrameworkPropertyMetadata((double)30.0, FrameworkPropertyMetadataOptions.None, new PropertyChangedCallback(OnValueChanged), new CoerceValueCallback(CoerceValue)), new ValidateValueCallback(IsValidateValue)); public double Value { get { return (double)GetValue(ValueProperty); } set { SetValue(ValueProperty, value); } } private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { Console.WriteLine("ValueChanged new value is {0}", e.NewValue); } private static object CoerceValue(DependencyObject d, object value) { Console.WriteLine("CoerceValue value is {0}", value); return value; } private static bool IsValidateValue(object value) { Console.WriteLine("ValidateValue value is {0}", value); return true; } }
按照以下方式执行:
SimpleDOUI sDo = new SimpleDOUI();
DoubleAnimation animation = new DoubleAnimation(1, 20, new Duration(TimeSpan.FromMilliseconds(5000)), FillBehavior.Stop); sDo.BeginAnimation(SimpleDOUI.ValueProperty, animation);
结果是Vlaue的值从 1 一直变化到 20,最后还将变未默认值 30.0, 除非将FillBehavior.HoldEnd, Value将变为 20. 如果显示的将sDo.Value = 40 (这个是LocalValue), 你得到的值仍然会是20, 应为animation的优先级要高。
在DP的定义中,我们会用到PropertyMetadata, 可以在子类中重写它,WPF中有FrameworkPropertyMetadata, UIPropertyMetadata以及PropertyMetadata,他们的继承关系是F -> U -> P.
最复杂的F的构造函数是:
public FrameworkPropertyMetadata( object defaultValue, FrameworkPropertyMetadataOptions flags, PropertyChangedCallback propertyChangedCallback, CoerceValueCallback coerceValueCallback, bool isAnimationProhibited, UpdateSourceTrigger defaultUpdateSourceTrigger);
三个Callback我们已经用代码演示过了, 我们来看看FrameworkPropertyMetadataOptions (WPF4.5):
public enum FrameworkPropertyMetadataOptions { // Summary: // No options are specified; the dependency property uses the default behavior // of the Windows Presentation Foundation (WPF) property system. None = 0, // // Summary: // The measure pass of layout compositions is affected by value changes to this // dependency property. AffectsMeasure = 1, // // Summary: // The arrange pass of layout composition is affected by value changes to this // dependency property. AffectsArrange = 2, // // Summary: // The measure pass on the parent element is affected by value changes to this // dependency property. AffectsParentMeasure = 4, // // Summary: // The arrange pass on the parent element is affected by value changes to this // dependency property. AffectsParentArrange = 8, // // Summary: // Some aspect of rendering or layout composition (other than measure or arrange) // is affected by value changes to this dependency property. AffectsRender = 16, // // Summary: // The values of this dependency property are inherited by child elements. Inherits = 32, // // Summary: // The values of this dependency property span separated trees for purposes // of property value inheritance. OverridesInheritanceBehavior = 64, // // Summary: // Data binding to this dependency property is not allowed. NotDataBindable = 128, // // Summary: // The System.Windows.Data.BindingMode for data bindings on this dependency // property defaults to System.Windows.Data.BindingMode.TwoWay. BindsTwoWayByDefault = 256, // // Summary: // The values of this dependency property should be saved or restored by journaling // processes, or when navigating by Uniform resource identifiers (URIs). Journal = 1024, // // Summary: // The subproperties on the value of this dependency property do not affect // any aspect of rendering. SubPropertiesDoNotAffectRender = 2048,
大致分为两类,有Affect的,表示这个属性变化后,要重新测量,绘制等。另一类,拿属性继承作为例子。WPF的依赖属性的可继承性是依附于对象的。 (疑问? 这个DP需要在每个对象里都定义一边吗?)
我们可以调用DependencyPropertyHelper的GetValueSource方法来获得当前依赖属性的信息:
ValueSource source = DependencyPropertyHelper.GetValueSource(sDo, SimpleDO.ValueProperty);
其中ValueSource:
public struct ValueSource { public BaseValueSource BaseValueSource { get; } public bool IsAnimated { get; } public bool IsCoerced { get; } public bool IsExpression { get; } }
其中的IsAnimated,IsCoerced,IsExpression用来指示当前依赖属性的状态,BaseValueSource指示当前BaseValue的优先级。它有
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:
DependencyObject提供了ReadLocalValue函数来读取当前的LocalValue
public object ReadLocalValue(DependencyProperty dp);
那么LocalValue和EffctiveValue的区别在哪呢?DependencyObject提供了GetValue方法来取得属性值,这个值就是EffctiveValue, 有一个例子(我已验证过)
1: Slider slider = new Slider(); 2: slider.Minimum = 0; 3: slider.Maximum = 10; 4: slider.Value = 3; 5: 6: slider.Minimum = 4; //After set, Value = 4; Value's Local Value = 3; 7: slider.Minimum = 1; //After set, Value = Value's Local Value = 3; 8: slider.Minimum = 13; //After set, Value = Maximum = 13;
第6行,当设置了Minimum=4后,Value的Coerce会被调用,在Coerce中,因为Value值(3)小于Minmum(4),Value值被强制为4。但Value的Local值仍然被保留,使用ReadLocalValue函数可以查看到Value的LocalValue仍然为3。第7行,Minimum的值为1后,在Value的Coerce中,因为Value的LocalValue(3)大于1,所以最终取得的Value和LocalValue都为3。
最后为Coerce 举个例子:
public class MyClockControl : FrameworkElement { // Dependency Property public static readonly DependencyProperty CurrentTimeProperty = DependencyProperty.Register( "CurrentTime", typeof(DateTime), typeof(MyClockControl), new FrameworkPropertyMetadata( DateTime.Now, OnCurrentTimePropertyChanged, OnCoerceCurrentTimeProperty ), OnValidateCurrentTimeProperty )); // .NET Property wrapper public DateTime CurrentTime { get { return (DateTime)GetValue(CurrentTimeProperty); } set { SetValue(CurrentTimeProperty, value); } } // Value Changed Callback private static void OnCurrentTimePropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e) { MyClockControl control = source as MyClockControl; DateTime time = (DateTime)e.NewValue; // Put some update logic here... } // Coerce Value Callback private static object OnCoerceTimeProperty( DependencyObject sender, object data ) { if ((DateTime)data > DateTime.Now ) { data = DateTime.Now; } return data; } // Validation Callback private static bool OnValidateTimeProperty(object data) { return data is DateTime; } }
最后补充一下依赖属性的继承,刚刚提到FontSize是怎么继承的,具体的例子为 Window里有个Button,当我在window里设置FontSize = 15时,它对Button也是有效的:
首先FontSize是定义在Control中的,而且Window和Button的基类里都有Control, 这就是说他们都有FontSize的定义。所以DP的继承的前提是都有同样的DP定义。
浙公网安备 33010602011771号