一、概要
C#中属性是抽象模型的核心部分,而依赖属性是专门针对WPF的。
在WPF库实现中,依赖属性使用普通的C#属性进行了包装,使得我们可以通过和以前一样的方式来使用依赖属性。
依赖属性优点如下:
- 依赖属性加入了属性变化通知、限制、验证等功能。
- 节约内存:在WinForm中,每个UI控件的属性都赋予了初始值,这样每个相同的控件在内存中都会保存一份初始值。而WPF依赖属性很好地解决了这个问题,
它内部实现使用哈希表存储机制,对多个相同控件的相同属性的值都只保存一份。
- 支持多种提供对象:可以通过多种方式来设置依赖属性的值。
二、依赖属性的定义
定义一般遵循如下步骤:
- 定义一个类继承自DependencyObject类。
- 使用public static 声明一个DependencyProperty的变量,该变量就是真正的依赖属性。
- 在类型的静态构造函数中通过Register方法完成依赖属性的注册。
- 提供一个依赖属性的包装属性,通过这个属性来完成对依赖属性的读写操作。
参考代码如下:
1 public class DataSource : DependencyObject 2 { 3 static DataSource() 4 { 5 // Using a DependencyProperty as the backing store for Title. This enables animation, styling, binding, etc... 6 TitleProperty = 7 DependencyProperty.Register("Title", typeof(string), typeof(DataSource), new PropertyMetadata("DefaultTitle", new System.Windows.PropertyChangedCallback(PropertyChangedCall))); 8 } 9 public static readonly DependencyProperty TitleProperty; 10 public string Title 11 { 12 get { return (string)GetValue(TitleProperty); } 13 set { SetValue(TitleProperty, value); } 14 } 15 16 public static void PropertyChangedCall(DependencyObject d, DependencyPropertyChangedEventArgs e) 17 { 18 19 } 20 21 }
可以使用如下快捷方式生成依赖属性:
在VS中输入“propdp”然后连续按两次Tab键。
三、依赖属性的优先级
WPF允许在多个地方设置依赖属性的值,那么自然就涉及到依赖属性获取值的优先级问题。
以下面代码片段来看下优先级
<Ellipse Grid.Column="1" Margin="20" Fill="Pink"> <Ellipse.Style> <Style TargetType="{x:Type Ellipse}"> <Setter Property="Fill" Value="Red"></Setter> <Style.Triggers> <Trigger Property="IsMouseOver" Value="True"> <Setter Property="Fill" Value="Green"></Setter> </Trigger> </Style.Triggers> </Style> </Ellipse.Style> </Ellipse>
我们分别在不同的三处地方设置了Ellipse的Fill属性,分别是Pink、Red和Green,运行后,我们可以发现椭圆是被Pink填充的。
如果我们把 <Ellipse Grid.Column="1" Margin="20" Fill="Pink">中的Fill="Pink"去掉,在运行程序,则会发现椭圆是被Red填充的,当鼠标移到椭圆上的时候,颜色会变成Green。
WPF中,整体优先级参见下图:
四、依赖属性的继承
依赖属性是可以被继承的,即父元素的相关设置会自动传递给所有的子元素。参见如下示例代码:
1 <Window x:Class="DependencyAttr.MainWindow" 2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 5 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 6 xmlns:local="clr-namespace:DependencyAttr" 7 mc:Ignorable="d" 8 Title="MainWindow" Height="350" Width="525" FontSize="18"> 9 <Grid ShowGridLines="True"> 10 <Grid.RowDefinitions> 11 <RowDefinition/> 12 <RowDefinition/> 13 </Grid.RowDefinitions> 14 <Grid.ColumnDefinitions> 15 <ColumnDefinition/> 16 <ColumnDefinition/> 17 </Grid.ColumnDefinitions> 18 <StackPanel Margin="20"> 19 <Label Content="TestDependencyAttribute"></Label> 20 <TextBox Name="tbxTestDp"></TextBox> 21 <Button Height="30" Name="bindingBtn"></Button> 22 <Button Height="30" Name="bindingBtn2"></Button> 23 </StackPanel> 24 <Ellipse Grid.Column="1" Margin="20" Fill="Pink"> 25 <Ellipse.Style> 26 <Style TargetType="{x:Type Ellipse}"> 27 <Setter Property="Fill" Value="Red"></Setter> 28 <Style.Triggers> 29 <Trigger Property="IsMouseOver" Value="True"> 30 <Setter Property="Fill" Value="Green"></Setter> 31 </Trigger> 32 </Style.Triggers> 33 </Style> 34 35 </Ellipse.Style> 36 </Ellipse> 37 38 <StackPanel Grid.Row="1"> 39 <Label Content="继承窗体字体"></Label> 40 <Label Content="显示设置字体" FontSize="14"></Label> 41 <StatusBar>Statusbar没有继承窗体的字体</StatusBar> 42 </StackPanel> 43 44 </Grid> 45 </Window>
从上图中我们可以发现
Window.FontSize字体设置会影响其子元素的字体设置,这就是依赖属性的继承。如第一个Label没有定义FontSize,它继承了Window.FontSize值,第二个Label显示设置了
字体大小,这种继承就会被打断,所以Window.FontSize值对于第二个Label不再起作用。
StatusBar没有显式设置FontSize值,但它的字体大小也没有继承Window.FontSize的值,而是保持了系统的默认值。这是因为并不是所有元素都支持属性值继承的,如StatusBar、Tooptip和Menu控件。
上面介绍了依赖属性的继承,那我们如何把自定义的依赖属性设置为可被其他控件继承呢?
通过AddOwer方法可以设置依赖属性的继承。参考以下代码:
1 using System; 2 using System.Windows; 3 using System.Windows.Controls; 4 5 namespace DependencyAttrInherit 6 { 7 /// <summary> 8 /// Interaction logic for MainWindow.xaml 9 /// </summary> 10 public partial class MainWindow : Window 11 { 12 public MainWindow() 13 { 14 InitializeComponent(); 15 } 16 } 17 public class CustomStackPanel:StackPanel 18 { 19 20 21 public DateTime MinDate 22 { 23 get { return (DateTime)GetValue(MinDateProperty); } 24 set { SetValue(MinDateProperty, value); } 25 } 26 27 // Using a DependencyProperty as the backing store for MinDate. This enables animation, styling, binding, etc... 28 public static readonly DependencyProperty MinDateProperty = 29 DependencyProperty.Register("MinDate", typeof(DateTime), typeof(CustomStackPanel), new FrameworkPropertyMetadata(DateTime.MinValue, FrameworkPropertyMetadataOptions.Inherits)); 30 } 31 public class CustomButton:Button 32 { 33 34 static CustomButton() 35 { 36 // AddOwner方法指定依赖属性的所有者,从而实现依赖属性的继承,即CustomStackPanel的MinDate属性被CustomButton控件继承。 37 // 注意FrameworkPropertyMetadataOptions的值为Inherits 38 MinDateProperty = CustomStackPanel.MinDateProperty.AddOwner(typeof(CustomButton), new FrameworkPropertyMetadata(DateTime.MinValue, FrameworkPropertyMetadataOptions.Inherits)); 39 } 40 public DateTime MinDate 41 { 42 get { return (DateTime)GetValue(MinDateProperty); } 43 set { SetValue(MinDateProperty, value); } 44 } 45 46 private static readonly DependencyProperty MinDateProperty; 47 48 49 50 51 } 52 }
1 <Window x:Class="DependencyAttrInherit.MainWindow" 2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 5 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 6 xmlns:local="clr-namespace:DependencyAttrInherit" 7 xmlns:sys="clr-namespace:System;assembly=mscorlib" 8 mc:Ignorable="d" 9 Title="MainWindow" Height="350" Width="525"> 10 <Grid> 11 <local:CustomStackPanel x:Name="customStackPanel" MinDate="{x:Static sys:DateTime.Now}"> 12 <ContentPresenter Content="{Binding Path=MinDate, ElementName=customStackPanel}"></ContentPresenter> 13 <local:CustomButton Content="{Binding RelativeSource={RelativeSource Mode=Self}, Path=MinDate}" Height="30"></local:CustomButton> 14 </local:CustomStackPanel> 15 </Grid> 16 </Window>
运行结果如下:
五、依赖属性的监听
我们可以使用DependencyPropertyDescriptor类来对依赖属性进行监听。
以下代码片段会对TextBox的Text属性进行监听,当Text属性发生变化的时候,就会调用函数tbxTestDp_TextChanged
public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); DependencyPropertyDescriptor descriptor = DependencyPropertyDescriptor.FromProperty(TextBox.TextProperty, typeof(TextBox)); descriptor.AddValueChanged(tbxTestDp, tbxTestDp_TextChanged); } public void tbxTestDp_TextChanged(object sender, EventArgs e) { Debug.WriteLine($"Sender {sender.ToString()}"); } }
六、依赖属性的验证
对于传统的CLR属性,可以在属性的设置器中进行属性值的验证,不满足条件的值可以抛出异常。
但对于依赖属性来说,这种方法不合适,因为依赖属性通过SetValue方法来直接设置其值的。
WPF中,可以使用以下方法对依赖属性的值进行验证。
- ValidateValueCallback:该回调函数可以接受或拒绝新值。该值可作为DependencyProperty.Register方法的一个参数。
- CoerceValueCallback:该回调函数可将新值强制修改为可被接受的值。该回调函数可作为PropertyMetadata构造函数参数进行传递。
参考代码如下:
1 public partial class MainWindow : Window 2 { 3 public MainWindow() 4 { 5 try 6 { 7 InitializeComponent(); 8 SimpleDPClass sdp = new SimpleDPClass(); 9 sdp.SimpleDP = 2; 10 //rectangle.Fill = new ImageBrush() { ImageSource = new BitmapImage(new Uri("pack://application:,,,/img/123.png")) }; 11 } 12 catch(Exception e) 13 { 14 MessageBox.Show(e.Message); 15 } 16 17 } 18 } 19 20 public class SimpleDPClass : DependencyObject 21 { 22 public static readonly DependencyProperty SimpleDPProperty = 23 DependencyProperty.Register("SimpleDP", typeof(double), typeof(SimpleDPClass), 24 new FrameworkPropertyMetadata((double)0.0, FrameworkPropertyMetadataOptions.None, 25 new PropertyChangedCallback(OnValueChanged), 26 new CoerceValueCallback(CoerceValue)), 27 28 new ValidateValueCallback(ValidateValue)); 29 30 public double SimpleDP 31 { 32 get { return (double)GetValue(SimpleDPProperty); } 33 set { SetValue(SimpleDPProperty, value); } 34 } 35 36 private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 37 { 38 Debug.WriteLine($"OnValueChanged Called OldValue:{e.OldValue} NewValue:{e.NewValue}"); 39 } 40 41 private static object CoerceValue(DependencyObject d, object value) 42 { 43 Debug.WriteLine($"CoerceValue Called IniValue:{value}"); 44 45 value = 168; 46 Debug.WriteLine($"IniValue:{value} changed to NewValue:168 in CoerceValue"); 47 48 return value; 49 } 50 51 private static bool ValidateValue(object value) 52 { 53 Debug.WriteLine($"ValidateValue Called IniValue:{value}"); 54 if ((double)value > 167) 55 return false; 56 return true; 57 } 58 }
注:本文参考https://www.cnblogs.com/zhili/p/WPFDependencyProperty.html