一、背景
使用WPF的朋友,大家都很喜歡采用定義控件的公共樣式,以便整個框架對該資源的使用,好處就是可以達到代碼復用、系統風格統一等;
1. 定義資源
<Setter Property= " Content ">
<Setter.Value>
<Image Source= " Imgs\btn.jpg "/>
</Setter.Value>
</Setter>
<Setter Property= " Width " Value= " 60 "/>
</Style>
2. 其他地方對該資源的引用:
<Button Style= " {DynamicResource ButtonStyle} "/>
<Button Style= " {DynamicResource ButtonStyle} "/>
<Button Style= " {DynamicResource ButtonStyle} "/>
</StackPanel>
該寫法會出現一種情況,如下圖所示(除了最后一個按鈕能顯示圖片,而其他按鈕卻是“空白”,這讓我的抑制不住我的好奇心,決定對該問題進行分析和思考:
二、代碼實現 :分別采用三種寫法來嘗試
1. 采用原來Content的代碼;(代碼及圖請參考上面)
2. 采用String來代替Content中的Image控件,具體代碼和結果如下:
<Setter Property= " Content " Value= " Button "/>
<Setter Property= " Width " Value= " 60 "/>
</Style>
3. 采用ContentTemplate來代替Content屬性,具體代碼和結果如下:
<Setter Property= " ContentTemplate ">
<Setter.Value>
<DataTemplate>
<Image Source= " Imgs\btn.jpg "/>
</DataTemplate>
</Setter.Value>
</Setter>
<Setter Property= " Width " Value= " 60 "/>
</Style>
結果出來了,這時能滿足我的需求,但是我的好奇心更加強烈了,我非常想知道“為什么”?
三、分析
我對以上3種情況,出現不同的結果非常好奇,我很想知道背后的運作原理;根據第一種情況,只有一個Button有圖片,所以我進行了以下大膽的假設:
1. 假設出現該情況的原因是因為引用該資源的所有按鈕使用了相同的一個Image控件,違背了“一個控件不能同存在可視樹上兩個不同節點上”;
2. 為什么String卻可以正常顯示;
3. ContentTemplate是渲染(呈現時)再進行實例化,所以顯示的每個Image控件是不同的控件;
我帶着這3個疑問進行了一步步的驗證:
1. 驗證:引用了ButtonStyle后是否使用了同一個對象;
<Button x:Name= " G_1_A " Style= " {DynamicResource ButtonStyle} "/>
<Button x:Name= " G_1_B " Style= " {DynamicResource ButtonStyle} "/>
<Button x:Name= " G_1_C " Style= " {DynamicResource ButtonStyle} "/>
</StackPanel>
Console.WriteLine( string.Format( " {0}:\t{1}(HashCode) ", G_1_A.Name, G_1_A.Content.GetHashCode()));
Console.WriteLine( string.Format( " {0}:\t{1}(HashCode) ", G_1_B.Name, G_1_B.Content.GetHashCode()));
Console.WriteLine( string.Format( " {0}:\t{1}(HashCode) ", G_1_C.Name, G_1_C.Content.GetHashCode()));
結果如下:
2. 疑問:為什么采用String卻可以,為了驗證該問題,用了以前我寫的一個工具“邏輯樹與可視化樹工具”;
錯誤圖(之前驗證方法有誤,誤導了大家),以下進行糾正
正確圖(進行了樹控件的優化)
錯誤結論:
以上3個Button的Content(String)的HashCode都不一樣,所以他們能正常顯示出來;我開始還以為String是同一個,因為String在編譯的時候,如果值是相同時會放入到駐留池中(這個出乎我的意料)
正確結論:3個Button的Content(String)的HashCode是一樣的,因為string重寫了GetHashCode方法 ,需要進一步確認是否是相同對象;
驗證是否是同個對象:
輸出結果:證明string是同個對象,驗證了string駐留池的說法是正確的;
3. 疑問:ContentTemplate是渲染(呈現時)再進行實例化,所以顯示的每個Image控件是不同的控件;
3.1 我需要對G_3_A Button的Content和ContentTemplate進行分析
var template = G_3_A.ContentTemplate;
結果如下:
3.2 我得到了一個結論是:ContentTemplate很有可能在渲染的時候才對Button的Content進行實例化,接下來的難題就是如何證明該觀點:
我需要驗證ContentTemplate里面的Content是否是同一個對象,結果如下:
Console.WriteLine( string.Format( " {0}:\t{1}(HashCode) ", G_3_B.Name, G_3_B.ContentTemplate.LoadContent().GetHashCode()));
Console.WriteLine( string.Format( " {0}:\t{1}(HashCode) ", G_3_C.Name, G_3_C.ContentTemplate.LoadContent().GetHashCode()));
結果如下:
3.3 證明ContentTemplate里面是不同的對象;我寫下了如下代碼:
var setter = style.Setters[ 0] as Setter;
var template = setter.Value as DataTemplate;
Console.WriteLine( string.Format( " 第{0}次加載ButtonContentTemplateStyle:\tContent的HashCode({1}) ", count, template.LoadContent().GetHashCode()));
count++;
結果如下:
四、總結
WPF的樹上是不允許有同一個控件存在兩個不同的節點上;如果想要實現該功能,需要實例化兩個對象然后存放到WPF的樹上(包括邏輯樹或可視化樹);Silverlight也應該是一樣的道理;WPF中的ContentControl定義樣式時都不能采用Content來定義;
五、代碼下載