天天看點

艾偉_轉載:WPF/Silverlight陷阱:XAML自定義控件的嵌套内容無法通過名稱通路

為了說明這個問題,假定我們需要實作一個具有特殊功能的按鈕控件。編寫Xaml檔案如下:

<Button x:Class="TestWpf.XamlButton"

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

Button>

對 Code Behind類,唯一的改動是把向導生成的基類從UserControl改成Button:

public partial class XamlButton : Button

{

    public XamlButton()

    {

        InitializeComponent();

    }

}

 然後在主窗體中放上這個新建立的控件:

<Window x:Class="TestWpf.Window1"

    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#代碼定義:

public class CsButton : Button

然後再試試用同樣的方式把這個控件加到主界面上:

<local:CsButton x:Name="csBtn" Click="csBtn_Click">

    <TextBlock x:Name="csText" Text="Cs Button" />

local:CsButton>

 完全沒有問題!csText通過代碼也是可以通路的,Click處理方法可以證明這一點:

private void csBtn_Click(object sender, RoutedEventArgs e)

    MessageBox.Show(csText.Text);

如果用Silverlight來實驗同樣的代碼,結果會稍有不同。在Silverlight XAML中添加x:Name并不會報錯 ,但是運作時就會出現問題——xamlText總是等于null,并且FindName("xamlText")同樣傳回null,是以文本内容用自動生成的代碼是無法通路的。但是以Button作為根對象來查找文本框,卻能夠找到:

xamlText = (TextBlock)xamlBtn.Content;

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