[WPF]在Style中设置ToolTip的问题分析

刚才开到智者千虑发的【WPF】在Style中设置ToolTip的问题的博文,虽然最终给了一个暂时解决问题的方案,但是没有分析和解释其中的问题,正与他所说:但至于为什么不能直接在Setter.Value中放置TextBlock还是一个未解之谜。

趁着中午间隙,跟踪了一下,这里我将带给你完整的分析。

为了描述问题,首先,给出问题的xaml,当然,你也可以去智者千虑的blog查看详细描述。

<TextBlock x:Name="textBlockContainer" Text="ABC" Margin="10">
    
<!--如下的写法没有问题-->
    
<!--<ToolTipService.ToolTip>
        <TextBlock 
                        Text="// 通过绑定等方式从某地方获取文本"
                        TextWrapping="Wrap"
                        Width="70" />
    </ToolTipService.ToolTip>
-->

    
<!--使用Style为ToolTip赋值,出错!将会抛出exception-->
    
<TextBlock.Style>
        
<Style TargetType="TextBlock">
            
<Setter Property="ToolTipService.ToolTip">
                
<Setter.Value>
                    
<TextBlock x:Name="tooltipBlock"
                        Text
="// 通过绑定等方式从某地方获取文本"
                        TextWrapping
="Wrap"
                        Width
="70" />
                
</Setter.Value>
            
</Setter>
        
</Style>  
    
</TextBlock.Style>
</TextBlock>

其中异常的信息为:

Exception
System.Windows.Markup.XamlParseException occurred
  Message
="Cannot add content of type 'System.Windows.Controls.TextBlock' to an object of type 'System.Object'.  Error at object 'System.Windows.Controls.TextBlock' in markup file 'WpfApplication1;component/window1.xaml' Line 17 Position 30."
  Source
="PresentationFramework"
  LineNumber
=17
  LinePosition
=30
  NameContext
="Value"
  StackTrace:
       at System.Windows.Markup.XamlParseException.ThrowException(String message, Exception innerException, Int32 lineNumber, Int32 linePosition, Uri baseUri, XamlObjectIds currentXamlObjectIds, XamlObjectIds contextXamlObjectIds, Type objectType)
  InnerException: 

 

  从异常信息来看,似乎是要将TextBlock设置为某个类型为Object的对象的Content,而对于WPF程序来说,外面开到的Exception是重新throw出来的,其实内部应该有另外的exception,所以这里需要打开IDE的Exceptions设置(通常快捷键为Ctrl+D,E),选中CLR Exception。然后再次调试,这次可以看到在上面的Exception之前,IDE捕捉到了另一个Exception:

Exception

通过这个Exception信息,可以知道是在BamlRecordReader的SetPropertyValueToParent方法里出现了问题,这里在尝试转换某个object对象成MemberInfo。那么这个SetPropertyToParent在做什么呢?从方法名称来看,是为当前节点的父节点的某个属性设值;那这里为什么又要把对象cast成MemberInfo呢?

要回答这些问题,我们得要去看看SetPropertyToParent的实现,在没有Microsoft Symbol Server的时候,最好的方式就是Reflector;当然如果能使用Symbol Server,那就可以直接设置断点,进入调试了。在我分析的时候,我是使用Reflector查看代码的,这个方法很长,但是里面总共只有5处会扔出exception,从每个exception的key来看这一段嫌疑很大:

System.Windows.Markup.BamlRecordReader。SetPropertyValueToParent

由此结合Exception的描述分析,肯定是TextBlock在xaml的解析时,它的父节点是Object,这样,问题又来了,为什么呢?于是我们不得不回到我们写的xaml上,显然外面的TextBlock(名称为textBlockContainer)的肯定不会出问题,因为我们注释掉Style程序就正常了,问题肯定在Style。

我想学习过WPF之后都会知道XAML在解析过程是自顶向下,有外向内的解析的,在解析这个Style的时候,首先会创建一个Style对象,然后添加Setter,于是就解析到Setter了,也就要创建Setter对象,并为Setter对象的Property属性赋值为"ToolTipService.ToolTip";下面就解析到Setter的Value属性了,此时解析器需要创建对象TextBlock(名称为toolTipBlock),创建好了以后就把它设置到到父上的某个属性,通常是ContentProperty,如果没有就按照上面代码的顺序搜索,直到什么都没找到,扔个exception通知一下。这里TextBlock在Xaml中的父是谁?从XAML可以看到是“Setter.Value”,而这个Setter.Value在没有赋值的时候,取它返回的是一个DependencyProperty.UnsetValue,就是一个Object,显然,不可能为Object添加子,于是WPF系统认为异常。

结论:

至此,我们终于找到了问题根源,那就是在WPF的XAML节点的处理方式是实例化当前节点,然后将其赋值到它的父节点的某个属性,如果此时父节点是一个Object类型的属性时,就会出现exception。

解决方案

知道了为什么,下一步就会想到该如何解决。当然,智者千虑提供的方法是可行的,代码如下,这样就可以避过为TextBlock的父,即Setter.Value赋值了。

Code
<TextBlock x:Name="textBlockContainer" Text="ABC" Margin="10">
    
<TextBlock.Resources>
        
<TextBlock x:Key="toolTipBlock"
                   Text
="// 通过绑定等方式从某地方获取文本"
                   TextWrapping
="Wrap"
                   Width
="70" />
    
</TextBlock.Resources>
    
<TextBlock.Style>
        
<Style TargetType="TextBlock">
            
<Setter
                
Property="ToolTipService.ToolTip"
                Value
="{StaticResource toolTipBlock}"/>
        
</Style>  
    
</TextBlock.Style>
</TextBlock>
posted @ 2009-01-15 16:49  winkingzhang  阅读(2995)  评论(2编辑  收藏  举报