zoukankan      html  css  js  c++  java
  • WPF DependencyProperty

    依赖属性的特点:

    (1) 使用高效的稀疏存储系统,这意味着在不设置本地值的情况下,所有同类型对象的依赖属性都将共享默认设置,大大节约内存开销。

    (2) 依赖属性具备变更通知(Change Notification)能力,当属性值发生变化时,可以通过预先注册的元数据信息触发联动行为。

    (3) 依赖属性可以从其在树中的父级继承属性值。

    (4) 依赖属性可以依据优先级从多个提供程序中获取最终值。

    1. 依赖属性实现

    依赖属性的实现很简单:

    (1) 所在类型继承自 DependencyObject,几乎所有的 WPF 控件都间接继承自该类型。

    (2) 使用 public static 声明一个 DependencyProperty,该字段才是真正的依赖属性 (字段)。

    (3) 在静态构造中完成依赖属性的元数据注册,并获取对象引用。

    (4) 提供一个依赖属性的实例化包装属性。注意使用 DependencyObject 相关方法作为读取/访问器。

    public class MyClass : DependencyObject
    {
      public static readonly DependencyProperty TestProperty;
      static MyClass()
      {
        TestProperty = DependencyProperty.Register("Test", typeof(string), typeof(MyClass),
          new PropertyMetadata("Hello, World!", OnTestChanged));
      }
      private static void OnTestChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
      {
        
      }
      public string Test
      {
        get { return (string)GetValue(TestProperty); }
        set { SetValue(TestProperty, value); }
      }
    }

    提示: 在 VS2008 中可以使用 "propdp + TAB" 快速生成依赖属性代码。

    2. 变更通知

    当依赖属性值发生变化时,WPF 会通过预先注册的元数据 (Metadata) 信息完成某些 "关联行为" 调用。这样我们就可以在 XAML 的声明中完成行为控制,比如开始或停止动画。

    我们试着将上面我们创建的自定义依赖属性作为源绑定给相关控件。

    public partial class Window1 : Window
    {
      MyClass o;
      public Window1()
      {
        InitializeComponent();
        o = new MyClass();
        var binding = new Binding("Test") { Source = o, Mode = BindingMode.TwoWay };
        this.textBox1.SetBinding(TextBox.TextProperty, binding);
      }
      private void btnTest_Click(object sender, RoutedEventArgs e)
      {
        o.Test = DateTime.Now.ToString();
      }
    }

    窗体初始化时,textBox1.Text 自动绑定为 MyClass.TestProperty 的默认值 "Hello, World!" (参考 MyClass 静态构造的元数据注册代码)。每当我们单击按钮修改依赖属性(o.Test)时,textBox1.Text 都将自动同步更新,这就是依赖属性的行为方式。当然,我们还应该提供一个完全基于 XAML 的声明方式演示,而不是上面的 C# 代码。

    <Window x:Class="Learn.WPF.Window1"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      Title="Window1">
      <Grid>
        <TextBox x:Name="textBox1" />
        <Label x:Name="label1" Content="{Binding ElementName=textBox1, Path=Text}" />
      </Grid>
    </Window>

    label1.Content 绑定到 textBox1.Text 这个依赖属性上,每当我们修改 textBox1.Text 时,label1.Content 都会保持同步修改,这些动作无需我们编写任何代码。很显然,这大大简化了 XAML 的行为控制能力,尤其是对所谓富功能 (Rich functionality) 的控制。

    WPF 提供了一种称之为 "触发器(Trigger)" 的机制来配合依赖属性工作,我们用一个简单的属性触发器看看效果。

    <Window x:Class="Learn.WPF.Window1"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      Title="Window1">
      <Grid>
        <Button x:Name="btnTest">
          <Button.Style>
            <Style TargetType="{x:Type Button}">
              <Style.Triggers>
                <Trigger Property="IsMouseOver" Value="True">
                  <Setter Property="Foreground" Value="Red" />
                </Trigger>
              </Style.Triggers>
            </Style>
          </Button.Style>
        </Button>
      </Grid>
    </Window>

    当依赖属性 Button.IsMouseOver 发生变化时 (== True),将导致触发器执行,设置 Foreground=Red。很显然这比我们处理 MouseEnter 事件要简单得多,关键是 UI 设计人员无需编写代码即可得到所需的效果。除了属性触发器外,WPF 还提供了数据触发器和事件触发器。

    3. 属性值继承

    此继承非 OOP 上的继承,它的本意是父元素的相关设置会自动传递给所有层次的子元素 (元素可以从其在树中的父级继承依赖项属性的值)。其实很简单也很熟悉,当我们修改窗体父容器控件的字体设置时,所有级别的子控件都将自动使用该字体设置 (未做自定义设置),相信你在 WinForm 中已经使用过了。

    <Window x:Class="Learn.WPF.Window1"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      Title="Window1" FontSize="20">
      <Grid>
        <TextBox x:Name="textBox1" />
        <Label x:Name="label1" Content="Hello, World!" />
        <Button x:Name="btnTest" Content="Test" />
      </Grid>
    </Window>

    Window.FontSize 设置会影响所有的内部元素字体大小,这就是所谓的属性值继承。当然,一旦子元素提供了显式设置(比如下例中的 label1.FontSize),这种继承就会被打断。

    <Window x:Class="Learn.WPF.Window1"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      Title="Window1" FontSize="20">
      <Grid>
        <TextBox x:Name="textBox1" />
        <Label x:Name="label1" Content="Hello, World!" FontSize="10" />
        <Button x:Name="btnTest" Content="Test" />
      </Grid>
    </Window>

    注意并不是所有的依赖属性都会继承父元素的设置。

    4. 多提供程序优先级

    WPF 允许我们可以在多个地方设置依赖属性的值,这的确很方便,但也问题也不少。比如下面的例子中,我们在三个地方设置了按钮的背景颜色,只是最后哪个会起作用呢?

    <Button x:Name="button1" Background="Red">
      <Button.Style>
        <Style TargetType="{x:Type Button}">
          <Setter Property="Background" Value="Green"/>
          <Style.Triggers>
            <Trigger Property="IsMouseOver" Value="True">
              <Setter Property="Background" Value="Blue" />
            </Trigger>
          </Style.Triggers>
        </Style>
      </Button.Style>
      Click
    </Button>

    确切的答案是 "<Button Background='Red'>" 起作用了,为什么呢?因为 WPF 内在的优先级规则决定了 "本地值" 优先级别最高。

    本地值 > 样式触发器 > 模板触发器 > 样式设置程序 > 主题样式触发器 > 出题样式设置程序 > 属性值继承 > 默认值

    这样的一个过程被称之为 "基础值判断"。我们需要特别说名一下,所谓本地值是指我们直接或间接调用了 DependencyObject.SetValue,也就是显示设置了依赖属性的值。我们可以用下面这样的代码清除本地值设置。

    this.button1.ClearValue(Button.BackgroundProperty);

    虽然我们获取了基础值,但事情并没有结束,接下来有几个更厉害的选手出场,他们的优先级别更高,依赖属性必须一一过关才算最后确定下来。

    基础值判断 -> 表达式计算 -> 应用动画 -> 限制(Coerce) -> 验证 -> 最终结果

    (1) 验证是指我们注册依赖属性所提供的 ValidateValueCallback 委托方法,它最终决定了属性值设置是否有效;

    (2) 限制则是注册时提供的 CoerceValueCallback 委托,它负责验证属性值是否在允许的限制范围之内,比如大于等于 9 小于等等 100;

    (3) 动画是一种特殊行为,它的优先级高于基础设置也能理解;

    (4) 如果依赖属性值是计算表达式 (System.Windows.Expression,比如前面示例中的绑定语法),那么 WPF 会尝试 "计算" 表达式的结果。

    (5) 基础值就是上面提供的那些显示设置,它的优先级比较好确定。

    5. 附加属性

    附加属性是一种特殊的依赖属性,它看上去颇为古怪,尤其是对我们这些熟悉了面向对象规则的程序员而言。看下面的例子。

    <DockPanel>
      <CheckBox DockPanel.Dock="Top">Hello</CheckBox>
    </DockPanel>

    DockPanel.Dock 是 DockPanel 中定义的依赖属性,但却出现在子元素的声明上,看上去很诡异。

    <StackPanel TextElement.FontSize="30" TextElement.FontStyle="Blod">
      <Button>Help</Button>
      <Button>OK</Button>
    </StackPanel>

    TextElement.FontSize, TextElement.FontStyle 既不属于 StackPanel,也不属于 Button,但却能完成元素树的字体定义。

    附加属性严格来说是一个 XAML 概念,依赖属性是 WPF 概念。附加属性通常用于界面元素的布局设置。这样一种特殊的扩展机制,使得父元素可以将一些自定义设置传递给所有子元素,而无需要求子元素必须具备相同的依赖属性。这种应用方式有点扩展方法的意思,初次接触时的确不太好理解。

  • 相关阅读:
    typescript学习记录-基础类型(3)
    typescript学习记录-基础语法(2)
    typescript学习记录-介绍与环境(1)
    elementUI中的日历组件(DatePicker)怎样单独设置默认年和默认月
    怎样保持div中的子元素的长宽比例展示
    大批量端口扫描、服务识别
    域环境搭建
    sed 替换换行符
    zmap/masscan 快速扫描网络
    内网渗透——代理和转发
  • 原文地址:https://www.cnblogs.com/luluping/p/2045916.html
Copyright © 2011-2022 走看看