除了普通的 CLR 属性, WPF 还有一套自己的属性系统。这个系统中的属性称为依赖属性。
1. 依赖属性
为啥叫依赖属性?不叫阿猫阿狗属性?
通常我们定义一个普通 CLR 属性,其实就是获取和设置一个私有字段的值。假设声明了 100 个 CLR 属性,每个属性占用 8 个字节(byte)的私有字段。那么实例化 10000 个这个类,就至少消耗了 100 * 8 * 10000 = 7.63M 内存。实际上,并非用到所有的属性。这就造成了内存浪费。
如何解决这种属性资源浪费的问题?
现实中一个例子,假设出去旅游,不可能把所有的日常生活用品都带去,一般也就带上日常换洗衣物,像锅碗瓢盆、洗衣粉、厕纸、洗发水等都要带上,岂不乱成一锅。所以,有些东西可以在要用的时候再去获取。
这就是 WPF 依赖属性的理念, 依赖属性本身没有值, 它依赖绑定源来获取值。
在 UserControl 中定义一个依赖属性,snippet 快捷方式(propdp),
public partial class DependencyPropertyDemo : UserControl
{
/// <summary>
/// 获取或设置MyProperty的值
/// </summary>
public string MyProperty
{
get => (string)GetValue(MyPropertyProperty);
set => SetValue(MyPropertyProperty, value);
}
/// <summary>
/// 标识 MyProperty 依赖属性。
/// </summary>
public static readonly DependencyProperty MyPropertyProperty =
DependencyProperty.Register(nameof(MyProperty), typeof(string),
typeof(DependencyPropertyDemo), new PropertyMetadata(default(string)));
public DependencyPropertyDemo()
{
InitializeComponent();
}
}
可以进方法 DependencyProperty.Register
查看,实质是调用内部 RegisterCommon
方法把属性注册到一个 Hashtable
,
private static Hashtable PropertyFromName = new Hashtable();
private static DependencyProperty RegisterCommon(
string name,
Type propertyType,
Type ownerType,
PropertyMetadata defaultMetadata,
ValidateValueCallback validateValueCallback)
{
//...
lock (DependencyProperty.Synchronized)
DependencyProperty.PropertyFromName[(object) key] = (object) dependencyProperty;
//...
}
这有点类似设计模式中的 享元模式(Flyweight Pattern)
,使用哈希表存储已经创建的内存对象,来减少内存消耗。
通过 GetValue/SetValue方法, 可以获取/设置依赖属性(绑定数据源)的值。
疑问:我们没有在 DependencyPropertyDemo 类中定义 GetValue/SetValue 方法,为什么也能使用呢?
因为它们已在基类中定义好了。
实际上,任何继承于 DependencyObject
的类中都可以定义依赖属性。我们用到的可视化控件基本都是继承于 Viusal
的,自然可以声明依赖属性。
2. 附加属性
附加属性,其实也是依赖属性。
使用 sinppet (propa)快捷方式创建一个附加属性,
public static readonly DependencyProperty MyAttachedProperty =
DependencyProperty.RegisterAttached(
"MyAttached",
typeof(string),
typeof(MyAttachedHelper),
new FrameworkPropertyMetadata(default(string),
flags: FrameworkPropertyMetadataOptions.Inherits)
);
public static string GetMyAttached(DependencyObject target)
{
return (string)target.GetValue(MyAttachedProperty);
}
public static void SetMyAttached(DependencyObject target, string value)
{
target.SetValue(MyAttachedProperty, value);
}
可以看到,它最终也是调用 DependencyProperty.RegisterCommon
来注册属性,GetValue/SetValue 方法一样也是基类 DependencyObject
中的 GetValue/SetValue 方法。
只是附加属性的使用场景不太一样:
依赖属性: 当希望类中某个属性支持数据绑定时, 可以用依赖属性。
附加属性: 当希望类可以绑定到某个数据源,但该类本身又没有这个依赖属性, 就可以借助其它类的依赖属性做绑定。这个过程即:类附加了其它类的一个依赖属性,简称附加属性。
3. 完整示例
在自定义控件中声明一个依赖属性,
public class MyControl : Control
{
/// <summary>
/// 获取或设置MyProperty的值
/// </summary>
public string MyProperty
{
get => (string)GetValue(MyPropertyProperty);
set => SetValue(MyPropertyProperty, value);
}
/// <summary>
/// 标识 MyProperty 依赖属性。
/// </summary>
public static readonly DependencyProperty MyPropertyProperty =
DependencyProperty.Register(nameof(MyProperty), typeof(string), typeof(MyControl),
new PropertyMetadata(default(string)));
static MyControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyControl), new FrameworkPropertyMetadata(typeof(MyControl)));
}
}
在另一个类中声明一个附加属性,
public class MyAttachedHelper : DependencyObject
{
public static readonly DependencyProperty MyAttachedProperty =
DependencyProperty.RegisterAttached(
"MyAttached",
typeof(string),
typeof(MyAttachedHelper),
new FrameworkPropertyMetadata(default(string),
flags: FrameworkPropertyMetadataOptions.Inherits)
);
public static string GetMyAttached(DependencyObject target)
{
return (string)target.GetValue(MyAttachedProperty);
}
public static void SetMyAttached(DependencyObject target, string value)
{
target.SetValue(MyAttachedProperty, value);
}
}
为控件指定样式,
<Style TargetType="{x:Type controls:MyControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type controls:MyControl}">
<Grid Background="DeepPink">
<StackPanel Orientation="Horizontal">
<TextBlock
Margin="4"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{TemplateBinding MyProperty}" />
<TextBlock
Margin="4"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="and" />
<TextBlock
Margin="4"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{TemplateBinding viewModels:MyAttachedHelper.MyAttached}" />
</StackPanel>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
绑定数据源,
public class DpViewModel
{
public string Name1 { get; set; }
public string Name2 { get; set; }
public DpViewModel()
{
Name1 = "Tom~";
Name2 = "Jerry~";
}
}
使用控件,
<Grid Width="200" Height="100">
<controls:MyControl MyProperty="{Binding Name1}"
viewModels:MyAttachedHelper.MyAttached="{Binding Name2}" />
</Grid>
显示结果,
均绑定成功。