WPF/Silverlight陷阱:XAML自定義控件的嵌套內容無法通過名稱訪問

作者: Shuhari  來源: 博客園  發布時間: 2009-10-30 11:32  閱讀: 3423 次  推薦: 0   原文鏈接   [收藏]  

為了說明這個問題,假定我們需要實現一個具有特殊功能的按鈕控件。編寫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="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#代碼定義:

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

0
0
 
標簽:WPF Silverligh
 
 

文章列表

全站熱搜
創作者介紹
創作者 大師兄 的頭像
大師兄

IT工程師數位筆記本

大師兄 發表在 痞客邦 留言(0) 人氣()