Attached Properties
Attached property是DP的一种特殊形态。它可将任意对象进行附加译。第一次听到这个名字可能会感到奇怪,但是WPF中的很多应用都会用到attached property。
在About dialog的例子中,比起在窗体级设置FontSize和FontStyle属性,在StackPanel控件里进行设置更加合适。但直接将这两个属性设置到StackPanel上是无效的,因为StackPanel自身并没有字体相关属性。所以必须使用FontSize和FontStyle的attached property,TextElement类定义了这两个属性。List3.5演示如如何使用attached property,图3.6为List3.5中代码的运行结果
LISTING 3.5 The About Dialog with Font Properties Moved to the Inner StackPanel
<Window xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation”
Title=”About WPF Unleashed” SizeToContent=”WidthAndHeight”
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 TextElement.FontSize=”30” TextElement.FontStyle=”Italic”
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>
TextElement.FontSize 和TextElement.FontStyle 必须设置在StackPanel内部,因为StackPanel没有字体相关的属性。当XAML的解析器或编译器遇到这种语法时,会去调用TextElement(有时也叫做attachd property提供者)的静态方法SetFontSize和SetFontStyle。List3.5中XAML代码对应的等效c#代码为:
StackPanel panel = new StackPanel();
TextElement.SetFontSize(panel, 30);
TextElement.SetFontStyle(panel, FontStyles.Italic);
panel.Orientation = Orientation.Horizontal;
panel.HorizontalAlignment = HorizontalAlignment.Center;
Button helpButton = new Button();
helpButton.MinWidth = 75;
helpButton.Margin = new Thickness(10);
helpButton.Content = “Help”;
Button okButton = new Button();
okButton.MinWidth = 75;
okButton.Margin = new Thickness(10);
okButton.Content = “OK”;
panel.Children.Add(helpButton);
panel.Children.Add(okButton);
注意FontStyles.Italic, Orientation.Horizontal和HorizontalAlignment。Center 这些枚举类型在XAML中分别为Italic,Horizontal和Center。这些转换是由.NET Framework的EnumConverter的转换器完成的。
上面的c#代码显示了List3.5中的XAML完成的工作并不神奇。只是通过一个方法将元素和不相关的属性来联系到一起。 实际上,SetFontSize这类方法在内部都调用了相同的DependencyObject.SetValue方法:
public static void SetFontSize(DependencyObject element, double value)
{
element.SetValue(TextElement.FontSizeProperty, value);
}
和SetValue方法类似,attached property也定义了GetXXX的静态方法(xxx是属性的名称),用来调用DependencyObject.GetValue方法:
public static double GetFontSize(DependencyObject element)
{
return (double)element.GetValue(TextElement.FontSizeProperty);
}
使用属性包装器时,一定不要在GetXXX和SetXXX方法里添加GetValue和SetValue以外的代码。
DIGGING DEEPER
Understanding the Attached Property Provider
List3.5中FontSize和FontStyle的attached property最令人困惑的就是这两个attached property既没有定义在Button上,也没有定义在那些含有FontSize和FontStyle的控件上。却是由看似毫不相关的TextElement类来定义的。TextElement.FontSizeProperty是独立于控件的FontSizeProperty之外的一个DP,那么attached proeprty是如何运转的呢?关键是attached property的注册方式。如果您查看了TextElement的源代码,会看到以下代码:
TextElement.FontSizeProperty = DependencyProperty.RegisterAttached(
“FontSize”, typeof(double), typeof(TextElement), new FrameworkPropertyMetadata(
SystemFonts.MessageFontSize, FrameworkPropertyMetadataOptions.Inherits |
FrameworkPropertyMetadataOptions.AffectsRender |
FrameworkPropertyMetadataOptions.AffectsMeasure),
new ValidateValueCallback(TextElement.IsValidFontSize));
除了针对attach property的应用场景对元数据进行了优化之外,上面的代码里和之前为Button控件注册IsDefaultProperty是类似的。
另一方面,WPF的控件本身并没有注册FontSize的DP。而是调TextElement类的AddOwner方法,获得了TextElement类的FontSize实例的副本:
Control.FontSizeProperty = TextElement.FontSizeProperty.AddOwner(
typeof(Control), new FrameworkPropertyMetadata(SystemFonts.MessageFontSize, FrameworkPropertyMetadataOptions.Inherits));
所以,控件的FontSize,FontStyle,以及这些和字体相关的DP,都是来源与TextElement!
理解起来会有些混乱,幸运的是,在大多情况下,那些暴露attatched property(例如GetXXX和SetXXX方法)的类实际上就是那些使用DependencyProperty.RegisterAttached方法定义了DP的类。
虽然About dialog例子中使用attached property是为了演示属性值继承,但实际上这个特性更多应用在用户界面元素的布局上。(实际上,attached property本身就是为了WPF的布局系统而设计的。)继承Panel类的各种布局类都定义了attached property,用于控制它们内部元素的摆放。这样,我们只需要在面板(panel)中定义各自的布局行为就可以了,而没必要将这些行为附加到他们的子元素上。attached property的另一个优点是,它增强了布局系统的扩展性:用户可以创建一个新型的Panel,并定义相应的attached property。第六章Layout with Panels和第十七章Layout with Custom Panels将详细介绍
DIGGING DEEPER
Attached Properties as an Extensibility Mechanism
和以前的Windows Forms技术一样,WPF内的许多类都定义了Tag属性(类型为System.Object)。该属性可以存储任意的对象的实例。而attached property也有Tag类似功能,它可以将用户数据与继承自DependencyObject的对象联系在一起,并且功能更强大,更灵活。
在XAML中使用attached property要用到静态的方法SetXXX,但您可以使用程序代码来避开这个麻烦,只需要直接调用DependencyObject类的SetValue方法就可以了。这样您就可以将任意DP作为attached property使用了。例如,下面的代码中,使用了SetValue将ListBox控件的IsTextSearchEnabled属性“系”到Button控件上,并分配了值:
// Attach an unrelated property to a Button and set its value to true:
okButton.SetValue(ListBox.IsTextSearchEnabledProperty, true);
虽然看上去没什么意义,并且也没能给Button带来任何新的功能,但是您通过这种方式就可以随意消费这个属性值,这对于应用程序或组件来说很有用处。
我们可以使用这种方式对元素进行扩展。例如,FrameworkElement类的Tag是个DP,所以可以将它附加到GeometryModel3D对象(该类是sealed的,并且没有Tag属性。第十二章3D Graphics将介绍)上:
GeometryModel3D model = new GeometryModel3D();
model.SetValue(FrameworkElement.TagProperty, “my custom data”);
这是WPF在不使用继承机制的情况对元素进行可扩展以一种方式。