WPF依賴屬性學習
概述
WPF 依賴屬性(Dependency Property)是 WPF 框架的核心基礎設施之一,它擴展了傳統 .NET 屬性的能力,為 WPF 提供數據綁定、動畫、樣式、繼承值、屬性值變更通知等高級功能。
為什么需要設計依賴屬性?
因為依賴屬性做到了CLR屬性沒做到的一些事情。
列舉幾個場景:
1、數據驅動 UI 的動態性需要“可計算的值
在 WPF 里,綁定的值、樣式 Setter 的值、觸發器的值、動畫幀的值,都是事后才知道的,甚至可以在運行時不斷切換來源。
CLR 屬性:值寫死在一個私有字段里,誰最后 set 就留誰。
依賴屬性:屬性系統先查看“當前這一幀到底是誰最有發言權”,再給出最終值——也就是“值是從外部來的,我只是按優先級算一算”的依賴計算。
2、大規模對象樹的內存壓力要求“默認值共享
WPF 的控件樹隨隨便便成千上萬實例,如果每個 Button 都把 FontSize = 11 存一份 double,內存就爆炸了。
依賴屬性把“默認值”壓縮到一個靜態全局哈希表里,沒顯式設置的實例,直接查表用同一份值。
3、樣式 / 動畫 / 綁定 / 繼承 / 觸發器 / 資源多路輸入需要統一的“優先級規則
同一個 Background,可以是:本地值(紅),主題樣式(藍),動畫(綠),觸發器(黃)……
傳統屬性里誰最后 set 誰贏,根本無法表達這種“多源頭分時復用”的復雜策略。
依賴屬性為此內置了一套顯式的優先級表(動畫>本地值>觸發器>樣式…),系統每次重新評估就行,無需控件開發者自己寫狀態機。
4、跨父子樹的“屬性值繼承
典型例子:FontSize 設到 Window 上,所有子孫 TextBlock 直接復用該值,但中途隨時可以用樣式或本地值覆蓋。
傳統字段存儲實現:父級改一次就要遞歸遍歷整棵樹;
依賴屬性:子元素在取值時惰性向上詢問,邏輯/性能都優雅。
學習依賴屬性
在創建自定義的時候,創建一個依賴屬性的示例如下所示:
public int Value
{
get => (int)GetValue(ValueProperty);
set => SetValue(ValueProperty, value);
}
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(
nameof(Value), typeof(int), typeof(RatingControl),
new FrameworkPropertyMetadata(0, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnValueChanged, CoerceValue));
首先來看看命名,一個CLR屬性是Value,依賴屬性是ValueProperty,這是一種命名約定,可以很容易將這兩個東西關聯起來。
依賴屬性都是通過DependencyProperty.Register方法注冊:
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register(
nameof(Value), // 屬性名 Value
typeof(int), // 屬性類型
typeof(RatingControl), // 所屬類型
new FrameworkPropertyMetadata(
0, // 默認值
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, // 默認為雙向綁定
OnValueChanged, // 值發生變更時的回調
CoerceValue) // 強制值回調
);
CoerceValue是強制回調:
private static object CoerceValue(DependencyObject d, object baseValue)
{
var ctl = (RatingControl)d;
int v = Math.Max(0, (int)baseValue); // 下限
v = Math.Min(v, ctl.Max); // 上限
return v;
}
OnValueChanged是變更回調:
private static void OnValueChanged(
DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var ctl = (RatingControl)d;
ctl.UpdateVisualStates();
}
作用:值真正改變后通知控件更新 UI。
e 中包含舊值 e.OldValue 與新值 e.NewValue,可進一步比較差異。
生命周期小結(一個賦值的全過程)
代碼 / 綁定 / 動畫嘗試改變 Value。
WPF 調用 CoerceValue 讓控件有機會矯正值。
如果矯正后的值與當前存儲值相同,流程結束;否則進入下一步。
觸發 OnValueChanged → 更新UI。
因為是 BindsTwoWayByDefault,若存在綁定的源(ViewModel),其對應屬性也會被同步。
現在大概了解了依賴屬性的設計,你可能也聽說過“附加屬性”與“繼承屬性”。
其實官方并沒有“繼承屬性”這個稱謂,繼承屬性只是將依賴屬性設置成可繼承罷了。
要想更好地理解依賴屬性的概念,一個很好的方式就是去看WPF的源碼,看看在源碼中是如何使用的,現在就讓我們一起去源碼中找找看吧!!
先來看看普通的依賴屬性定義:


