最近读了WPFUnleashed这本书的第三章,主要介绍了一下几个方面:
1. Logical and Visual Trees(逻辑树和视觉树)
2. Dependency Properties(依赖属性)
3. Routed Events
4. Commands
5. WPF 类的层次概述
下面分别是我觉得比较重要的地方的笔记。
1. 逻辑树和视觉树
逻辑树就是我们实际看到的节点间的树的结构。视觉树可以看作是对逻辑树的扩展,它把每一个元素还原
成它的真实面目,我们可以看到每一个元素由那些visual components组成的。例如一个ListBox,在逻辑
树中它就是一个元素,但在视觉树中我们可以看到它包含一个Border,两个Scrollbar,等等组成的。
注意在XamlPad中,只能显示Page的Visual Tree,对于Window,我们可以把根节点改成Page来查看它的
Visual Tree。
逻辑树在构造函数中就可以打印出来查看了,但是视觉树要等到整个元素(例如Window)被layout至少一次
以后才可以形成的。所以我们可以在Override void OnContentRendered(EventArgs e)中查看视觉树。
2. Dependency Properties
WPF用Dependency Properties来实现styling,动态data binding,animation,change notification等等功能。
为什么叫Dependency Properties呢,因为一个Dependency property在任何时刻都能及时的根据许多的
Providers来决定它的值,例如这个providers可能是从父元素那里继承来的属性值、由动画连续改变它的值,
自己定义的缺省值等等。
Property value inheritance(属性值的继承机制)
注意:不是所有的dependency property都能被继承的,在Dependency.Register中我们通过
FrameworkPropertyMetadataOptions.Inherits来指定这个属性可以被继承。
因为有多个Providers,它们是通过下面的顺序来决定dependency property的最终值的:
Determine Base Value -----> Evaluate -----> Apply animations -----> Coerce -----> Validate
Step(1) Determine Base Value
Base value 有以下八个提供者,其中1为最高的优先级:
1. Local value
2. Style triggers
3. Template triggers
4. Style setters
5. Theme Style triggers
6. Theme Style setters
7. Property value inheritance
8. Default value
其中Local value是指直接对某个元素设置的值,它会调用或者我们直接调用DependencyObject.SetValue
方法。Default value是Register是设置的值。
Step(2) Evaluate
如果第一步得到的值是一个expression(derives from System.Windows.Expression),WPF则根据这个
expression来计算值,这个expression可能来自于使用了DynamicResource或者是data binding。
Step(3) Apply animations
如果有动画,动画会改变上面得到的值。
Step(4) Coerce
将上面得到的值给CoerceValueCallback代理,判断是否需要返回一个强制的value。
Step(5) Validate
最后,将上面得到的值给ValidateValueCallback代理,这个代理会返回true如果上面的值是合法的,
false说明不合法,会取消整个过程,或者抛一个异常。
Tooltip:在debug的时候,如果不能确定我们最终得到的值来自哪里,我们可以用如下的方法,
例如有一个StatusBar,名称是statusBar:
ValueSource vs = DependencyPropertyHelper.GetValueSource(statusBar, FontSizeProperty);
vs 的第一个属性值是BaseValueSource,它是一个枚举,对应了Step(1)中的base value
的来源,其他的值是IsAnimated,IsCoerced,IsExpression等,对应其它几步的来源。
假设有下面的xaml:
<StackPanel TextElement.FontSize="20">
<Button>click</Button>
<StatusBar>You have successfully registered this product.</StatusBar>
</StackPanel>
我们会发现Button的字体继承了父亲的FontSize属性,大小是20。但StatusBar的字体很小,明显不是
20,为什么呢?
如果我们用上面的方法得到的BaseValueSource的值是DefaultStyle,DefaultStyle表示这个base value
来自当前系统的theme style setter的设置,排在base value的第6位,而property inheritance value排在
第7位,所以优先用系统的字体设置了。类似的还有Menu、ToolTip等等。
Clearing a local value(清除一个本地设置的值)
例如有一个Button名称为b,我们在MouseEnter trigger中设置了Foreground属性为Red,但是当Mouse
Leave的时候如何回到最初的default值呢,这时只要调用b.ClearValue(b, ForegroundProperty)就可以了。
如果我们想要清除所有的已经设定了的LocalValue,可以调用DependencyObject的
GetLocalValueEnumerator()方法来获得所有已经设定了的LocalValue的枚举:
LocalValueEnumerator locallySetProperties = uie.GetLocalValueEnumerator();
while (locallySetProperties.MoveNext())
{
DependencyProperty propertyToClear = (DependencyProperty)locallySetProperties.Current.Property;
if (!propertyToClear.ReadOnly) { uie.ClearValue(propertyToClear); }
}
Attached Properties(附加属性)
Attached Property是一种特殊的Dependency property,可以看作是在子元素中设置父元素的某些属性。
注意在上面的xaml片段中,因为StackPanel本身是没有FontSize属性的,这里是用到了TextElement的
FontSize这个Attached property,为什么能够这样做呢?
我们看这个属性的注册方式:
TextElement.FontSizeProperty = DependencyProperty.RegisterAttached(
“FontSize”, typeof(double), typeof(TextElement), new FrameworkPropertyMetadata(
SystemFonts.MessageFontSize, FrameworkPropertyMetadataOptions.Inherits |
FrameworkPropertyMetadataOptions.AffectsRender |
FrameworkPropertyMetadataOptions.AffectsMeasure),
new ValidateValueCallback(TextElement.IsValidFontSize));
然后在Control类里用AddOwner方法把Control类也当作是这个已经注册了的Attached property的Owner:
Control.FontSizeProperty = TextElement.FontSizeProperty.AddOwner(
typeof(Control), new FrameworkPropertyMetadata(SystemFonts.MessageFontSize,FrameworkPropertyMetadataOptions.Inherits));
这样就相当于Control类借用了这个属性了。而Control类又是Window类的基类,所以就实现了。
当这个根元素是Page的时候,也可以这样做,是因为在Page类中
FontSizeProperty = TextElement.FontSizeProperty.AddOwner(typeof(Page));
Attached Properties的扩展用法
例如GeometryModel3D类本身没有Tag属性,但是我们可以借用FrameworkElement的Tag属性,达到
类似于该类有这个属性的效果:
GeometryModel3D model = new GeometryModel3D();
model.SetValue(FrameworkElement.TagProperty, “my custom data”);
这样接着我们就可以通过GetValue得到这个customized data了:
object obj = model.GetValue(FrameworkElement.TagProperty);