WPF 依赖属性详解

依赖属性

    • 简介
    • 值解析策略
    • 背后的魔力
    • 如何创建 DependencyProperty
      • 值更改回调
      • 强制值回调
      • 验证回调
    • 只读依赖属性
    • 附加属性
    • 监听依赖属性变化
    • 如何清除本地值

简介

当您开始使用 WPF 开发应用程序时,您很快就会遇到 DependencyProperties。它们看起来与普通的 .NET 属性非常相似,但背后的概念要复杂和强大得多。

主要区别在于,普通 .NET 属性的值是直接从类中的私有成员读取的,而 DependencyProperty 的值是在调用从 DependencyObject 继承的 GetValue() 方法时动态解析的。

当您设置依赖属性的值时,它不会存储在对象的字段中,而是存储在基类 DependencyObject 提供的键和值的字典中。条目的键是属性的名称,值是您要设置的值。

依赖属性的优点是

  • 减少内存占用
    当您认为 UI 控件的 90% 以上的属性通常保持其初始值时,为每个属性存储一个字段是一种巨大的消耗。依赖属性通过仅在实例中存储修改的属性来解决这些问题。默认值在依赖属性中存储一次。

  • 值继承
    访问依赖项属性时,将使用值解析策略解析该值。如果未设置本地值,则依赖项属性将向上导航逻辑树,直到找到值。当您在根元素上设置FontSize时,它将应用于下面的所有文本块,除非您覆盖该值。

  • 更改通知
    依赖属性具有内置的更改通知机制。通过在属性元数据中注册回调,您会在属性值更改时收到通知。这也被数据绑定使用。

值解析策略

每次访问依赖属性时,它都会按照从高到低的优先级在内部解析值。它检查本地值是否可用,如果不可用,如果自定义样式触发器处于活动状态,…并继续直到找到一个值。最后,默认值始终可用。

WPF 依赖属性详解_第1张图片

背后的魔力

每个 WPF 控件都将一组 DependencyProperties 注册到静态 DependencyProperty 类。它们中的每一个都包含一个键 - 每个类型必须是唯一的 - 和一个包含回调和默认值的元数据。

所有想要使用 DependencyProperties 的类型都必须从 DependencyObject 派生。这个基类定义了一个键值字典,其中包含依赖属性的本地值。条目的键是使用依赖属性定义的键。

当您通过其 .NET 属性包装器访问依赖属性时,它会在内部调用 GetValue(DependencyProperty) 来访问该值。此方法通过使用下面详细说明的值解析策略来解析值。如果本地值可用,它会直接从字典中读取它。如果没有设置值,则向上查找逻辑树并搜索继承的值。如果未找到值,则采用属性元数据中定义的默认值。这个序列有点简化,但它 显示主要概念。
WPF 依赖属性详解_第2张图片

如何创建 DependencyProperty

要创建 DependencyProperty,请将类型为 DepdencyProperty 的静态字段添加到您的类型并调用 DependencyProperty.Register() 以创建依赖项属性的实例。 DependendyProperty 的名称必须始终以 …Property 结尾。这是 WPF 中的命名约定。

要使其可作为普通 .NET 属性访问,您需要添加一个属性包装器。这个包装器除了通过使用从 DependencyObject 继承的 GetValue() 和 SetValue() 方法并将 DependencyProperty 作为键传递来在内部获取和设置值外,什么都不做。

重要提示:不要向这些属性添加任何逻辑,因为只有在您从代码设置属性时才会调用它们。如果从 XAML 设置属性,则直接调用 SetValue() 方法。

如果您使用的是 Visual Studio,则可以键入 propdp 并点击 2x 选项卡来创建依赖项属性。

// Dependency Property
public static readonly DependencyProperty CurrentTimeProperty = 
     DependencyProperty.Register( "CurrentTime", typeof(DateTime),
     typeof(MyClockControl), new FrameworkPropertyMetadata(DateTime.Now));
 
// .NET Property wrapper
public DateTime CurrentTime
{
    get { return (DateTime)GetValue(CurrentTimeProperty); }
    set { SetValue(CurrentTimeProperty, value); }
}

每个 DependencyProperty 都提供用于更改通知、值强制和验证的回调。这些回调在依赖属性上注册。

new FrameworkPropertyMetadata( DateTime.Now, 
                       OnCurrentTimePropertyChanged, 
                       OnCoerceCurrentTimeProperty ),
                       OnValidateCurrentTimeProperty );

值更改回调

更改通知回调是一个静态方法,每次 TimeProperty 的值更改时都会调用该方法。新值在 EventArgs 中传递,更改值的对象作为源传递。

