[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 >

其中异常的信息为:

ContractedBlock.gif ExpandedBlockStart.gif 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:

ContractedBlock.gif ExpandedBlockStart.gif Exception
System.InvalidCastException occurred
  Message
="Unable to cast object of type 'System.Object' to type 'System.Reflection.MemberInfo'."
  Source
="PresentationFramework"
  StackTrace:
       at System.Windows.Markup.BamlRecordReader.SetPropertyValueToParent(Boolean fromStartTag, Boolean
& isMarkupExtension)
  InnerException: 

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

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

ContractedBlock.gif ExpandedBlockStart.gif System.Windows.Markup.BamlRecordReader。SetPropertyValueToParent
element = this.GetCurrentObjectData();            
//一段处理,和exception无关……
    object parentObjectData = this.GetParentObjectData();
    IDictionary dictionaryFromContext 
= this.GetDictionaryFromContext(parentContext, true);
    
if (dictionaryFromContext != null)
    {
        
//这段貌似在处理字典资源……
    }
    
else
    {
        IList listFromContext 
= this.GetListFromContext(parentContext);
        
if (listFromContext != null)
        {
            
//parent是IList就调用IList把节点Add到父上……
        }
        
else
        {
            ArrayExtension arrayExtensionFromContext 
= this.GetArrayExtensionFromContext(parentContext);
            
if (arrayExtensionFromContext != null)
            {
                
//parent是数组……
            }
            
else
            {
                IAddChild iAddChildFromContext 
= this.GetIAddChildFromContext(parentContext);
                
if (iAddChildFromContext != null)
                {
                    
//parent是IAddChild……
                }
                
else
                {
                    
object contentProperty = parentContext.ContentProperty;
                    
if (contentProperty != null)
                    {
                        
//为某个property设值……
                    }
                    
else if (parentContext.ContextType == ReaderFlags.PropertyComplexClr)
                    {
                        
//为复杂clr属性设值……
                    }
                    
else if (parentContext.ContextType == ReaderFlags.PropertyComplexDP)
                    {
                        
//为复杂DP设值……
                    }
                    
else
                    {
                        
//其他,貌似就扔异常了
                        Type parentType = this.GetParentType();
                        
string parameter = (parentType == null? string.Empty : parentType.FullName;
                        
if (element == null)
                        {
                            
this.ThrowException("ParserCannotAddAnyChildren", parameter);
                        }
                        
else
                        {
                            
this.ThrowException("ParserCannotAddAnyChildren2", parameter, element.GetType().FullName);
                        }
                    }
                }
            }
        }
    }
}

由此结合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赋值了。

ContractedBlock.gif ExpandedBlockStart.gif 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>

转载于:https://www.cnblogs.com/winkingzhang/archive/2009/01/15/1376479.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
WPF,可以使用ToolTip来为控件添加提示文本。根据引用\[1\]和引用\[2\]的代码,可以看出可以通过设置ToolTip的样式来自定义提示框的外观。在引用\[2\],定义了一个名为"FlowToolTip"的样式,其设置了提示框的水平对齐方式、垂直对齐方式以及背景颜色等属性。同时,还使用了一个边框和阴影效果来增加提示框的立体感。在样式的模板,使用了一个TextBlock来显示提示文本的内容。这个TextBlock的字体、字号和前景色等属性也可以根据需要进行调整。引用\[3\]提到,TextBlock也可以用于ToolTip,可以通过设置TextBlock的Text属性来显示提示文本的内容。如果希望提示文本换行,可以参考引用\[4\]的代码,将TextBlock放置在一个TextBox,并设置TextWrapping属性为Wrap,这样就可以实现提示文本的换行显示。 #### 引用[.reference_title] - *1* *2* [WPF自定义tooltip样式](https://blog.csdn.net/FireGhost57/article/details/103714602)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* *4* [WPFToolTip自动换行](https://blog.csdn.net/lulei6/article/details/106545828)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值