【问题标题】:WPF: What distinguishes a Dependency Property from a regular CLR Property?WPF:依赖属性与常规 CLR 属性的区别是什么?
【发布时间】:2011-01-20 08:28:46
【问题描述】:

在 WPF 中,“依赖属性”到底意味着什么?

我阅读了 Microsoft 的 Dependency Properties Overview,但它并没有真正吸引我。那篇文章的一部分说:

样式和模板是使用依赖属性的两个主要激励场景。样式对于设置定义应用程序用户界面 (UI) 的属性特别有用。样式通常定义为 XAML 中的资源。样式与属性系统交互,因为它们通常包含特定属性的“设置器”,以及根据另一个属性的实时值更改属性值的“触发器”。

然后示例代码是这样的:

<Style x:Key="GreenButtonStyle">
  <Setter Property="Control.Background" Value="Green"/>
</Style>
....
<Button Style="{StaticResource GreenButtonStyle}">I am green!</Button>

但我不明白这有什么特别之处。它是否只是暗示,当我将按钮上的Style 设置为给定样式时,我实际上是在隐式设置Background?这是它的症结所在吗?

【问题讨论】:

    标签: wpf dependency-properties


    【解决方案1】:

    这是我一直希望有人为我编写的关于依赖属性如何工作的解释。它是不完整的,很可能是错误的,但它会帮助你对它们有足够的理解,从而能够掌握你阅读的文档。

    依赖属性是类似属性的值,通过DependencyObject 类的方法获取和设置。它们可以(并且通常确实)看起来非常像 CLR 属性,但事实并非如此。这是关于他们的第一个令人困惑的事情。一个依赖属性实际上是由几个组件组成的。

    这是一个例子:

    DocumentRichTextBox 对象的属性。这是一个真正的 CLR 属性。也就是说,它有一个名称、一个类型、一个 getter 和一个 setter,就像任何其他 CLR 属性一样。但与“普通”属性不同的是,RichTextBox 属性不仅仅获取和设置实例内部的私有值。在内部,它是这样实现的:

    public FlowDocument Document
    {
       get { return (FlowDocument)GetValue(DocumentProperty); }
       set { SetValue(DocumentProperty, value); }
    }
    

    当您设置Document 时,您传入的值将与DocumentProperty 一起传递给SetValue那个是什么?而GetValue 又是如何获得它的价值的呢?还有……为什么?

    首先是什么。在RichTextBox 上定义了一个名为DocumentProperty 的静态属性。当这个属性被声明时,它是这样完成的:

    public static DependencyProperty DocumentProperty = DependencyProperty.Register(
        "Document",
        typeof(FlowDocument), 
        typeof(RichTextBox));
    

    在这种情况下,Register 方法告诉依赖属性系统 RichTextBox - 类型,而不是实例 - 现在有一个名为 Document 的类型为 FlowDocument 的依赖属性。此方法存储此信息...某处。确切地说,是对我们隐藏的实现细节。

    Document 属性的setter 调用SetValue 时,SetValue 方法查看DocumentProperty 参数,验证它确实是属于RichTextBox 的属性并且value 是正确的类型,然后将其新值存储在某个地方。 DependencyObject 的文档在此实现细节上含糊其辞,因为您实际上并不需要知道它。在我关于这些东西如何工作的心智模型中,我假设有一个 Dictionary&lt;DependencyProperty, object&gt; 类型的属性是 DependencyObject 私有的,所以派生类(如 RichTextBox)看不到它,但 GetValueSetValue可以更新它。但谁知道呢,说不定是和尚写在羊皮纸上的。

    无论如何,这个值现在就是所谓的“本地值”,也就是说,它是这个特定 RichTextBox 的本地值,就像普通属性一样。

    所有的重点是:

    1. CLR 代码不需要知道属性是依赖属性。它看起来与任何其他属性完全一样。您可以调用GetValueSetValue 来获取和设置它,但除非您使用依赖属性系统做某事,否则您可能不需要。
    2. 与普通属性不同,在获取和设置它时,可能会涉及到它所属的对象以外的东西。 (可以想象,您可以通过反思来做到这一点,但反思很慢。在字典中查找内容很快。)
    3. 这个东西——它是依赖属性系统——本质上位于一个对象和它的依赖属性之间。它可以做各种各样的事情

    什么样的东西?好吧,让我们看一些用例。

    绑定。当您绑定到一个属性时,它必须是一个依赖属性。这是因为Binding 对象实际上并没有在目标上设置属性,它在目标对象上调用SetValue

    样式。当您将对象的依赖属性设置为新值时,SetValue 会告诉样式系统您已经这样做了。这就是触发器的工作原理:它们不会发现属性的值通过魔法发生了变化,依赖属性系统会告诉它们。

    动态资源。如果您编写像Background={DynamicResource MyBackground} 这样的XAML,您可以更改MyBackground 资源的值,并且引用它的对象的背景会更新。这也不是魔术。动态资源调用SetValue

    动画。动画通过操纵属性值来工作。这些必须是依赖属性,因为动画正在调用SetValue 来获取它们。

    更改通知。注册依赖属性时,还可以指定SetValue在设置属性值时调用的函数。

    值继承。 注册依赖属性时,可以指定它参与属性值继承。当您调用GetValue 来获取对象的依赖属性的值时,GetValue 会查看是否存在本地值。如果没有,它会向上遍历父对象链,查看该属性的它们的本地值。

    这就是您可以在Window 上设置FontFamily 的方式,并且神奇地(我经常使用这个词)窗口中的每个控件都使用新字体。此外,您可以在一个窗口中拥有数百个控件,而每个控件都没有 FontFamily 成员变量来跟踪其字体(因为它们没有本地值),但您仍然可以设置 FontFamily在任何一个控件上(因为每个 DependencyObject 都有的 seekrit 隐藏值字典)。

    【讨论】:

    • 这很重要:“你可以通过反射来做到这一点,但反射很慢。在字典中查找内容很快”。这和更改的事件是依赖属性的两个很好的理由。
    • 我同意:如果我要重写这个,它会更加强调游戏中的性能问题。依赖属性系统的大部分奇怪和复杂性来自这样一个事实,即如果它不快,就会破坏 WPF。另外,我会将价值继承添加到您的充分理由列表中。值继承对 WPF 非常重要。而价值继承+变更通知=魔法。
    • 我见过的对这个话题最好的解释!
    • "绑定。当你绑定到一个属性时,它必须是一个依赖属性。这是因为 Binding 对象实际上并没有在目标上设置属性,它在目标上调用 SetValue目标对象。” 你可以绑定到一个普通的 CLR 属性,尽管你几乎肯定会想要实现 INotifyPropertyChanged
    【解决方案2】:

    在 WPF 中,“依赖属性”到底意味着什么?

    为了成为依赖属性,该属性实际上必须在类上静态定义为DependencyProperty。依赖属性系统与标准 CLR 属性有很大不同。

    但是,依赖属性的处理方式非常不同。一个类型静态地定义了一个依赖属性,并提供了一个默认值。在需要之前,运行时实际上不会为实例生成值。这提供了一个好处 - 在请求类型之前,该属性不存在,因此您可以拥有大量属性而无需开销。

    这是使样式起作用的属性的原因,但对于允许附加属性、通过可视化树“继承”属性以及 WPF 依赖的许多其他东西也很重要。

    例如,采用 DataContext 依赖属性。通常,您为 Window 或 UserControl 设置 DataContext 依赖属性。默认情况下,该窗口中的所有控件都自动“继承”其父级的DataContext proeprty,这允许您为控件指定数据绑定。使用标准 CLR 属性,您需要为窗口中的每个控件定义 DataContext,以使绑定正常工作。

    【讨论】:

    • 啊,很好的信息,谢谢。好的,那么魔术是如何工作的,它允许子控件访问祖先 Window 的 DataContext ?你能给我一个链接到这篇文章吗?
    • @Cheeso:在此处搜索“属性值继承”:msdn.microsoft.com/en-us/library/ms753391.aspx 该页面上有一些内容...
    【解决方案3】:

    了解依赖属性试图解决的问题可能会有所帮助。

    如果我们将 Binding、Animation 和 Change Event 模型放在一边,就像在其他答案中讨论过的那样,好处是内存使用,因此可扩展性以在一个窗口中托管数千个 WPF 对象。

    如果一个窗口包含 1000 个 Label 对象,每个 Label 对象具有通常的 ForegroundBackgroundFontFamilyFontSizeFontWeight 等,那么传统上这会消耗内存因为每个属性都有一个私有的支持字段来存储值。

    大多数应用程序只会更改少数属性,其中大部分将保留其默认值。基本上是非常浪费和冗余的信息(每个对象只是在内存中保存相同的默认值)

    这就是依赖属性不同的地方。

    // Lets register the Dependency Property with a default value of 20.5
    public static readonly DependencyProperty ColumnWidthProperty =
        DependencyProperty.Register("ColumnWidth", typeof(double), typeof(MyWPFControl), new UIPropertyMetadata(20.5, ColWitdhPropChanged));
    
    public double ColumnWidth
    {
      get { return (double)GetValue(ColumnWidthProperty); }
      set { SetValue(ColumnWidthProperty, value); }
    }
    

    没有私有支持字段。注册依赖属性时,可以指定默认值。因此,在大多数情况下,GetValue 的返回值是默认值,它只存储一次以覆盖应用程序所有窗口中的所有 Label 对象实例。

    当使用SetValue 设置依赖属性时,它会将非默认值存储在由对象实例标识的集合中,以便在所有后续GetValue 调用中返回。

    因此,此存储方法只会为已更改默认值的 WPF 对象的属性消耗内存。即只有与默认值的差异。

    【讨论】:

      【解决方案4】:

      一个简单/根本的区别 - 更改通知:对依赖属性的更改会在 UI 中反映/刷新,而 CLR 属性则不会。

      <Window x:Class="SampleWPF.MainWindow"
              x:Name="MainForm"
              xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
              xmlns:local="clr-namespace:SampleWPF"
              Title="About WPF Unleashed" SizeToContent="WidthAndHeight"
              Background="OrangeRed"
              >
          <StackPanel DataContext="{Binding ElementName=MainForm}">
              <!-- Bind to Dependency Property -->
              <Label Name="txtCount1" FontWeight="Bold" FontSize="20" Content="{Binding ElementName=MainForm, Path=Count1, Mode=OneWay}" />
      
              <!-- Bind to CLR Property -->
              <Label Name="txtCount2" Content="{Binding ElementName=MainForm, Path=Count2, Mode=OneWay}"></Label>
      
              <!-- Bind to Dependency Property (Using DataContext declared in StackPanel) -->
              <Label Name="txtCount3" FontWeight="Bold" FontSize="20" Content="{Binding Count1}" />
      
              <!-- Child Control binding to Dependency Property (Which propagates down element tree) -->
              <local:UserControl1 />
      
              <!-- Child Control binding to CLR Property (Won't work as CLR properties don't propagate down element tree) -->
              <local:UserControl2 />
      
              <TextBox Text="{Binding ElementName=txtCount1, Path=Content}" ></TextBox>
              <TextBox Text="{Binding ElementName=txtCount2, Path=Content}" ></TextBox>
      
              <Button Name="btnButton1" Click="btnButton1_Click_1">Increment1</Button>
              <Button Name="btnButton2" Click="btnButton1_Click_2">Increment2</Button>
          </StackPanel>
      </Window>
      
      <UserControl x:Class="SampleWPF.UserControl1"
                   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                   xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
                   xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
                   mc:Ignorable="d" 
                   d:DesignHeight="300" d:DesignWidth="300">
          <StackPanel>
              <Label Content="{Binding Count1}" ></Label>
              <!--
              <Label Content="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=Count1}"></Label>
              -->
          </StackPanel>
      </UserControl>
      
      <UserControl x:Class="SampleWPF.UserControl2"
                   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                   xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
                   xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
                   mc:Ignorable="d" 
                   d:DesignHeight="300" d:DesignWidth="300">
          <StackPanel>
              <Label Content="{Binding Count2}" ></Label>
              <!--
              <Label Content="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=Count2}"></Label>
              -->
          </StackPanel>
      </UserControl>
      

      以及后面的代码(声明CLR和Dependency属性):

          using System;
          using System.Collections.Generic;
          using System.Linq;
          using System.Text;
          using System.Threading.Tasks;
          using System.Windows;
          using System.Windows.Controls;
          using System.Windows.Data;
          using System.Windows.Documents;
          using System.Windows.Input;
          using System.Windows.Media;
          using System.Windows.Media.Imaging;
          using System.Windows.Navigation;
          using System.Windows.Shapes;
          namespace SampleWPF
          {
              /// <summary>
              /// Interaction logic for MainWindow.xaml
              /// </summary>
              public partial class MainWindow : Window
              {
                  public static readonly DependencyProperty Count1Property;
                  private int _Count2 = 2;
                  public int Count2
                  {
                      get { return _Count2; }
                      set { _Count2 = value; }
                  }
                  public MainWindow()
                  {
                      return;
                  }
                  static MainWindow()
                  {
                      // Register the property
                      MainWindow.Count1Property = 
                          DependencyProperty.Register("Count1",
                          typeof(int), typeof(MainWindow),
                          new FrameworkPropertyMetadata(1,
                          new PropertyChangedCallback(OnCount1Changed)));
                  }
                  // A .NET property wrapper (optional)
                  public int Count1
                  {
                      get { return (int)GetValue(MainWindow.Count1Property); }
                      set { SetValue(MainWindow.Count1Property, value); }
                  }
                  // A property changed callback (optional)
                  private static void OnCount1Changed(
                    DependencyObject o, DependencyPropertyChangedEventArgs e) {
      
                  }
                  private void btnButton1_Click_1(object sender, RoutedEventArgs e)
                  {
                      Count1++;
                  }
                  private void btnButton1_Click_2(object sender, RoutedEventArgs e)
                  {
                      Count2++;
                  }
              }
          }
      

      Dependency Properties 提供的另一个特性是值继承 - 在顶级元素中设置的值会沿元素树向下传播 - 在以下来自 http://en.csharp-online.net 的示例中,“Window”标签上声明的 FontSize 和 FontStyle 应用于下面的所有子元素:

      <Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        Title="About WPF Unleashed" SizeToContent="WidthAndHeight"
        FontSize="30" FontStyle="Italic"
        Background="OrangeRed">
        <StackPanel>
          <Label FontWeight="Bold" FontSize="20" Foreground="White">
            WPF Unleashed (Version 3.0)
          </Label>
          <Label>© 2006 SAMS Publishing</Label>
          <Label>Installed Chapters:</Label>
          <ListBox>
            <ListBoxItem>Chapter 1</ListBoxItem>
            <ListBoxItem>Chapter 2</ListBoxItem>
          </ListBox>
          <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
            <Button MinWidth="75" Margin="10">Help</Button>
            <Button MinWidth="75" Margin="10">OK</Button>
          </StackPanel>
          <StatusBar>You have successfully registered this product.</StatusBar>
        </StackPanel>
      </Window>
      

      参考资料: http://www.codeproject.com/Articles/29054/WPF-Data-Binding-Part-1 http://en.csharp-online.net/WPF_Concepts%E2%80%94Property_Value_Inheritance

      【讨论】:

        猜你喜欢
        • 2015-08-25
        • 1970-01-01
        • 1970-01-01
        • 2011-02-04
        • 1970-01-01
        • 1970-01-01
        • 2010-10-11
        • 2011-05-12
        • 1970-01-01
        相关资源
        最近更新 更多