private static void OnCurrentTimePropertyChanged(DependencyObject source, 
        DependencyPropertyChangedEventArgs e)
{
    MyClockControl control = source as MyClockControl;
    DateTime time = (DateTime)e.NewValue;
    // Put some update logic here...
}

强制值回调

强制回调允许您在值超出边界时调整该值而不会引发异常。一个很好的例子是进度条,其值设置为低于最小值或高于最大值。在这种情况下,我们可以在允许的边界内强制该值。在以下示例中,我们将时间限制为过去。

private static object OnCoerceTimeProperty( DependencyObject sender, object data )
{
    if ((DateTime)data > DateTime.Now )
    {
        data = DateTime.Now;
    }
    return data;
}

验证回调

在验证回调中,您检查设置值是否有效。如果返回 false,则会抛出 ArgumentException。在我们的示例需求中,数据是 DateTime 的实例。

private static bool OnValidateTimeProperty(object data)
{
    return data is DateTime;
}

只读依赖属性

WPF 控件的某些依赖属性是只读的。它们通常用于报告控件的状态,例如 IsMouseOver 属性。为这个值提供一个 setter 是没有意义的。

也许您会问自己,为什么不使用普通的 .NET 属性?一个重要的原因是您不能在普通的 .NET 属性上设置触发器。

创建只读属性类似于创建常规 DependencyProperty。不是调用 DependencyProperty.Register(),而是调用 DependencyProperty.RegisterReadonly()。这会返回一个 DependencyPropertyKey。此密钥应存储在您的类的私有或受保护的静态只读字段中。该键使您可以访问从类中设置值并将其用作普通依赖属性。

第二件事是注册分配给 DependencyPropertyKey.DependencyProperty 的公共依赖属性。此属性是可以从外部访问的只读属性。

// Register the private key to set the value
private static readonly DependencyPropertyKey IsMouseOverPropertyKey = 
      DependencyProperty.RegisterReadOnly("IsMouseOver", 
      typeof(bool), typeof(MyClass), 
      new FrameworkPropertyMetadata(false));
 
// Register the public property to get the value
public static readonly DependencyProperty IsMouseoverProperty = 
      IsMouseOverPropertyKey.DependencyProperty;    
 
// .NET Property wrapper
public int IsMouseOver
{
   get { return (bool)GetValue(IsMouseoverProperty); }
   private set { SetValue(IsMouseOverPropertyKey, value); }
}

附加属性

附加属性是一种特殊的 DependencyProperties。它们允许您将一个值附加到一个对该值一无所知的对象上。

这个概念的一个很好的例子是布局面板。每个布局面板需要不同的数据来对齐其子元素。 Canvas 需要 Top 和 Left,DockPanel 需要 Dock,等等。既然你可以写自己的布局面板,列表是无限的。所以你看,不可能在所有 WPF 控件上都拥有所有这些属性。

解决方案是附加属性。它们由在特定上下文中需要来自另一个控件的数据的控件定义。例如,由父布局面板对齐的元素。

若要设置附加属性的值,请在 XAML 中添加一个特性,并带有提供附加属性的元素的前缀。要设置在 Canvas 面板中对齐的按钮的 Canvas.Top 和 Canvas.Left 属性,您可以这样编写:

<Canvas>
    <Button Canvas.Top="20" Canvas.Left="20" Content="Click me!"/>
</Canvas>
public static readonly DependencyProperty TopProperty =
    DependencyProperty.RegisterAttached("Top", 
    typeof(double), typeof(Canvas),
    new FrameworkPropertyMetadata(0d,
        FrameworkPropertyMetadataOptions.Inherits));
 
public static void SetTop(UIElement element, double value)
{
    element.SetValue(TopProperty, value);
}
 
public static double GetTop(UIElement element)
{
    return (double)element.GetValue(TopProperty);
}

监听依赖属性变化

如果要侦听依赖项属性的更改,可以将定义该属性的类型子类化并覆盖属性元数据并传递 PropertyChangedCallback。但更简单的方法是通过调用 AddValueChanged() 获取 DependencyPropertyDescriptor 并连接回调

DependencyPropertyDescriptor textDescr = DependencyPropertyDescriptor.
    FromProperty(TextBox.TextProperty, typeof(TextBox));
 
if (textDescr!= null)
{
    textDescr.AddValueChanged(myTextBox, delegate
    {
        // Add your propery changed logic here...
    });
} 

如何清除本地值

因为 null 也是一个有效的本地值,所以有一个常量 DependencyProperty.UnsetValue 来描述一个未设置的值。

button1.ClearValue( Button.ContentProperty );

你可能感兴趣的:(WPF,wpf,c#,数据库)