DependencyProperty 並非取代 CLR 屬性,而是透過 DependencyObject 提供的 GetValue/SetValue 方法,將屬性值儲存在高效的稀疏儲存體中,並內建 變更通知、值繼承、樣式/模板/動畫/繫結 等多重機制的整合點。WPF 內建控件超過 90% 屬性皆為依賴屬性。
public class MyButton : Button {
// 1. 靜態唯讀欄位持有識別器
public static readonly DependencyProperty IsActiveProperty =
DependencyProperty.Register(
name: "IsActive",
propertyType: typeof(bool),
ownerType: typeof(MyButton),
typeMetadata: new FrameworkPropertyMetadata(
defaultValue: false,
flags: FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
propertyChangedCallback: OnIsActiveChanged,
coerceValueCallback: CoerceIsActive),
validateValueCallback: v => (bool)v); // 僅驗證型別
// 2. CLR 包裝器(供 XAML/IntelliSense 使用,內部僅呼叫 GetValue/SetValue)
public bool IsActive {
get => (bool)GetValue(IsActiveProperty);
set => SetValue(IsActiveProperty, value);
}
// 3. 變更回調(靜態,傳入 DependencyObject 避免捕獲 this)
static void OnIsActiveChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
if (d is MyButton btn) { /* 視覺狀態切換、命令執行等 */ }
}
// 4. 強制值回調(在 SetValue 後、變更回調前,可修正值)
static object CoerceIsActive(DependencyObject d, object baseValue) {
return (bool)baseValue; // 例如:若 IsEnabled=False 則強制回 false
}
}
FrameworkPropertyMetadata 旗標精選:
AffectsMeasure/Arrange/Render/ParentMeasure/ParentArrange:標記佈局/渲染影響範圍。Inherits:啟用屬性值繼承,子元素未設值時自動向上邏輯樹尋找(如 FontSize、Foreground、DataContext)。BindsTwoWayByDefault:繫結預設 Mode=TwoWay(如 TextBox.Text、Selector.SelectedItem)。Journal:支援瀏覽器前進/後退記錄。附加屬性本質是 DependencyProperty.RegisterAttached 註冊的依賴屬性,慣例提供靜態 GetXxx/SetXxx 方法。父元素設定值,子元素透過邏輯樹繼承或直接讀取:
<StackPanel TextElement.FontSize="16" TextElement.Foreground="Navy">
<TextBlock>繼承字體大小與顏色</TextBlock>
<Button Content="按鈕也繼承"/>
<TextBlock FontSize="10">局部覆寫為 10pt</TextBlock>
</StackPanel>
佈局系統大量使用:Grid.Row、Grid.Column、Canvas.Left、DockPanel.Dock 皆為附加屬性,使子元素無需繼承特定基類即可參與佈局協商。
| 類型 | 監聽目標 | 適用場景 | 關鍵語法 |
|---|---|---|---|
| Property Trigger | DependencyProperty | UI 狀態(IsMouseOver、IsFocused、Validation.HasError) | <Trigger Property="IsMouseOver" Value="True"><Setter .../></Trigger> |
| Data Trigger | 任意 CLR Property (via Binding) | ViewModel/POCO 狀態(Role=="Admin"、Status==Completed) | <DataTrigger Binding="{Binding Status}" Value="Completed">... |
| Event Trigger | RoutedEvent | 啟動 Storyboard/動畫/Command | <EventTrigger RoutedEvent="Button.Click"><BeginStoryboard>...</EventTrigger> |
MultiTrigger / MultiDataTrigger 以 Conditions 集合實現邏輯 AND,所有條件滿足才套用 Setter。
啟用 Inherits 旗標的依賴屬性(FontSize、Foreground、DataContext、Language 等)會在邏輯樹向下傳遞。子元素可透過 DependencyObject.GetValue 取得繼承值,或直接在 XAML 省略該屬性。注意:視覺樹不參與繼承,僅邏輯樹;FrameworkContentElement(如 Run、Paragraph)同樣參與繼承。
WPF 原生不支援在 Trigger 中直接呼叫方法,但可透過附加依賴屬性 + PropertyChangedCallback 實現「行為封裝」:
public static class ClickBehavior {
public static readonly DependencyProperty CommandProperty =
DependencyProperty.RegisterAttached("Command", typeof(ICommand), typeof(ClickBehavior),
new PropertyMetadata(null, OnCommandChanged));
public static ICommand GetCommand(DependencyObject d) => (ICommand)d.GetValue(CommandProperty);
public static void SetCommand(DependencyObject d, ICommand v) => d.SetValue(CommandProperty, v);
static void OnCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
if (d is ButtonBase btn) {
if (e.OldValue != null) btn.Click -= OnClick;
if (e.NewValue != null) btn.Click += OnClick;
}
}
static void OnClick(object sender, RoutedEventArgs e) {
var cmd = GetCommand((DependencyObject)sender);
if (cmd?.CanExecute(null) == true) cmd.Execute(null);
}
}
XAML 使用:
<Button Content="送出" local:ClickBehavior.Command="{Binding SubmitCommand}"/>
此模式為 Microsoft.Xaml.Behaviors (原 Blend SDK) 的核心實現基礎,實現 MVVM 下事件到命令的純 XAML 連接。
DependencyPropertyHelper.GetValueSource(element, prop) 可查看值來自 Local/Style/Inheritance/Template/Animation/Coercion/Default 等來源優先序。Dispatcher.BeginInvoke 或 Async 模式。Grid.Row、Canvas.Left)。DependencyPropertyKey 控制寫入權限,對外僅暴露 Getter,內部 SetValue(key, value)(如 UIElement.IsMouseOver)。