【WPF】ContentControl Style定義與使用出現問題後 -- 引發的思考
一、背景
使用WPF的朋友,大家都很喜歡採用定義控制元件的公共樣式,以便整個框架對該資源的使用,好處就是可以達到程式碼複用、系統風格統一等;
1. 定義資源
<Style TargetType="Button" x:Key="ButtonStyle"> <Setter Property="Content"> <Setter.Value> <Image Source="Imgs\btn.jpg"/> </Setter.Value> </Setter> <Setter Property="Width" Value="60"/> </Style>
2. 其他地方對該資源的引用:
<StackPanel>
<Button Style="{DynamicResource ButtonStyle}"/>
<Button Style="{DynamicResource ButtonStyle}"/>
<Button Style="{DynamicResource ButtonStyle}"/>
</StackPanel>
二、程式碼實現 :分別採用三種寫法來嘗試
1. 採用原來Content的程式碼;(程式碼及圖請參考上面)
2. 採用String來代替Content中的Image控制元件,具體程式碼和結果如下:
<Style TargetType="Button" x:Key="ButtonWithOutStringStyle">
<Setter Property="Content" Value="Button"/>
<Setter Property="Width" Value="60"/>
</Style>
3. 採用ContentTemplate來代替Content屬性,具體程式碼和結果如下:
<Style TargetType="Button" x:Key="ButtonContentTemplateStyle">
<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後是否使用了同一個物件;
<StackPanel>
<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方法 ,需要進一步確認是否是相同物件;
驗證是否是同個物件:
Console.WriteLine(string.Format("{0} is {1}:{2}", G_2_A.Name, G_2_B.Name, object.ReferenceEquals(G_2_A.Content, G_2_B.Content)));
輸出結果:證明string是同個物件,驗證了string駐留池的說法是正確的;
G_2_A is G_2_B:True
3. 疑問:ContentTemplate是渲染(呈現時)再進行例項化,所以顯示的每個Image控制元件是不同的控制元件;
3.1 我需要對G_3_A Button的Content和ContentTemplate進行分析
var content = G_3_A.Content;
var template = G_3_A.ContentTemplate;
結果如下:
3.2 我得到了一個結論是:ContentTemplate很有可能在渲染的時候才對Button的Content進行例項化,接下來的難題就是如何證明該觀點:
我需要驗證ContentTemplate裡面的Content是否是同一個物件,結果如下:
Console.WriteLine(string.Format("{0}:\t{1}(HashCode)", G_3_A.Name, G_3_A.ContentTemplate.LoadContent().GetHashCode()));
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 style = this.FindResource("ButtonContentTemplateStyle") as Style;
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來定義;
五、程式碼下載
作者:SmlAnt
出處:http://www.cnblogs.com/smlAnt
注意:轉載請保留以上內容,並標作者和出處。