• WPF---依赖属性(一)


    一、概要

    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     }
    View Code

      可以使用如下快捷方式生成依赖属性:

      在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属性,分别是PinkRedGreen,运行后,我们可以发现椭圆是被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>
    View Code

    从上图中我们可以发现

    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 }
    View Code
     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>
    View Code

    运行结果如下:

    五、依赖属性的监听

     我们可以使用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     }
    View Code

    注:本文参考https://www.cnblogs.com/zhili/p/WPFDependencyProperty.html

  • 相关阅读:
    centos Cannot allocate memory for the buffer pool
    hive query with field is json
    doubleclick video notes
    shell command
    最简单好用的免费录屏软件
    mysql export query result
    浏览器-前端网络
    vue-main.js中new vue()的解析
    webpack-从零搭建vuecli环境
    【js重学系列】call-apply-bind
  • 原文地址:https://www.cnblogs.com/3xiaolonglong/p/9709084.html
Copyright © 2020-2023  润新知