原文链接:
http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/148e95c6-6fb5-4399-8a56-41d0e0a72f1b
疑问:
以下代码定义了一个TextBlock的默认样式:
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<Trigger Property="Text" Value="">
<Setter Property="Visibility" Value="Collapsed"/>
</Trigger>
</Style.Triggers>
</Style>
以下是TreeView的一个 HierarchicalDataTemplate 的定义:
<HierarchicalDataTemplate DataType="XYZ" ItemsSource ="{Binding}">
<Grid x:Uid="Grid_7" Width="Auto" Height="Auto">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
</Grid.RowDefinitions>
<Image Grid.Column="0" Grid.Row="0" Margin="5,0,0,0"/>
<TextBlock Grid.Column="1" Grid.Row="0" Padding="5,0,0,0"/>
<TextBlock Grid.Column="2" Grid.Row="0" Foreground="Blue"Padding="5,0,0,0"/>
<TextBlock Grid.Column="3" Grid.Row="0" Foreground="Red"Padding="5,0,0,0"/>
</Grid>
</HierarchicalDataTemplate>
之后,就算我们把 TextBlock的默认样式加到<TreeView.Resources> 之中,TextBlock也不会像预期的那样(预期:如果为空串时 Visibility == Collapsed)。
Johnny Q. 回答:
这个行为“by design”,简而言之,当一个直接继承自 FrameworkElement 的对象在一个 FrameworkTemplate 之中时,默认样式不起作用(当该样式定义在 FrameworkTemplate 之外的逻辑树的祖先节点时)。
This is by design, as far as I know (if it's good or bad, that can be debatable; maybe things will change in future releases). For short, default styles do not work with objects directly derived from FrameworkElement, when placed inside FrameworkTemplate. See also http://shevaspace.blogspot.com/2007/03/wtf-of-wpf-part-one-templating-styling.html
注:
http://shevaspace.blogspot.com/2007/03/wtf-of-wpf-part-one-templating-styling.html (需FQ)中更为详细的描述了这个问题,给出了一个可以在 xamlpad 中演示的例子,并指出了这个行为是在 FrameworkElement.FindImplicitStyleResource() 函数中进行限制的。 以下先给出链接中的例子:
Code
<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Page.Resources>
<Style TargetType="{x:Type TextBlock}" x:Key="textBlock">
<Setter Property="TextElement.Foreground" Value="Cyan"/>
</Style>
<Style TargetType="{x:Type ContentPresenter}">
<Setter Property="TextElement.Foreground" Value="Cyan"/>
</Style>
<Style TargetType="{x:Type AccessText}">
<Setter Property="TextElement.Foreground" Value="Cyan"/>
</Style>
<Style TargetType="{x:Type Rectangle}">
<Setter Property="Fill" Value="Cyan"/>
</Style>
<Style TargetType="{x:Type InkCanvas}">
<Setter Property="Background" Value="Cyan"/>
</Style>
<Style TargetType="{x:Type Border}">
<Setter Property="BorderThickness" Value="10"/>
<Setter Property="BorderBrush" Value="Cyan"/>
</Style>
<Style TargetType="{x:Type StackPanel}">
<Setter Property="Background" Value="Green"/>
</Style>
<Style TargetType="{x:Type Control}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Label>Inside Control</Label>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<ControlTemplate x:Key="template">
<!--WTF, StackPanel, TextBlock, AccessText, ContentPresenter, InkCanvas, Image, Rectangle,
MediaElement etc cannot pick up their respective implicit styles here,
one takeway from this empircal discovery is that Elements directly derived from FramworkElement cannot work in this scenario.-->
<StackPanel>
<TextBlock Name="tb">inside TextBlock</TextBlock>
<AccessText>inside AccessText</AccessText>
<ContentPresenter>
<ContentPresenter.Content>
inside ContentPresenter
</ContentPresenter.Content>
</ContentPresenter>
<InkCanvas/>
<Rectangle Width="40" Height="40"/>
<!--WTF, Border can pick up implicit style here.-->
<Border Width="200" Height="20"><TextBlock Name="tb2">bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb</TextBlock> </Border>
<!--WTF, Control can pick up implicit style here, since Border and Control can work here, our previous hypothesis breaks.-->
<Control/>
</StackPanel>
</ControlTemplate>
</Page.Resources>
<Control Template="{StaticResource template}"/>
</Page>
之后,我们打开 MS 发布的 wpf 的源代码(.net 3.0版)找到 FrameworkElement.FindImplicitStyleResource() 函数(注意参数重载,第一个参数为FrameworkElement的才是我们要找的):
Code
1 // FindImplicitSytle(fe) : Default: unlinkedParent, deferReference
2 internal static object FindImplicitStyleResource(FrameworkElement fe, object resourceKey, out object source)
3 {
4 // Do a FindResource call only if someone in the ancestry has
5 // implicit styles. This is a performance optimization.
6
7 #if !DEBUG
8 if (fe.ShouldLookupImplicitStyles)
9 {
10 #endif
11 object unlinkedParent = null;
12 bool allowDeferredResourceReference = false;
13 bool mustReturnDeferredResourceReference = false;
14
15 // Implicit style lookup must stop at the app.
16 bool isImplicitStyleLookup = true;
17
18 // For non-controls the implicit StyleResource lookup must stop at
19 // the templated parent. Look at task 25606 for further details.
20 DependencyObject boundaryElement = null;
21 if (!(fe is Control))
22 {
23 boundaryElement = fe.TemplatedParent;
24 }
25
26 object implicitStyle = FindResourceInternal(fe, null, FrameworkElement.StyleProperty, resourceKey, unlinkedParent, allowDeferredResourceReference, mustReturnDeferredResourceReference, boundaryElement, isImplicitStyleLookup, out source);
27
28 // The reason this assert is commented is because there are specific scenarios when we can reach
29 // here even before the ShouldLookupImplicitStyles flag is updated. But this is still acceptable
30 // because the flag does get updated and the style property gets re-fetched soon after.
31
32 // Look at AccessText.GetVisualChild implementation for example and
33 // consider the following sequence of operations.
34
35 // 1. contentPresenter.AddVisualChild(accessText)
36 // 1.1. accessText._parent = contentPresenter
37 // 1.2. accessText.GetVisualChild()
38 // 1.2.1 accessText.AddVisualChild(textBlock)
39 // 1.2.1.1 textBlock.OnVisualParentChanged()
40 // 1.2.1.1.1 FindImplicitStyleResource(textBlock)
41 // .
42 // .
43 // .
44 // 1.3 accessText.OnVisualParentChanged
45 // 1.3.1 Set accessText.ShouldLookupImplicitStyle
46 // 1.3.2 FindImplicitStyleResource(accessText)
47 // 1.3.3 Set textBlock.ShouldLookupImplicitStyle
48 // 1.3.4 FindImplicitStyleResource(textBlock)
49
50 // Notice how we end up calling FindImplicitStyleResource on the textBlock before we have set the
51 // ShouldLookupImplicitStyle flag on either accessText or textBlock. However this is still acceptable
52 // because we eventually going to synchronize the flag and the style property value on both these objects.
53
54 // Debug.Assert(!(implicitStyle != DependencyProperty.UnsetValue && fe.ShouldLookupImplicitStyles == false),
55 // "ShouldLookupImplicitStyles is false even while there exists an implicit style in the lookup path. To be precise at source " + source);
56
57 return implicitStyle;
58 #if !DEBUG
59 }
60
61 source = null;
62 return DependencyProperty.UnsetValue;
63 #endif
64 }
65
在这里我们只需要关注两个地方:
1. 第4、5行的注释。
2. 第18-24行的注释及代码。
可知,如果FrameworkElement的 非Control子类 的对象,其默认样式的搜索边界是其TemplateParent,而MS是出于性能优化的角度,进行这样的设计的。
为什么值得这样设计呢?以下是我的分析、推测:
我们知道,Control 类有 Template 属性,依照上面的结论, ControlTemplate 中 FrameworkElement 的非Control子类 的对象,其默认样式的搜索边界就是 其 TemplateParent,这样设计之后,搜索默认样式的速度就会被加快(可通过使用Reflector+Reflector's baml viewer add-in 查看 PresentationFramework.Aero.dll 中的 themes/aero.normalcolor.baml 来查看 Aero 主题的控件的默认Template)。
回到篇首提到的那个问题,我们可知,可通过在HierarchicalDataTemplate 的 Grid.Resource 中定义 TextBlock 的默认样式来实现提问者想要的功能。