依賴項屬性

定義依賴項屬性

注意:只能為依賴物件(繼承自DependencyObject的類)新增依賴項屬性。WPF中的元素基本上都繼承自DependencyObject類。

  • 靜態欄位
  • 名稱約定(屬性末尾加上Property)
  • Readonly(只能在靜態建構函式中進行設定)
public static readonly DependencyProperty ScalingRatioProperty =
DependencyProperty.Register("ScalingRatio",
typeof(double),
typeof(FundusCanvas),
new FrameworkPropertyMetadata(1.0));

註冊依賴項屬性

為了確保DependencyProperty物件不被直接例項化,使能使用靜態的DependencyProperty.Register方法建立依賴項屬性例項,而且建立後不能被改變,所以使用只讀屬性。建立過程中需要提供的幾個要素:

  • 屬性名(ScalingRatio)
  • 屬性使用的資料型別(double)
  • 擁有該屬性的型別(FundusCanvas)
  • 一個具有附加屬性設定的FrameworkPropertyMetadata物件(可選)
  • 一個用於驗證屬性的回撥函式(可選)

使用FrameworkPropertyMetadata物件和屬性驗證回撥可以更加豐富依賴項屬性,建立依賴項屬性的附加功能。FrameworkPropertyMetadata類的所有屬性:

名稱 說明
AffectsArrange
AffectsMeasure
AffectsParentArrange
AffectsParentMeasure
如果為true,依賴項屬性會影響在佈局操作的測量過程和排列過程中如何放置相鄰的元素或者父元素。如果屬性值發生變化,那麼佈局容器需要重新執行測量步驟以確定新的佈局
AffectsRender 如果為true,依賴項屬性會對元素的繪製方式造成一定的影響,要求重新繪製元素
BindsTwoWayByDefault 如果為true,預設情況下,依賴項屬性將使用雙向資料繫結而不是單向資料繫結。不過,當建立資料繫結時,可以明確指定所需的繫結行為
Inherits 如果為true,就通過元素樹傳播該依賴項屬性值,並且可以被巢狀的元素繼承。例如:Font是可繼承的依賴項屬性,如果在更高層次的元素中為Font屬性設定了值,那麼該屬性值就會被巢狀的元素繼承,除非手動設定來覆蓋繼承來的值
IsAnimationProhibited 如果為true,就不能將依賴項屬性用於動畫
IaNoDataBindable 如果為true,就不能使用繫結表示式設定依賴項屬性
Journal 如果為true,基於頁面的應用程式中,依賴項屬性將被儲存到日誌中(瀏覽過的頁面歷史記錄)
SubPropertiesDoNotAffectRender 如果為true,並且物件的某個子屬性(屬性的屬性)發生了變化,WPF將不會重新渲染該物件
DefaultUpdateSourceTrigger 當該屬性用於繫結表示式時,該屬性用於為Binding.UpdateSourceTrigger屬性的預設值。UpdateSourceTrigger屬性決定了資料繫結值在何時應用自身的變化。當建立繫結時,可手動設定UpdateSourceTrigger屬性
DefaultValue 該屬性用於為依賴項屬性設定預設值
CoerceValueCallback 該屬性提供一個回撥函式,用於驗證依賴項屬性之前嘗試“糾正”屬性值
PropertyChangedCallback 該屬性提供一個回撥函式,當依賴項屬性的值發生變化時呼叫該函式

新增屬性包裝器

public FundusEditMode EditMode
{
get => (FundusEditMode)GetValue(EditModeProperty);
set => SetValue(EditModeProperty, value);
}

當建立屬性封裝器時,應當值包含對SetValue和GetValue方法的呼叫,不應當新增任何驗證屬性的額外程式碼,引發事件的程式碼等。WPF提供了用於進行這些工作的地方-使用依賴項屬性回撥函式。應該通過前面介紹的ValidateValueCallback回撥函式進行驗證操作。

依賴項屬性遵循嚴格的優先規則來確定他們的當前值。即使沒有直接設定依賴項屬性,也可能已經有了值(可能是由資料繫結、樣式、或者動畫提供的,也可能是繼承來的),不過,主要直接設定了屬性值,設定的屬性值就會覆蓋所有其他的影響。

可能希望刪除本地值設定,並像從來沒有設定過那樣確定屬性值。顯然不能夠通過設定新值來實現,反而需要使用方法ClearValue()。

使用依賴項屬性

WPF的許多功能都需要使用依賴項屬性,這些功能都是通過每個依賴項屬性都支援的兩個關鍵行為進行工作的———更改通知和動態值識別。

  • 更改通知

當屬性值發生變化時,依賴項屬性不會自動引發事件以通知屬性值發生了變化。相反,他們會觸發受保護的名為OnPropertyChangedCallback()的方法。該方法通過兩個WPF服務(資料繫結和觸發器)傳遞資訊,並呼叫PropertyChangedCallback回撥函式。也就是說,當屬性變化時,如果希望進行相應,有兩種選擇--可使用屬性建立繫結,也可以編寫能夠自動改變其他屬性或者開始動畫的觸發器。但是依賴項屬性沒有提供一種通用的方法觸發一些程式碼,進而對屬性的變化進行相應。

  • 動態值識別

本質上,依賴項屬性依賴於多個屬性提供者,每個提供者都有各自的優先順序。當從屬性檢索值事,WPF屬性系統會通過一系列步驟獲取最終值。首先通過考慮以下因素(優先順序從低到高)來決定基本值。

  1. 預設值(有FrameworkPropertyMetadata物件設定的值)
  2. 繼承而來的值(假設設定了FrameworkPropertyMetadata.Inherits標誌,併為包含層次中的某個元素提供了值)
  3. 來自主題樣式的值
  4. 來自專案樣式的值
  5. 本地值(使用程式碼或者XAML直接為物件設定的值)

