WPF/Silverlight陷阱:XAML自定義控件的嵌套內容無法通過名稱訪問
為了說明這個問題,假定我們需要實現一個具有特殊功能的按鈕控件。編寫Xaml文件如下:
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
Button>
對 Code Behind類,唯一的改動是把向導生成的基類從UserControl改成Button:
{
public XamlButton()
{
InitializeComponent();
}
}
然后在主窗體中放上這個新創建的控件:
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TestWpf"
Title="Window1" Height="300" Width="300">
<StackPanel>
<local:XamlButton x:Name="xamlBtn" Click="xamlBtn_Click">
<TextBlock x:Name="xamlText" Text="Xaml Button" />
local:XamlButton>
StackPanel>
Window>
看起來很平常的代碼,但是很遺憾,編譯無法通過。Visual Studio會告訴我們這樣的信息:
無法在元素“TextBlock”上設置 Name 屬性值“xamlText”。“TextBlock”位于元素“XamlButton”的范圍之內,該元素已經具有在其他范圍中定義時注冊的名稱。
或許是翻譯的問題,這段錯誤提示可以說是文不對題,因為我們可以肯定的說:這個程序里面再沒有別的地方用到xamlBtn或者xamlText這樣的名稱。
如果我們換個方式,不再用XAML聲明控件,而是用C#代碼定義:
{
}
然后再試試用同樣的方式把這個控件加到主界面上:
<TextBlock x:Name="csText" Text="Cs Button" />
local:CsButton>
完全沒有問題!csText通過代碼也是可以訪問的,Click處理方法可以證明這一點:
{
MessageBox.Show(csText.Text);
}
如果用Silverlight來實驗同樣的代碼,結果會稍有不同。在Silverlight XAML中添加x:Name并不會報錯 ,但是運行時就會出現問題——xamlText總是等于null,并且FindName("xamlText")同樣返回null,因此文本內容用自動生成的代碼是無法訪問的。但是以Button作為根對象來查找文本框,卻能夠找到:
HtmlPage.Window.Alert(xamlText.Text);
此實驗可以說明:用XAML來聲明自定義控件是存在嚴重問題的,控件內容中的對象無論是通過自動生成的成員變量還是用根容器的FindName都無法訪問。要繞開這個限制,有以下幾種可能的途徑:
1. 使用C#手工構造自定義控件,不用XAML聲明;
2. 使用自定義控件的FindName找到內容對象,然后手工綁定到成員變量;
3. 使用RegisterName手工管理命名空間。此方法我沒有實驗,并且它僅對WPF有效,Silverlight是沒有這個方法的。
上述方法2是我們最初曾經使用的方法,但是目前已經放棄了,因為手工綁定需要程序員自己編寫大量無聊的代碼,并且非常容易出錯。方法1是目前采用的方法,為此我們刪除了許多原先已經寫好的XAML,全部改用C#代碼手工創建,其實這個工作并不算困難,因為大多數時候XAML到C#的映射還是比較直觀的,但由于Silverlight的自身設計的限制,存在一個明顯的限制:
不像WPF,Silverlight里面沒有簡單的辦法可以從代碼創建一個Template。在WPF中,可以指定Template.VisualTree,但是Silverlight沒有提供這個屬性,所以要從代碼里是生成Template是很困難的。網上曾有人提供過一個思路,即用字符串拼出模板的XAML字符串,再用XamlReader.Load讀出模板對象。這個方法雖然可行,但比較丑陋,拼字符串總是下下策,維護也很困難。我們現在使用的是一個折中的辦法,Template還是用XAML來保存,但是需要編寫一些自定義代碼,以便把C#控件和XAML中的模板關聯起來。不幸的是,這個辦法導致本來是同一個控件的內容不得不在兩個地方分別維護,還要時時注意兩邊的代碼保持同步,因此也不能說是一個完滿的解決辦法。
后記: 我現在主要的工作,是基于Silverlight開發一個應用程序平臺,在此過程中已經感覺到Silverlight的一些不足,包括實現上不夠完整(比如說缺少Decorator,沒有OnRender),部分API在版本之間的大幅度變動(針對Silverlight 3 Beta的一些例子現在都已經失效了),也有設計上的復雜性導致的一些微妙的問題,本文所提到的就是這些問題的其中之一,給框架層面的實現帶來了不少麻煩。此外值得一提的是,我們現在編譯的xap包大小已經長到了800k以上,可以說和Adobe Flex編譯出來的文件大小不相上下。對于文件大小“貢獻”最多的是System.Windows.Controls、System.Windows.Controls.Data、System.Windows.Controls.Toolkit和System.Xml.Serialization這四個程序集,其中除了最后一個或許可以考慮以后不再用XML序列化,前面3個是不可能不使用的。所以Flex文件編譯以后6、7百K的體積真的算不上大,Silverlight同樣是這個水平,那些總是叫喚文件太大的同學也應該了解,RIA程序的尺寸基本上也就這樣了,除了用RSL之類技術切割一下以外,已經沒有多大優化的余地了。如果這個大小您也不能接受的話,那還是用回Ajax吧。