1. WPF中的依賴屬性
- 依賴屬性是專門基于WPF創建的,在WPF庫實作中,依賴屬性使用普通的C#屬性進行了包裝,使用方法與普通的屬性是相同的,
1.1 依賴屬性提供的屬性功能
- 資源
- 資料系結
- 樣式
- 影片
- 元資料重寫
- 屬性值繼承
- WPF 設計器集成
1.2 依賴屬性優先級串列
運行時值分配給依賴項屬性時,屬性系統使用的明確優先級順序,由高到低為:
- 屬性系統強制,
- 活動影片或具有保留行為的影片,
- 本地值,
- TemplatedParent 模板屬性值,
- 隱式樣式,
- 樣式觸發器,
- 模板觸發器,
- 樣式 setter 值,
- 默認樣式,也稱為 主題樣式,
- 繼承, 子元素的某些依賴屬性從父元素繼承其值, 因此,可能不需要在整個應用程式中設定每個元素的屬性值,
- 依賴項屬性元資料中的默認值 依賴屬性可以在該屬性的屬性系統注冊程序中設定默認值, 繼承依賴屬性的派生類可以重寫依賴屬性元資料 (包括基于每個型別) 的默認值, 對于繼承的屬性,父元素的默認值優先于子元素的默認值, 因此,如果未設定可繼承屬性,則將使用根或父元素的默認值,而不是子元素的默認值,
1.3 附加屬性
附加屬性允許子元素為父元素中定義的屬性指定唯一值, 常見方案是一個子元素,它指定其父元素在 UI 中的呈現方式, 例如, DockPanel.Dock是附加屬性,因為它在 的子元素上 DockPanel設定,而不是本身 DockPanel ,
2. 依賴屬性的使用
2.1 定義依賴屬性
- 定義一個名叫
Name的依賴屬性,根據命名約定,依賴屬性以屬性名稱加Property來命名 - 依賴屬性的所有者必須繼承自
DependencyObject類
public class People : DependencyObject
{
public static readonly DependencyProperty NameProperty;
}
2.2 注冊依賴屬性
- 使用
DependencyProperty.Register()靜態方法對依賴屬性進行注冊
public class People : DependencyObject
{
public static readonly DependencyProperty NameProperty =
DependencyProperty.Register("Name", typeof(string), typeof(People));
}
引數說明:
- 第一個引數表示要注冊的依賴屬性的名稱
- 第二個引數表示要注冊的依賴屬性的型別
- 第三個引數表示要注冊的依賴屬性的所有者
- ......(
DependencyProperty.Register()提供了多種多載方式,其他引數參考檔案即可,最少需要上面3個引數)
注冊方法的定義:
public static DependencyProperty Register(string name, Type propertyType, Type ownerType, PropertyMetadata typeMetadata, ValidateValueCallback validateValueCallback)
{
}
-
第四個引數為依賴屬性元資料,具體見下文講解
-
第五個引數為一個回呼函式
原理說明:
上面定義了一個string型別的依賴屬性Name, 在WPF的源代碼中, 其實是生成了一個key/value存盤在Hashtable里面,
- 生成
key的代碼片段

- 添加到
Hashtable中

2.3 添加屬性包裝器
- 創建屬性包裝器時應當只包含對
GetValue()和SetValue()方法的呼叫,不應當添加任何驗證屬性值、引發事件等額外的代碼,因為WPF中的其他功能可能會忽略屬性封裝器,直接呼叫GetValue()和SetValue()方法
public class People : DependencyObject
{
public string Name
{
get { return (string)GetValue(NameProperty); }
set { SetValue(NameProperty, value); }
}
public static readonly DependencyProperty NameProperty =
DependencyProperty.Register("Name", typeof(string), typeof(People));
}
2.4 依賴屬性元資料
-
PropertyMetadata類存盤屬性系統使用的大多數元資料 -
在實作新的依賴項屬性時,可以通過使用方法的
Register多載來設定其元資料,
定義如下:
public PropertyMetadata(object defaultValue, PropertyChangedCallback propertyChangedCallback, CoerceValueCallback coerceValueCallback)
{
}
引數說明:
- 更改默認值,這是一個常見方案,
- 驗證回呼:更改或添加屬性,更改回呼
- 強制回呼:可以用來修正屬性值
示例說明:
- 設定了
Name依賴屬性的默認值為“元資料”
public class People : DependencyObject
{
public string Name
{
get { return (string)GetValue(NameProperty); }
set { SetValue(NameProperty, value); }
}
public static PropertyMetadata metadata = https://www.cnblogs.com/xcoast/p/new PropertyMetadata("元資料", propertyChangedCallback, coerceValueCallback);
private static void propertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
}
private static object coerceValueCallback(DependencyObject d, object baseValue)
{
return null;
}
public static readonly DependencyProperty NameProperty =
DependencyProperty.Register("Name", typeof(string), typeof(People), metadata);
}
3. 依賴屬性的繼承
-
屬性值繼承是依賴屬性值從父元素傳播到包含屬性的元素樹中的子元素的機制
-
AllowDrop在基類上UIElement實作,因此,派生自UIElement的每個控制元件上也存在該依賴項屬性, WPF 啟用依賴項屬性的值繼承,使用戶可以輕松地在父元素上設定屬性值一次,并使該屬性值傳播到元素樹中的子代元素,
3.1 定義一個可繼承的依賴屬性
public class UCStackPanel : StackPanel
{
public DateTime NowDate
{
get { return (DateTime)GetValue(NowDateProperty); }
set { SetValue(NowDateProperty, value); }
}
public static readonly DependencyProperty NowDateProperty =
DependencyProperty.Register("NowDate", typeof(DateTime), typeof(UCStackPanel), new FrameworkPropertyMetadata(DateTime.MinValue,FrameworkPropertyMetadataOptions.Inherits));
}
public class UCButton : Button
{
public DateTime NowDate
{
get { return (DateTime)GetValue(NowDateProperty); }
set { SetValue(NowDateProperty, value); }
}
public static readonly DependencyProperty NowDateProperty =
UCStackPanel.NowDateProperty.AddOwner(typeof(UCButton), new FrameworkPropertyMetadata(DateTime.MinValue, FrameworkPropertyMetadataOptions.Inherits));
}
<GroupBox Header="依賴屬性繼承" FontSize="25" Margin="20 20">
<local:UCStackPanel NowDate="{x:Static sys:DateTime.Now}">
<local:UCButton Content="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=NowDate}"/>
</local:UCStackPanel>
</GroupBox>

4. 只讀依賴屬性
創建只讀依賴項屬性的程序與創建讀/寫依賴項屬性的方式很類似,其中包括:
- 注冊只讀屬性時,呼叫
RegisterReadOnly而不是Register, - 實作 CLR 屬性包裝時,請確保它沒有公共
set訪問器, RegisterReadOnly回傳DependencyPropertyKey而不是DependencyProperty,DependencyPropertyKey將存盤在非公共類成員中,
public class UCLabel : Label
{
public UCLabel():base()
{
SetValue(AgePropertyKey, 108);
}
public int Age
{
get { return (int)GetValue(AgePropertyKey.DependencyProperty); }
}
public static readonly DependencyPropertyKey AgePropertyKey =
DependencyProperty.RegisterReadOnly("Age", typeof(int), typeof(UCLabel), new PropertyMetadata(88));
}
<GroupBox Header="只讀依賴屬性" FontSize="25" Margin="20 20">
<local:UCLabel Content="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=Age}"/>
</GroupBox>

5. 附加屬性
-
附加屬性允許子元素為父元素中定義的屬性指定唯一值,常見方案是一個子元素,它指定其父元素在 UI 中的呈現方式
-
DockPanel.Dock是附加屬性,因為它在 的子元素上DockPanel設定,而不是本身DockPanel -
附加屬性是 XAML 概念
-
依賴屬性是 WPF 概念
-
遵循 WPF 屬性命名約定,通過命名識別符號欄位來區分欄位和它們表示的屬性
<property name>Property, -
提供靜態
Get<property name>和Set<property name>訪問器方法,使屬性系統能夠訪問附加屬性,
public class PassWordExtension
{
public static string GetPassWord(DependencyObject obj)
{
return (string)obj.GetValue(PassWordProperty);
}
public static void SetPassWord(DependencyObject obj, string value)
{
obj.SetValue(PassWordProperty, value);
}
public static readonly DependencyProperty PassWordProperty =
DependencyProperty.RegisterAttached("PassWord", typeof(string), typeof(PassWordExtension), new PropertyMetadata(string.Empty));
}
6. 集合型別依賴屬性
- 如果屬性值是參考型別,應在注冊依賴屬性的類的建構式中設定默認值,
- 依賴屬性元資料不應包含默認的參考型別值,因為該值將分配給類的所有實體,從而創建單一實體類,
只讀依賴屬性:
public class People : DependencyObject
{
private static readonly DependencyPropertyKey InfosPropertyKey =
DependencyProperty.RegisterReadOnly(
name: "Infos",
propertyType: typeof(List<int>),
ownerType: typeof(People),
typeMetadata: new FrameworkPropertyMetadata()
//typeMetadata: new FrameworkPropertyMetadata(new List<int>())
);
public People() => SetValue(InfosPropertyKey, new List<int>());
public List<int> Infos =>
(List<int>)GetValue(InfosPropertyKey.DependencyProperty);
}
People p1 = new People();
People p2 = new People();
p1.Infos.Add(1);
p2.Infos.Add(10);
MessageBox.Show($"p1 contains {p1.Infos.Count}\r\n" +
$"p2 contains {p2.Infos.Count}");
p1 contains 1
p2 contains1
讀寫依賴屬性:
public class People : DependencyObject
{
public static readonly DependencyProperty InfosProperty =
DependencyProperty.Register(
name: "Infos",
propertyType: typeof(List<int>),
ownerType: typeof(Aquarium)
);
public People() => SetValue(InfosProperty, new List<int>());
public List<FrameworkElement> Infos
{
get => (List<int>)GetValue(InfosProperty);
set => SetValue(InfosProperty, value);
}
}
FreezableCollection 依賴項屬性:
-
集合型別依賴屬性不會自動報告其子屬性中的更改, 因此,如果要系結到集合,則系結可能不會報告更改,使某些資料系結方案失效, 但是,如果將 用于
FreezableCollection依賴屬性型別,則正確報告對集合元素屬性的更改,并且系結將正常作業, -
若要在依賴物件集合中啟用子屬性系結
FreezableCollection,請使用集合型別 ,具有任何派生類DependencyObject的型別約束,
下面的示例宣告一個類 Aquarium ,該類包含 FreezableCollection 型別約束為 的 FrameworkElement, 傳遞給RegisterReadOnly(String, Type, Type, PropertyMetadata)方法的PropertyMetadata中不包含默認集合值,而是使用 類建構式將默認集合值設定為新的 FreezableCollection,
public class Aquarium : DependencyObject
{
// Register a dependency property with the specified property name,
// property type, and owner type.
private static readonly DependencyPropertyKey s_aquariumContentsPropertyKey =
DependencyProperty.RegisterReadOnly(
name: "AquariumContents",
propertyType: typeof(FreezableCollection<FrameworkElement>),
ownerType: typeof(Aquarium),
typeMetadata: new FrameworkPropertyMetadata()
);
// Store the dependency property identifier as a static member of the class.
public static readonly DependencyProperty AquariumContentsProperty =
s_aquariumContentsPropertyKey.DependencyProperty;
// Set the default collection value in a class constructor.
public Aquarium() => SetValue(s_aquariumContentsPropertyKey, new FreezableCollection<FrameworkElement>());
// Declare a public get accessor.
public FreezableCollection<FrameworkElement> AquariumContents =>
(FreezableCollection<FrameworkElement>)GetValue(AquariumContentsProperty);
}
7. 屬性回呼(監控依賴屬性)
- 對依賴屬性的改變進行監聽
- 使用
RegisterAttached或Register方法時,傳入一個帶回呼函式(propertyChangedCallback)的元資料
public static readonly DependencyProperty PassWordProperty =
DependencyProperty.RegisterAttached("PassWord", typeof(string), typeof(PassWordExtension), new PropertyMetadata(string.Empty, propertyChangedCallback));
private static void propertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
PasswordBox? passwordBox = d as PasswordBox;
if (passwordBox != null)
{
passwordBox.Password = e.NewValue?.ToString();
}
}
<PasswordBox local:PassWordExtension.PassWord="{Binding PassWord, UpdateSourceTrigger=PropertyChanged}" PasswordChar="*" FontSize="25"/>
引數說明:
DependencyObject d表示哪個依賴物件使用了此依賴屬性,這里是PasswordBoxDependencyPropertyChangedEventArgs e中存盤了需要的引數,例如:老的值、新的值等
8. 屬性驗證
WPF .NET (依賴項屬性回呼和)
8.1 驗證回呼
- 在注冊依賴屬性時傳入
ValidateValueCallback型別的回呼 ValidateValueCallback回傳一個bool值,回傳false時會觸發例外ValidateValueCallback不能訪問設定屬性的實際物件,意味著不能檢查其它屬性值(一次只能訪問一個屬性)
public static DependencyProperty RegisterAttached(string name, Type propertyType, Type ownerType, PropertyMetadata defaultMetadata, ValidateValueCallback validateValueCallback)
示例說明:
public static readonly DependencyProperty PassWordProperty =
DependencyProperty.RegisterAttached("PassWord", typeof(string), typeof(PassWordExtension), new PropertyMetadata(string.Empty, propertyChangedCallback), validateValueCallback);
private static bool validateValueCallback(object value)
{
return true;
}
<PasswordBox local:PassWordExtension.PassWord="{Binding PassWord, UpdateSourceTrigger=PropertyChanged}" PasswordChar="*" FontSize="25"/>
8.2 強制回呼
- 使用
RegisterAttached或Register方法時,傳入一個帶回呼函式(coerceValueCallback)的元資料 - 可以通過回呼函式
coerceValueCallback對屬性值進行調整就叫強制回呼,也叫屬性強制 coerceValueCallback傳遞兩個引數,該數值將要應用到的物件以及準備使用的數值- 可以通過強制回呼
coerceValueCallback處理相互關聯的屬性,例如ScrollBar中的Maximun、Minimum和Value屬性,使Minimum屬性必須小于Maximun屬性,Value屬性必須位于兩者之間等等
public static DependencyProperty RegisterAttached(string name, Type propertyType, Type ownerType, PropertyMetadata defaultMetadata)
public PropertyMetadata(object defaultValue, PropertyChangedCallback propertyChangedCallback, CoerceValueCallback coerceValueCallback)
9. 使用場景
- 依賴屬性: 當您需要單獨創建控制元件時, 并且希望控制元件的某個部分能夠支持資料系結時, 你則可以使用到依賴屬性,
- 附加屬性: 這種情況很多, 正因為WPF當中并不是所有的內容都支持資料系結, 但是我們希望其支持資料系結, 這樣我們就可以創建基于自己宣告的附加屬性,添加到元素上, 讓其元素的某個原本不支持資料系結的屬性間接形成系結關系,例如:為PassWord定義附加屬性與PassWord進行關聯,例如DataGrid控制元件不支持SelectedItems, 但是我們想要實作選中多個條目進行資料系結, 這個時候也可以宣告附加屬性的形式讓其支持資料系結,
10. 使用案例
- 以密碼框
PasswordBox為例,PasswordBox的Password屬性不是依賴屬性,不支持MVVM系結,需要自定義依賴屬性來間接支持
使用方法一:
- 自定義一個幫助類
public class PasswordHelper
{
public static readonly DependencyProperty PasswordProperty =
DependencyProperty.RegisterAttached("Password", typeof(string), typeof(PasswordHelper), new FrameworkPropertyMetadata("", new PropertyChangedCallback(OnPropertyChanged)));
public static string GetPassword(DependencyObject d)
{
return d.GetValue(PasswordProperty).ToString();
}
public static void SetPassword(DependencyObject d, string value)
{
d.SetValue(PasswordProperty, value);
}
public static readonly DependencyProperty AttachProperty =
DependencyProperty.RegisterAttached("Attach", typeof(bool), typeof(PasswordHelper), new FrameworkPropertyMetadata(default(bool), new PropertyChangedCallback(OnAttached)));
public static bool GetAttach(DependencyObject d)
{
return (bool)d.GetValue(AttachProperty);
}
public static void SetAttach(DependencyObject d, bool value)
{
d.SetValue(AttachProperty, value);
}
static bool _isUpdating = false;
private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
PasswordBox password = d as PasswordBox;
password.PasswordChanged -= Password_PasswordChanged;
if (!_isUpdating)
password.Password = e.NewValue?.ToString();
password.PasswordChanged += Password_PasswordChanged;
}
private static void OnAttached(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
PasswordBox password = d as PasswordBox;
password.PasswordChanged += Password_PasswordChanged;
}
private static void Password_PasswordChanged(object sender, RoutedEventArgs e)
{
PasswordBox passwordBox = sender as PasswordBox;
_isUpdating = true;
SetPassword(passwordBox, passwordBox.Password);
_isUpdating = false;
}
}
<PasswordBox local:PasswordHelper.Password="{Binding PassWord, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
local:PasswordHelper.Attach="True" PasswordChar="*" FontSize="25"/>
使用方法二:
- 使用行為來進行支持
/// <summary>
/// 增加Password擴展屬性
/// </summary>
public static class PasswordBoxHelper
{
public static string GetPassword(DependencyObject obj)
{
return (string)obj.GetValue(PasswordProperty);
}
public static void SetPassword(DependencyObject obj, string value)
{
obj.SetValue(PasswordProperty, value);
}
public static readonly DependencyProperty PasswordProperty =
DependencyProperty.RegisterAttached("Password", typeof(string), typeof(PasswordBoxHelper), new PropertyMetadata("", OnPasswordPropertyChanged));
private static void OnPasswordPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
PasswordBox box = sender as PasswordBox;
string password = (string)e.NewValue;
if (box != null && box.Password != password)
{
box.Password = password;
}
}
}
/// <summary>
/// 接收PasswordBox的密碼修改事件
/// </summary>
public class PasswordBoxBehavior : Behavior<PasswordBox>
{
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.PasswordChanged += AssociatedObject_PasswordChanged;
}
private void AssociatedObject_PasswordChanged(object sender, RoutedEventArgs e)
{
PasswordBox passwordBox = sender as PasswordBox;
string password = PasswordBoxHelper.GetPassword(passwordBox);
if (passwordBox != null && passwordBox.Password != password)
PasswordBoxHelper.SetPassword(passwordBox, passwordBox.Password);
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.PasswordChanged -= AssociatedObject_PasswordChanged;
}
}
<PasswordBox local:PasswordBoxHelper.Password="{Binding PassWord, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
PasswordChar="*" FontSize="25">
<i:Interaction.Behaviors>
<local:PasswordBoxBehavior/>
</i:Interaction.Behaviors>
</PasswordBox>
11. 參考資料:
-
docs.microsoft.com
-
WPF編程寶典
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/475300.html
標籤:WPF
上一篇:如何將SqlDataReader結果轉換為通用串列List<T>
下一篇:Net5剃須刀頁面與mvc