可通過直接應用一個值來覆蓋整個層次,否則屬性值可通過上面5中可用項來確定。

WPF按照上面的5種情況去確定依賴項屬性的基本值。但是基本值未必就是最後從屬性中檢索到的值。這是因為WPF還需要考慮其他幾個可能改變屬性值的提供者:

  1. 確定基本值(按照上述步驟)
  2. 如果屬性是使用表示式設定的,就對錶達式進行求值(資料繫結或者資源)
  3. 如果屬性是動畫,就應用動畫
  4. 執行CoerceValueCallback回撥函式來修正屬性值

共享依賴項屬性

儘管一些類具有不同的繼承層次,但他們會共享同一依賴項屬性。

public static readonly DependencyProperty FrontContentProperty =
FlipPanel.FrontContentProperty.AddOwner(typeof(FlipPanel2));

可以使用相同的技術建立自己的自定義類(假定繼承的父類沒有提供屬性,否則直接重寫即可),還可以使用過載的AddOwner方法來提供驗證回撥函式以及應用依賴項屬性的新方法的FrameworkPropertyMetadata物件。

附加的依賴項屬性

public static readonly DependencyProperty FrontContent1Property =
DependencyProperty.RegisterAttached("FrontContent", typeof(object), typeof(FlipPanel2), null);

附加屬性是一種依賴項屬性,由WPF屬性管理器管理,不同之處在於附加屬性被應用到的類並非定義附加屬性的那個類。例如Grid.Row屬性是應用在Grid中定義,但是應用於其他控制元件。

屬性驗證

WPF提供了兩種方法類阻止非法值:

  • ValidateValueCallback:該回調函式可接受或者拒絕新值。通常,該回調函式用於捕獲違反屬性約定的明顯錯誤。可作為DependencyProperty.Register方法的一個引數提供該回調函式。
  • CoerceValueCallback:該回調函式可將新值修改為更能夠被接受的值。該回調函式通常用於處理為相同物件設定的依賴項屬性值相互衝突的問題。這些值本身是合法的,但是同時應用時他們是不相容的。當建立FrameworkPropertyMetadata物件時,提供一個引數作為回撥函式。

應用程式試圖設定依賴項屬性時,所有這些內容的作用過程:

  1. 首先,CoerceValueCallback方法有機會修改提供的值(通常,使提供的值和其他屬性相容),或者返回DependencyProperty.UnsetValue,這會拒絕修改
  2. 接下來啟用ValidateValueCallback方法,該方法返回true以接受一個值作為合法值,或者返回false拒絕值。與CoerceValueCallback不同,本方法不能訪問設定屬性的實際物件,這意味著不能檢查其他屬性值。
  3. 最後,如果前兩個階段獲得成功,就會觸發PropertyChangedCallback方法

驗證回撥(ValidateValueCallback)

可使用回撥函式加強驗證,驗證通常應被新增到屬性過程的設定部分。提供的回撥函式必須指向一個接受物件引數並返回bool值的方法,如果返回true來接受物件是合法的,否則返回false拒絕物件。

對於驗證回撥函式有一個限制,他們必須是靜態方法而且無法訪問正在被驗證的物件,所有能夠獲得資訊就是剛剛應用的數值。

public static readonly DependencyProperty CornerRadiusProperty =
    DependencyProperty.Register("CornerRadius", typeof(CornerRadius),
        typeof(FlipPanel), null, new ValidateValueCallback(CornerRadiusValidate)); private static bool CornerRadiusValidate(object value)
{
    CornerRadius cornerRadius = (CornerRadius)value;
    if (cornerRadius.TopLeft == double.NaN ||
        cornerRadius.TopRight == double.NaN ||
        cornerRadius.BottomLeft == double.NaN ||
        cornerRadius.BottomRight == double.NaN)
        return false;
    return true;
}

強制回撥(CoerceValueCallback)

通過設定CoerceValueCallback回撥函式處理相互關聯的屬性。設定本屬性的時候,需要處理好本屬性和其他屬性之間的關係,根據處理結果返回響應的值。

public CornerRadius CornerRadius
{
    get => (CornerRadius)GetValue(CornerRadiusProperty);
    set => SetValue(CornerRadiusProperty, value);
}
public static readonly DependencyProperty CornerRadiusProperty =
    DependencyProperty.Register("CornerRadius", typeof(CornerRadius),
        typeof(FlipPanel), new FrameworkPropertyMetadata(new CornerRadius(5, 5, 5, 5),
            FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
            propertyChangedCallback: CornerRadiusChangedCallback,
            coerceValueCallback: CornerRadiusCoerceValueCallback),
        new ValidateValueCallback(CornerRadiusValidate)); private static bool CornerRadiusValidate(object value)
{
    CornerRadius cornerRadius = (CornerRadius)value;
    if (cornerRadius.TopLeft == double.NaN ||
        cornerRadius.TopRight == double.NaN ||
        cornerRadius.BottomLeft == double.NaN ||
        cornerRadius.BottomRight == double.NaN)
        return false;
    return true;
} private static object CornerRadiusCoerceValueCallback(DependencyObject d, object value)
{
    CornerRadius cornerRadius = (CornerRadius)value;
    if (cornerRadius.TopLeft == double.NaN ||
        cornerRadius.TopRight == double.NaN ||
        cornerRadius.BottomLeft == double.NaN ||
        cornerRadius.BottomRight == double.NaN)
        return new CornerRadius(5, 5, 5, 5);
    return cornerRadius;
} private static void CornerRadiusChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{ }