首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> 關於.NET

WPF 依賴屬性與附加屬性深度解析:從註冊機制到觸發器、值繼承與附加行為模式

     編輯:關於.NET

依賴屬性:WPF 屬性系統的基石

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 TriggerDependencyPropertyUI 狀態(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 TriggerRoutedEvent啟動 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 等來源優先序。
  • 避免在 PropertyChangedCallback 執行耗時操作:屬性變更頻繁(如滾動、動畫),回調應極輕量;重邏輯改用 Dispatcher.BeginInvoke 或 Async 模式。
  • 附加屬性命名衝突:不同類別註冊同名附加屬性會衝突;慣例加上擁有者類別前綴(如 Grid.Row、Canvas.Left)。
  • 唯讀依賴屬性:DependencyPropertyKey 控制寫入權限,對外僅暴露 Getter,內部 SetValue(key, value)(如 UIElement.IsMouseOver)。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved