所有的XAML均是一個WPF類,但是並不是所有的WPF類都可以用XAML描述。那些可以用XAML描述出來的元素大多數是用來表現用戶界面的,這些元素都是由System.Windows.UIElement類派生出的。該類包含一些與可視化用戶界面相關的屬性,以提供給XAML元素使用。
一個從System.Windows.UIElement派生的元素可以在頁面上進行可視化的呈現、可以接受來自鍵盤和鼠標的輸入、還可以可視化地調整大小和布局子元素的位置,以及觸發事件。
並不是所有的XAML元素都派生自System.Windows.UIElement。例如LineBreak、TableColumn和Document等元素,都是派生自System.Windows.FrameworkContentElement類。通常該類中的元素是不能在頁面上進行呈現出來的。
大部分的XAML元素按照其功能可以分為如下的5個部分:
·根元素
·控件元素
·面板元素
·圖形和幾何元素
·文檔元素
根元素
根元素是用來作為包含所有用戶界面元素的基本容器。一個XAML頁面必須要有一個根元素,最常用的根元素有Page、Window、StackPanel、DockPanel、Canvas和Grid。我們可以在一個根元素包含一個或者多個XAML元素,這些包含在內的XAML元素稱為該根元素的子元素(在你打開XAMLPad工具時,它會自動為你創建一個根元素-Page)。除了上述的幾種根元素外,我們還可以從Page或Window中派生出一個新類,這樣我們就可以創建自定義的根元素,並在XAML頁面中使用。
根元素還必須引用適合的命名空間,就像以前的示例程序所顯示的那樣,我們在WPF程序中經常引用的命名空間為:
01 XMLns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
02 XMLns:x="http://schemas.microsoft.com/winfx/2006/xaml"
控件元素
控件元素處理和用戶的交互。例如我們使用文本框控件輸入數據、使用多選控件選擇列表項或者執行其它的一些交互操作。這些控件元素按照其特性也可以分成5類
·簡單控件 直接由System.Windows.Control派生而來,並且不包含Content、Items或Header等屬性。這些控件主要有HorizontalScrollBar、VerticalScrollBar、Frame、TextBox和RichTextBox。
·內容控件 有Cotent屬性,但是沒有Items或Header屬性。注意內容控件中只可以有一個XAML元素作為其包含的內容,但是所包含的XAML元素又可以包含多個元素,例如Panel元素。內容控件主要有Button、RepeatButton、Label、RadioButton、CheckBox、ListBoxItem、GroupItem、StatusBarItem、ToolTip、ScrollVIEwer和Window。
·項目控件 包含Items屬性,但是沒有Head和Content屬性。項目控件通常用來展示多個選項供用戶選擇。這些控件主要有ListBox、ComboBox、Menu、ContextMenu、RadioButtonList, 和TabControl。
·標題項目控件 包含Head和Items屬性,但是沒有Content屬性。Header屬性用來為各個項目添加一個標題,而Items屬性可以用來指定多個子元素。注意在標題項目控件中,Items屬性是隱式聲明的,也就是說我們並不直接使用Items屬性指定各個子元素。下面通過下面的代碼片斷來查看標題項目控件(這裡為MenuItem)的具體實現方式:
03 <MenuItem Header="First Menu Item">
04 <MenuItem Header="First Child Item" />
05 <MenuItem Header="Second Child Item" />
06 </MenuItem>
上述的示例中包含一個主MenuItem元素,並且使用Header屬性為其指定說明信息。在<MenuItem>和</MenuItem>之間還包含了另外兩個MenuItem元素,作為主MenuItem元素的子項目。
標題項目控件有如下兩種,分別為MenuItem和ToolBar。
·標題內容控件 包含Header和Content屬性,但是沒有Items屬性。如同內容控件一樣,標題內容控件可以通過Content屬性指定一個子元素。標題內容控件包括Expander和TabItem。
Table 3-1. Attributes supported by control typ
控件類型 Content Header Items Simple N N N Content Y N N Item N N Y Header item N Y Y Header content Y Y N面板元素
面板元素主要是用來處理頁面的布局,並且可以作為容器來包含其它的元素,例如控件或其它的面板。一些從Panel派生的元素通常被用作為根元素,但是Panel的首要目的還是為XAML頁面提供布局支持,並對頁面上的各個元素進行合理的布置。尤其是一些特殊的面板元素,可以在設計時指定一個特定的布局方案。用來用戶界面設計的面板元素包括DockPanel、StackPanel、Canvas、WrapPanel和Grid。
圖形和幾何元素
圖形和幾何元素通常是用來呈現二維矢量圖形。圖形元素派生自Shape類,我們使用這些元素我就可以直接創建相關的圖形。在WPF中可以直接使用的圖形包括Ellipse(橢圓)、Line(線)、Path、Polygon(多邊形)、Polyline(折線)和Rectangle(矩形)。圖形元素也是UIElement中的一種,這就意味著我們可以在Panel元素和其它的控件中使用這些圖形控件。
幾何元素,同樣是用來呈現二維矢量圖形,但是它比圖形元素更具靈活性。我們可以使用幾何元素來生成一些簡單的二維矢量圖形,例如圓或平面多邊形,也可以用來構建一些復雜的矢量圖形,例如貝塞爾曲線和弧形。注意,幾何元素是無法進行自我呈現的,它必須依靠其它的元素才能繪制圖形,例如Drawing和Path。因為幾何元素只是創建圖形的輪廓,並沒有具體的呈現,所以我們還要使用那些在圖形元素中經常使用的屬性Fill(填充)、Stroke(畫筆)和StrokeThicknessare(畫筆濃度),來創建矢量圖形的具體呈現方式。幾何元素包括CombinedGeometry、LineGeometry, EllipseGeometry、GeometryGroup、PathGeometry、RectangleGeometry、PathSegment、ArcSegment, LineSegmen、BezierSegment、QuadraticBezierSegment、PolyQuadraticBezierSegment、PolyLineSegment, PolyBezIErSegment、StartSegment和CloseSegment等。
文檔元素
文檔元素是用來處理文檔的呈現方式。文檔一般分為兩大類,流式布局和固定布局。FixedDocument元素就是用來設計那種所見即所得的固定布局。也就是說你在設計時,文檔的格式是什麼,在呈現時它的格式也是什麼,沒有任何的差異。
而用FlowDocument元素構建的流式布局文檔在呈現時具備更好的靈活性,並且也提高了文檔的可讀性。流式布局文檔的呈現效果是由多種因素決定的,例如屏幕和頁面的大小、字體大小,以及用戶的喜好所作的設置。流式布局文檔是由一個或多個自Block或者Inline派生的元素組成的。Block、Figure、Floater、List、ListItem、Paragraph、Section、Table和TableCell等Block元素通常是用來組織和格式化文本塊。而Inline元素則是用來格式化文本塊中的文本信息,Inline元素主要包括Bold、AccessKey、LineBreak、Hyperlink、Italic、Subscript、Superscript和Underline。
一些文檔元素看起來是不是似曾相識,例如Paragraph、Table和Italic。是的,我們其它的一些用戶界面標記語言中也會用到這些類似的格式化元素,例如HTML中的<p>、<table>和<i>。雖然它們的實現機制都差不多,但是XAML中的文檔元素包含了豐富的屬性,這是相應的Html副本所不具備的。
盡管XAML的核心語法和其他的標記語言(例如HTML)非常類似,但是XAML的用戶界面元素在包含要呈現的內容時,所受到的限制是非常有限的,並不像Html中那麼嚴格。例如,我們在使用一個Button元素時並不一定就要使用文本作為提示信息,你還可以使用圖片或者其它的由UIElement派生的元素作為提示信息。下面的這個示例就是一個在Button元素中,使用一個"紅綠燈"式的三個圓作為提示信息。
07 <StackPanel XMLns=http://schemas.microsoft.com/winfx/2006/xaml/presentation
08 XMLns:x="http://schemas.microsoft.com/winfx/2006/xaml"
09 HorizontalAlignment="Center"
10 Margin="10">
11 <Button
12 Width="50"
13 Height="100">
14 <DockPanel>
15 <Ellipse Margin="5"
16 DockPanel.Dock="Top"
17 Stroke="Black"
18 Width="10"
19 Height="10"
20 Fill="Red" />
21 <Ellipse Margin="5"
22 DockPanel.Dock="Top"
23 Stroke="Black"
24 Width="10"
25 Height="10"
26 Fill="Yellow" />
27 <Ellipse Margin="5"
28 DockPanel.Dock="Top"
29 Stroke="Black"
30 Width="10"
31 Height="10"
32 Fill="Green" />
33 </DockPanel>
34 </Button>
35 </StackPanel>
屬性
在本文曾多次提過XAML元素的屬性是和WPF類中的屬性對應的,例如XAML中的Button元素存在Width屬性,該屬性是和System.Windows.Button類中的Width屬性對應的。為了說明XAML和WPF類的相互關系,我們分別在如下的兩個程序清單中使用XAML和C#來聲明一個Button元素實例:
36 <Button
37 Width="100"
38 Name="myButton"
39 Height="20"
40 Content="This is my button" />
相應的C#代碼實現:
41 Button myButton;
42 myButton.Width=100;
43 myButton.Height=20;
44 myButton.Content = "This is my button";
就如同XAML中的Button元素名稱和Button類的名稱相同一樣,Button元素的各個屬性也和Button類中的屬性名稱一一對應(Name= Name,Height=Height,依此類推)。
在上述代碼示例中,Name和Content屬性的類型都是String,因為XML本身就是一種基於文本的格式,所以其屬性值用文本來描述是合乎情理的。那麼作為XML的一種衍生類型,XAML其屬性是否還有其它類型呢?我們在下面的示例代碼使用了不同的屬性類型:
45 <Rectangle
46 Width="200"
47 Height="100"
48 Stroke="Red"
49 Fill="Yellow" />
在上述的Rectangle元素中沒有一個屬性是String類型的,Width和Height都是Double類型的,而Stroke和Fill是Brush類型的。為了支持各自不同的屬性類型,XAML需要使用.Net中的類型轉換系統(TypeConverter)。也就是在XAML中的所有屬性值都是用文本字符串來描述的,但是類型轉換系統會在某一特定的階段將這些文本字符串和屬性本身的類型映射起來。例如在上述的示例中,LengthConverter類型轉換器會自動將Width和Height對應的"200"和"100"文本字符串轉換為雙精度類型的200和100(WPF之所以知道使用Double類型是因為在FrameworkElement類中的WidthProperty和HeightProperty字段被標記了一個TypeConverterAttribute,這樣就可以知道在類型轉換時使用何種類型轉換器)。由於Stroke和Fill這兩個屬性並沒有標記一個TypeConverterAttribute,但是它們都是屬性Brush類型的,而Brush類型又標記一個TypeConverterAttribute來指定其類型轉換器為BrushConverter,所以BrushConverter會自動將其文本字符串類型的屬性值轉換為Brush類型。
盡管WPF中的類型轉換系統是非常有用的,但是在某些情況下它仍然存在著一些不足。例如某個屬性值不存在適合的類型轉換器進行從文本到實際類型的轉換,或者是一個描述屬性值的文本字符串過於復雜,以致我們不得不作大量的工作才可以實現類型的轉換。現在請你考慮一個問題,我們要實現上述的給矩形填充一種顏色是非常容易的,但是如果我們要給一個矩形填充多種顏色該怎麼實現呢(具體的效果見3-3)?我們不可以像上面那樣直接使用Fill="Yellow Red…"這樣的方式去為矩形設置不同的顏色,也就是說BrushConverter類型轉換器是無法直接轉換這種包含多個顏色描述文本的字符串的。為了講解上述類型轉換系統的缺點,這裡引入了一個新的概念,那就是從屬屬性。
圖3-3 帶漸變效果的Button元素
這裡先使用一個簡單的實例來說明下什麼是從屬屬性,從屬屬性就是一種"屬性-元素"的語法,使用這種語法來設置一個屬性時,你可以使用一種嵌套方式的元素替代原本要設置的屬性。這個嵌套方式的元素其格式為"父元素.屬性名",這裡的父元素就是將要設置屬性的元素,而屬性名就是對應的在元素中要設置的屬性。這裡說的可能讓你有點糊塗,沒關系,現在我們看看下面的程序清單:
01 <Page XMLns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
02 XMLns:x="http://schemas.microsoft.com/winfx/2006/xaml">
03 <Button Width="200" Height="100">
04 <Button.Background>
05 <SolidColorBrush Color="Blue" />
06 </Button.Background>
07 Click me
08 </Button>
09 </Page>
這是對應的C#實現代碼:
01 Button btn = new Button( );
02 SolidColorBrush brush = new SolidColorBrush( );
03 brush.Color = Colors.Blue;
04 btn.Background = brush;
05 btn.AddText("Click me");
我們要為Button預算設置其背景顏色為藍色。在上面的示例中我們使用了從屬屬性"父元素.屬性名"類型的語法,其中第4行的<Button.Background>就是一個從屬屬性,它以元素的形式存在,但是其實質上卻是一個屬性。
上面僅僅是為了讓你了解從屬屬性的用法和實現方式,如果只是為了實現上述的效果,那麼下面的程序清單完全就可以實現,你只需將Background設置為"Blue"就行了完全沒有必要大費周章地使用從屬屬性。盡管使用"父元素.屬性名"這樣格式的從屬屬性去設置一個背景顏色顯得很復雜,但是這種版本的實現可以清楚地看到你在這所創建的是何種類型的brush。
01 <Page XMLns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
02 XMLns:x="http://schemas.microsoft.com/winfx/2006/xaml">
03 <Button
04 Width="200"
05 Height="100"
06 Background="Blue">
07 </Button>
08 </Page>
如果使用從屬屬性只是為了了解底層的類型轉換實現機制的話,那麼我們根本就沒有理由去使用這些冗長的語法實現。從屬屬性之所以重要是因為在某些情況下我們需要在一個屬性中實現非常復雜的定義,就如同我們上述提及的要在一個矩形中使用多種顏色作背景。其代碼如下:
01 <Button VerticalAlignment="Center" HorizontalAlignment="Center">
02 <Button.Background>
03 <LinearGradIEntBrush StartPoint="0,0" EndPoint="0,1">
04 <LinearGradientBrush.GradIEntStops>
05 <GradIEntStop Offset="0" Color="Yellow" />
06 <GradIEntStop Offset="0.35" Color="Red" />
07 <GradIEntStop Offset="1" Color="Green" />
08 </LinearGradientBrush.GradIEntStops>
09 </LinearGradIEntBrush>
10 </Button.Background>
11 Click me
12 </Button>
這是使用C#實現的對應版本
01 Button b = new Button( );
02 b.VerticalAlignment = VerticalAlignment.Center;
03 b.HorizontalAlignment = HorizontalAlignment.Center;
04 LinearGradientBrush brush = new LinearGradIEntBrush( );
05 brush.StartPoint = new Point(0,0);
06 brush.EndPoint = new Point(0,1);
07 GradientStop gs = new GradIEntStop( );
08 gs.Offset = 0;
09 gs.Color = Color.FromRgb(0x80, 0, 0);
10 brush.GradIEntStops.AddChild(gs);
11 gs = new GradIEntStop( );
12 gs.Offset = 0.35;
13 gs.Color = Colors.Red;
14 brush.GradIEntStops.AddChild(gs);
15 gs = new GradIEntStop( );
16 gs.Offset = 0.35;
17 gs.Color = Color.FromRgb(0x50, 0, 0);
18 brush.GradIEntStops.AddChild(gs);
19 b.Background = brush;
20 b.AddText("Click me");