目前我們接觸到了DependencyProperty與DependencyPropertyKey。
DependencyPropertyKey表示只讀依賴屬性。
這里官方源碼將按鈕是否按下這個屬性設置為了只讀依賴屬性,為什么官方是這樣做的呢?
想象一下一個按鈕的 IsPressed 屬性。這個屬性應該是 true 還是 false,不應該由應用程序的邏輯直接決定(比如,你不應該寫 myButton.IsPressed = true; 來“按下”一個按鈕)。它的狀態應該完全由用戶的交互行為(鼠標按下、觸摸、鍵盤空格鍵等)來驅動。
如果你把它做成一個普通的可以隨意讀寫的屬性:
public bool IsPressed { get; set; }
那么任何代碼都可以修改它,這會破壞按鈕的內在邏輯和行為一致性。
如果你把它做成一個普通的只讀屬性:
private bool _isPressed;
public bool IsPressed { get { return _isPressed; } }
雖然外部代碼不能修改了,但這樣做有幾個缺點:
不支持 WPF 高級功能:它不再是一個依賴屬性,因此無法享受數據綁定、樣式、動畫、屬性值繼承等 WPF 的核心特性。比如,你無法在 XAML 中寫一個 Trigger 來在 IsPressed 為 true 時改變按鈕的背景色。
缺少變更通知:如果 _isPressed 的值改變了,WPF 的其他部分(比如 UI 渲染系統)不會自動知道。你需要手動實現 INotifyPropertyChanged 接口,這額外增加了復雜性。
為了解決上述問題,WPF 引入了“只讀依賴屬性” (Read-Only Dependency Property) 。這種屬性擁有兩全其美的優勢:
對外是只讀的:保護了屬性的完整性,防止外部代碼隨意篡改。
內部是可讀寫的:屬性的“所有者”可以在特定邏輯下修改其值。
擁有依賴屬性的全部特性:支持數據綁定、樣式、動畫、觸發器等。
再來看看附加依賴屬性:

Grid.Row是一個很經典的附加依賴屬性。
注冊附加依賴屬性使用的是DependencyProperty.RegisterAttached方法。
附加屬性必須提供靜態的Get和 Set 方法:

在WPF中一個很經典的可繼承依賴屬性的例子就是FontSize,讓我們來看看它的定義:

使用了FrameworkPropertyMetadataOptions.Inherits。
這個枚舉類有以下幾個選項:
| 名稱 | 說明 |
|---|---|
| None | 無標志。 |
| AffectsMeasure | 此屬性影響測量(Measure)過程。當此屬性值改變時,元素需要重新計算其所需大小。 |
| AffectsArrange | 此屬性影響布局(Arrange)過程。當此屬性值改變時,元素需要重新定位并確定其最終大小。 |
| AffectsParentMeasure | 此屬性影響父級的測量過程。當此屬性值改變時,其父元素需要重新進行測量。 |
| AffectsParentArrange | 此屬性影響父級的布局過程。當此屬性值改變時,其父元素需要重新進行布局。 |
| AffectsRender | 此屬性影響渲染。當此屬性值改變時,元素可能需要部分或完全重繪。 |
| Inherits | 此屬性的值可以被子元素繼承。 |
| OverridesInheritanceBehavior | 此屬性會導致繼承和資源查找過程,忽略在查找路徑上任何元素 (FE) 設置的 InheritanceBehavior 值。 |
| NotDataBindable | 此屬性不支持數據綁定。 |
| BindsTwoWayByDefault | 對此屬性的數據綁定默認為雙向(Two-Way)模式。 |
| Journal | 在通過 URI 進行日志記錄/導航時,此屬性的值應該被保存和恢復。 |
| SubPropertiesDoNotAffectRender | 此屬性的子屬性不會影響渲染。例如,若屬性 X 有子屬性 Y,則修改 X.Y 不會觸發渲染更新。 |
現在只是差不多了解了WPF中的依賴屬性的一些概念與使用,要想真正明白依賴屬性的設計與實現,還得多研究研究源碼。

浙公網安備 33010602011771號