在定義任何類型的屬性時,都需要面對錯誤設置屬性的可能性。對於傳統的.NET屬性,可嘗試在屬性設置器中捕獲這類問題。但對於依賴項屬性而言,這種方法不合適,因為可能通過WPF屬性系統使用SetValue()方法直接設置屬性。
作為代替,WPF提供了兩種方法來阻止非法值:
- ValidateValueCallback:該回調函數可接受或拒絕新值。通常,該回調函數用於捕獲違反屬性約束的明顯錯誤。可作為DependencyProperty.Register()方法的一個參數提供該回調函數。
- CoerceValueCallback:該回調函數可將新值修改為更能被接受的值。該回調函數通常用於處理為相同對象設置的依賴項屬性值相互沖突的問題。這些值本身可能是合法的,但當同時應用時它們是不相容的。為了使用這個回調函數,當創建FrameworkPropertyMetadata對象時(然后該對象將被傳遞到DependencyProperty.Register()方法),作為構造函數的一個參數提供該回調函數。
下面是當應用程序試圖設置依賴項屬性時,所有這些內容的作用過程:
(1)首先,CoerceValueCallback方法有機會修改提供的值(通常,使提供的值和其他屬性相容),或者返回DependencyProperty.UnsetValue,這會完全拒絕修改。
(2)接下來激活ValidateValueCallback方法。該方法返回true以接受一個值作為合法值,或者返回false拒絕值。與CoerceValueCallback方法不同,ValidateValueCallback方法不能訪問設置屬性的實際對象,這意味着你不能檢查其他屬性值。
(3)最后,如果前兩個階段都獲得成功,就會觸發PropertyChangedCallback方法。此時,如果希望為其他類提供通知,可以引發更改事件。
一、驗證回調
正如前面所看到的,DependencyProperty.Register()方法接受可選的驗證回調函數:
static FrameworkElement(){ FrameworkPropertyMetadata metadata=new FrameworkPropertyMetadata(new Thickness(),FrameworkPropertyMetadataOptions.AffectsMeasure); MarginProperty=DependencyProperty.Register("Margin",typeof(Thickness),typeof(FrameworkElement),metadata,new ValidateValueCallback(FrameworkElement.IsMarginValid)); .... }
可使用這個回調函數驗證,驗證通常應被添加到屬性過程的設置部分。提供的回調函數必須指向一個接受對象參數並返回Boolean值得方法。返回true以接受對象是合法的,返回false拒絕對象。
對FrameworkElement.Margin屬性的驗證十分枯燥乏味,因為它依賴於內部的Thickness.IsValid()方法。該方法確保當前使用的Thickness對象(表示邊距)是合法的。例如,可能構造了一個完全可以接受的Thickness對象(卻不適於設置邊距)。一個例子是Thickness對象使用了負值。如果提供的Thickness對象對於邊距時是不合法的。IsMarginValid方法將返回false:
public static bool IsMarginValid(object value) { Thickness thickness1=(Thickness)value; return thickness1.IsValid(true,false,true,false); }
對於驗證回調函數有一個限制:它們必須是靜態方法而且無權訪問正在被驗證的對象。所有能夠獲得的信息只有剛剛應用的數值。盡管這樣更便於重用屬性,但可能無法創建考慮其他屬性的驗證例程。典型的例子是具有Maximum和Minimum屬性的元素。顯然,為Maximum屬性設置的值不能小於為Minimum屬性設置的值。但是,不能使用驗證回調函數來實施這一邏輯,因為一次之恩你訪問一個屬性。
二、強制回調
通過FrameworkPropertyMetadata對象使用CoerceValueCallback回調函數。下面是示例:
FrameworkPropertyMetadata metadata=new FrameworkPropertyMetadata(new Thickness(),FrameworkPropertyMetadataOptions.AffectsMeasure); metadata.CoerceValueCallback=new CoerceValueCallback(CoerceMaximum); DependencyProperty.Register("Maxium",typeof(double),typeof(RangeBase),metadata);
可以通過CoerceValueCallback回調函數處理相互關聯的屬性。例如,ScrollBar控件提供了Maximum、Minimum和Value屬性,這些屬性都繼承自RangeBase類。保持對這些屬性進行調整的一種方法是使用屬性強制。
例如,當設置Maximum屬性時,必須使用強制以確保不能小於Minimum屬性的值:
private static object CoerceMaximum(DependencyObject d,object value) { RangeBase base1=(RangeBase)d; if((double)value)<base1.Minimum) { return base1.Minimum; } return value; }
換句話說,如果應用於Maximum屬性的值小於Minimum屬性的值,就用Minimum屬性的值設置Maximum屬性。注意,CoerceValueCallback傳遞兩個參數——准備使用的數值和該數值將要應用到得對象。
當設置Value屬性時,會發生類似的強制過程。對Value屬性進行強制,確保不會超出由Minimum和Maximum屬性定義的范圍,使用下面的代碼:
internal static object ConstrainToRange(DependencyObject d,object value) { double newValue=(double)value; RangeBase base1=(RangeBase)d; double minimum=base1.Minimum; if(newValue<minimum) { return minimum; } double maximum=bas1.Maximum; if(newValue>maximum) { return maximum; } return newValue; }
Minimum屬性根本不使用值強制。相反,一旦值發生變化,就觸發PropertyChangedCallback,然后通過手動觸發Maximum和Value屬性的強制過程,使它們適應Minimum屬性值得變化:
private static void OnMinimumChanged(DependencyObject d,DependencyPropertyChangedEventArgs e) { RangeBase base1=(RangeBase)d; ... base1.CoerceValue(RangeBase.MaximumProperty); base1.CoerceValue(RangeBase.ValueProperty); }
類似地,一旦設置或強制Maximum屬性的值,那么也會手動強制Value屬性以適應Maximum屬性值得變化:
private static void OnMaximumChanged(DependencyObject d,DependencyPropertyChangedEventArgs e) { RangeBase base1=(RangeBase)d; ... base1.CoerceValue(RangeBase.ValueProperty); base1.OnMaximumChanged((double)e.OldValue,(double)e.NewValue); }
如果設置的值相互沖突,最終結果是Minimum屬性具有優先權,其次是Maximum屬性(並且可能會被Minimum屬性強制),最后是Value屬性(並且可能會被Maximum和Minimum屬性強制)。