程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> [WPF]在Style中設置ToolTip的問題分析

[WPF]在Style中設置ToolTip的問題分析

編輯:關於.NET

剛才開到智者千慮發的【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

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來看這一段嫌疑很大:

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賦值了。

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

To be the apostrophe which changed “Impossible” into “I’m possible”

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved