[.NET] XAML(1)--物件生成

摘要:[.NET] XAML(1)--物件生成

前言

XAML是微軟推出的一種宣告式標記語言,採用XML的格式讓開發人員設計應用程式介面。在微軟近期推出的各種開發平台,例如WPF、Silverlight、WP7、甚至Win8的Metro style app開發上都可以看到XAML的身影。XAML可以這麼的神奇的跨平台運作,是因為XAML不涉足執行平台的運作、機制...等等,只單純的依照開發人員的設計,建立對應的物件讓執行平台使用。例如:


XAML範例

<phone:PhoneApplicationPage 
    x:Class="XamlSample.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="800">

    <TextBlock x:Name="ShowTextBlock" Text="Hello World" FontSize="72"/>

</phone:PhoneApplicationPage>
namespace XamlSample
{
    public partial class MainPage : PhoneApplicationPage
    {
        public MainPage()
        {
            // Base
            InitializeComponent();
        }
    }
}

Code範例

<phone:PhoneApplicationPage 
    x:Class="XamlSample.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="800">

</phone:PhoneApplicationPage>
namespace XamlSample
{
    public partial class MainPage : PhoneApplicationPage
    {
        public MainPage()
        {
            // Base
            InitializeComponent();

            // Create
            TextBlock showTextBlock = new TextBlock();
            showTextBlock.Name = "ShowTextBlock";
            showTextBlock.Text = "Hello World";
            showTextBlock.FontSize = 72;
            this.Content = showTextBlock;
        }
    }
}

執行結果


這兩個WP7的範例程式,執行結果都是在畫面上顯示Hello World。而我們在程式碼裡加入中斷點,來檢視執行結果的物件(如下圖),可以看出兩個範例最終產生的物件結構是相同的。也就是說,不管是使用XAML或是使用程式碼的方式來建構畫面物件都是相同的。.NET會依照開發人員設計的XAML內容建立物件,就像是開發人員使用程式碼建立物件一樣。理解這個範例之後,可以簡單的說,XAML是用來產生物件的設定檔、執行平台使用XAML產生的物件。而.NET依照設定來產生物件是採用Reflection,我們也可以更廣義的說「XAML是用來產生物件的Reflection設定檔」。


XAML範例中斷


Code範例中斷

本篇文章採用「XAML是用來產生物件的Reflection設定檔」,這樣的角度剖析XAML。來輔助開發人員理解XAML,並且知道是如何透過XAML來產生物件。


Object-Element

下面這段XAML,代表一個TextBlock物件。當程式執行的時候,.NET會剖析XAML Element來產生一個TextBlock物件。像這樣會產生一個物件的XAML Element稱為「Object-Element」。


<sample:TextBlock x:Name="ShowTextBlock" Text="Hello World" FontSize="72" xmlns:sample="clr-namespace:System.Windows.Controls;assembly=System.Windows" />

有用過Reflection的開發人員,會知道組件名稱、命名空間、類別名稱,有這三項字串資料就可以反射生成一個物件出來。在Object-Element裡,這三項資料也有各自設定的規範。依照XAML的規範來解讀上面這個Object Element,可以得到:「組件名稱」是System.Windows、「命名空間」是System.Windows.Controls、「類別名稱」則是TextBlock。.NET剖析Object Element之後,就會依照這些字串資料,反射生成出一個TextBlock物件。將這個XAML Element取代Hello World範例裡的TextBlock依然可以正常的顯示Hello World。


<phone:PhoneApplicationPage 
    x:Class="XamlSample.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="800">

    <sample:TextBlock x:Name="ShowTextBlock" Text="Hello World" FontSize="72" xmlns:sample="clr-namespace:System.Windows.Controls;assembly=System.Windows" />
    
</phone:PhoneApplicationPage>

當然這樣的XAML內容,看起來跟一般常見的XAML有所出入,一整個複雜了許多。因為XAML是由XML發展出來的,很多的格式沿用XML的規範。許多命名空間可以由上層的Element所提供,這樣的規範大量減少XAML需要設定的資料內容。這部分有興趣的開發人員可以參考XML的相關技術資料。另外各種開發平台也定義了一些預設的關鍵字,讓XAML的設計可以變得更簡潔,這部分有興趣的開發人員可以參考開發平台的相關技術資料。以上面這個範例來說,因為是要產生開發平台預設的TextBlock,而這個開發平台預設的命名空間已經在PhoneApplicationPage做過宣告,所以可以將組件名稱、命名空間都省略掉。XAML經過這些規範的簡化之後,就可以產生出一般常見的XAML資料內容。


Property-Attribute

下面這段XAML,代表一個TextBlock物件, TextBlock物件有一個Text屬性。當程式執行的時候,.NET會剖析XAML Element來產生一個TextBlock物件,並且將這個TextBlock物件的Text屬性設定為Hello World、FontSize屬性設定為72。像這樣會設定一個物件屬性的設定,稱為「Property-Attribute」。


<TextBlock x:Name="ShowTextBlock" Text="Hello World" FontSize="72"/>

查詢MSDN可以發現TextBlock的FontSize屬性,是一個型別為System.Double的屬性。而XAML因為是XML的格式,所以被限制了只能輸入字串形式的資料。受於這樣的限制,.NET剖析Property-Attribute的時候,會嘗試將字串資料轉型為物件屬性的型別。以下面這個範例來說,在執行的階段會看到.NET的錯誤通知,告知無法將字串資料轉型為System.Double。


<TextBlock x:Name="ShowTextBlock" Text="Hello World" FontSize="72Clark"/>


Property-Element

在XAML的規範裡,Property-Attribute章節裡的TextBlock範例,也可以寫成下面範例的格式。將TextBlock的FontSize屬性改寫成為一對獨立的標籤,並且設定值寫在標籤的內容裡。像這樣設定一個物件屬性的設定,稱為「Property-Element」。


<TextBlock x:Name="ShowTextBlock" Text="Hello World" >
    <TextBlock.FontSize>
        72
    </TextBlock.FontSize>
</TextBlock>

Property-Element的寫法看起來有點多餘,但其實這是為了XAML的延展性而設計。一般物件的屬性有些不單純是int、double這些實值型別,也有可能是一個物件(Class)、一個結構(Struct)。而一個物件又會有屬性,整個物件就是以樹狀結構生長下去。這時Property-Attribute使用字串來設定這個物件樹狀結構會顯得力不從心。Property-Element定義了,可以使用Object-Element來當作內容來解決這個問題。.NET在剖析Property-Element的時候,會將Object-Element內容反射生成出對應的物件,設定為物件的物件屬性。而使用Object-Element來當作Property-Element的內容另一個原因是,Object-Element自己是描述一個物件,它又可以擁有自己的Property-Attribute、Property-Element,這樣就可以一層一層設計出物件的樹狀結構。


下面這段XAML,採用Property-Element來設定TextBlock物件的Foreground屬性。而TextBlock物件的Foreground屬性,是一個型別為Brush的物件屬性。所以在Property-Element裡採用Object-Element來生成要設定給Foreground屬性的一個Brush物件。


<TextBlock x:Name="ShowTextBlock" Text="Hello World" FontSize="72">
    <TextBlock.Foreground>
        <SolidColorBrush Color="#FF0000" />
    </TextBlock.Foreground>
</TextBlock>

仔細看上面的範例,會發現並不是生成一個Brush物件,而是生成Brush的延伸物件SolidColorBrush。這是因為Brush物件是一個抽象類別,並沒有辦法直接生成。所以只能生成延伸自Brush的SolidColorBrush來當作Foreground屬性的物件。這也就是說,我們可以生成延伸類別來設定物件屬性。這是一個物件導向開發很重要的功能,提供了開發人員抽換物件的能力,大幅增加系統物件的彈性。


Property-Element-Collection

既然一般物件的屬性不單純是int、double這些實值型別,有可能是一個物件(Class)、一個結構(Struct)。就免不了的物件的屬性,也有可能是物件、結構的集合(Collection)。Property-Element另外也定義了,可以使用多個Object-Element來當作內容來解決這個問題。.NET在剖析Property-Element的時候,會將多個Object-Element內容反射生成出對應的物件,並且加入物件的物件集合屬性。


下面這段XAML,採用Property-Element來設定LinearGradientBrush物件的GradientStops屬性。而LinearGradientBrush物件的GradientStops屬性,是一個GradientStop型別的物件集合屬性。所以在Property-Element裡採用多個Object-Element,來生成要設定給GradientStops屬性的多個GradientStop物件。


<TextBlock x:Name="ShowTextBlock" Text="Hello World" FontSize="72">
    <TextBlock.Foreground>
        <LinearGradientBrush StartPoint="0,1" EndPoint="1,0" >
            <LinearGradientBrush.GradientStops>
                <GradientStop Color="#FF0000" Offset="0.0"/>
                <GradientStop Color="#00FF00" Offset="0.5"/>
                <GradientStop Color="#0000FF" Offset="1.0"/>
            </LinearGradientBrush.GradientStops>
        </LinearGradientBrush>
    </TextBlock.Foreground>
</TextBlock>


後記

了解XAML的物件生成,在學習WPF、Silverlight、WP7等等開發平台的時候,就可以參考MSDN類別庫資料來查詢透過XAML生成的物件。了解物件本身的職責及工作內容,以及設定每個屬性會讓物件發生何種變化。這樣從物件本身開始學習的路線,會比較正確而且快速、並且不會被過多繁雜的變化所迷惑。

期許自己
能以更簡潔的文字與程式碼,傳達出程式設計背後的精神。
真正做到「以形寫神」的境界。