XAML使用標簽來定義Ul元素(UIElement),每個標簽對應NET Framework類庫中的一個控件類。通過設置標簽的Atribute,不但可以對標簽所對應控件對象的Property進行賦值,還可以做一些額外的事件(如聲明命名空間、指定類名等)。
樹形結構
在界面上添加一些控件,界面如下:
界面的XAML如下所示:
<Window x:Class="LearnWpf.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:LearnWpf"
mc:Ignorable="d"
Title="MainWindow" Height="164.458" Width="344.578">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="69*"/>
<ColumnDefinition Width="19*"/>
</Grid.ColumnDefinitions>
<TextBox HorizontalAlignment="Left" Height="23" Margin="10,33,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="245"/>
<Button Content="Button" HorizontalAlignment="Left" Margin="10,91,0,0" VerticalAlignment="Top" Width="245" Height="21"/>
<StackPanel HorizontalAlignment="Left" Height="25" Margin="10,61,0,0" VerticalAlignment="Top" Width="245" Orientation="Horizontal">
<TextBox HorizontalAlignment="Left" Height="23" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="120"/>
<TextBox HorizontalAlignment="Left" Height="23" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="120"/>
</StackPanel>
<TextBox HorizontalAlignment="Left" Height="23" Margin="10,10,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="245" />
</Grid>
</Window>
可以看出UI是平面結構,XAML是樹形結構,而且同一種UI布局界面的XAML代碼可以有多種實現。在開發時,可以通過WPF基本類庫中的VisuaTreeHelper和LogicarTreeHelper兩個助手類操作XAML樹。
VS2019可以通過“視圖->其他窗口->文檔大綱”查看XAML代碼的結構,如圖所示:
對象屬性賦值語法
XAML是一種聲明性語言,在文檔只能使用標簽聲明對象、初始化對象的屬性。XAML中為對象屬性賦值共有兩種語法:
- 使用字符串(標簽的Attribute)進行簡單賦值
- 使用屬性元素(Property Element)進行復雜賦值。
注:能使用Attribute=Value形式賦值的就不使用屬性元素。
使用標簽的Attribute
一個標簽的Attribute里有一部分與對象的Property互相對應,如<Rectangle>標簽的Fill這個Attribute與Rectangle類對象的Fill屬性對應。Rectangle.Fill的類型Brush是一個抽象類,凡是以Brush為基類的類都可作為Fill屬性的值。
使用字符串對Atribute進行簡單賦值,讓Rectangle填充成單一的藍色,XAML代碼(省略了Window標簽)如下:
<Grid>
<Rectangle x:Name="rectangle" Fill="Blue" HorizontalAlignment="Left" Height="100" Margin="70,17,0,0" Stroke="Black" VerticalAlignment="Top" Width="100"/>
</Grid>
上面的“Blue”最終被翻譯成了SolidColorBrush對象,等效C#代碼如下:
SolidColorBrush sBrush = new SolidColorBrush();
sBrush.Color = Colors.Blue;
this.rectangle.Fill = sBrush;
使用屬性元素
屬性元素指的是某個非空標簽的一個元素(夾在起始標簽和結束標簽之間的一些子級標簽)對應這個標簽的一個屬性,即以元素的形式來表達一個實例的屬性。
上面Attribute賦值的例子可以用屬性元素的方式實現,XAML(省略了Window標簽)代碼如下:
<Grid>
<Rectangle x:Name="rectangle" HorizontalAlignment="Left" Height="100" Margin="70,17,0,0" Stroke="Black" VerticalAlignment="Top" Width="100">
<Rectangle.Fill>
<SolidColorBrush Color="Blue"/>
</Rectangle.Fill>
</Rectangle>
</Grid>
屬性元素語法的優勢在屬性是復雜對象時才能體現出來,比如線性漸變畫刷填充矩陣代碼(省略了Window標簽):
<Grid>
<Rectangle x:Name="rectangle" HorizontalAlignment="Left" Height="100" Margin="70,17,0,0" Stroke="Black" VerticalAlignment="Top" Width="100">
<Rectangle.Fill>
<LinearGradientBrush>
<LinearGradientBrush.GradientStops>
<GradientStop Offset="0.2" Color="LightBlue"/>
<GradientStop Offset="0.7" Color="Blue"/>
<GradientStop Offset="1.0" Color="DarkBlue"/>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
</Grid>
擴展:標記擴展(Markup Extensions)
上面大多數賦值都是為屬性生成一個新對象,當需要為對象的屬性進行特殊類型賦值時就需要使用標記擴展了,如:
- 把同一個對象賦值給兩個對象的屬性
- 給對象的屬性賦一個null值
- 將一個對象的屬性值依賴在其他對象的某個屬性上
標記擴展是一種特殊的Atribule=Value語法,Value字符串是由一對花括號及其括起來的內容組成,XAML編譯器會對這樣的內家做出解析並生成相應的對象。
使用Binding類的實例將TextBox的Text屬性依賴在Slider的Value上,當Slider的滑塊滑動時TextBox就會顯示Slider當前的值,XAML代碼(省略了Window標簽)如下:
<Grid>
<StackPanel HorizontalAlignment="Left" Height="115" Margin="10,10,0,0" VerticalAlignment="Top" Width="205">
<TextBox Height="23" TextWrapping="Wrap" Text="{Binding ElementName=slider1,Path=Value,Mode=OneWay}"/>
<Slider x:Name="slider1" Margin="0,5,0,0"/>
</StackPanel>
</Grid>
Text="{Binding ElementName=slider1,Path=Value,Mode=OneWay}"就是標記擴展,對象的數據類型名是緊鄰左花括號的字符串,對象的屬性由一串以逗號連接的子字符串負責初始化(不加引號)。
標記擴展也是對屬性的賦值,完全可以使用屬性標簽的形式來替換標記擴展,但基本上沒人這么做(太不簡潔了)。
只有MarkupExtension類的派生類(直接或間接均可)才能使用標記擴展語法來創建對象,后面會主要說明下面幾種:
- MarkupExtension的直接派生類並不多,它們是:
- System.Windows.ColorConvertedBitmapExtension
- System.Windows.DynamicResourceExtension
- System.Windows.ResourceKey
- System.Windows.StaticResourceExtension
- System.Windows.TemplateBindingExtension
- System.Windows.ThemeDictionaryExtension
- System.Windows.Data.BindingBase
- System.Windows.Data.RelativeSource
- System.Windows.Markup.ArrayExtension
- System.Windows.Markup.NullExtension
- System.Windows.Markup.StaticExtension
- System.Windows.Markup.TypeExtension
擴展:使用TypeConverter 類映射Atribute與Property
有時候我們有一些自定義的類需要使用XAML語言進行聲明,並允許它的Property與XAML標簽的Atribute互相映射,那就需要為這些Property准備適當的轉換機制。
下面以自定義類Human為例演示TypeConverter的用法,XAML代碼(省略了Window標簽)如下:
<Window.Resources>
<local:Human x:Key="human" Child="AB" />
</Window.Resources>
<Grid>
<Button Content="Button" HorizontalAlignment="Left" Margin="61,40,0,0" VerticalAlignment="Top" Width="75" Click="Button_Click"/>
</Grid>
//按鈕事件
private void Button_Click(object sender, RoutedEventArgs e)
{
Human h = (Human)this.FindResource("human");
MessageBox.Show(h.Child.Name);
}
/// <summary>
/// 以綁定轉換器的目標類型Human(TypeConverter是TypeConverterAttribute的簡寫)
/// </summary>
[TypeConverter(typeof(StringToHumanlypeConverter))]
public class Human
{
public string Name { get; set; }
public Human Child { get; set; }
}
/// <summary>
/// 字符串轉目標類型Human的轉換器
/// </summary>
public class StringToHumanlypeConverter : TypeConverter
{
//運行時轉換源類型為此目標類型,缺少此方法的重載會使運行時類型轉換異常
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string)
{
Human h = new Human();
h.Name = value as string;
return h;
}
return base.ConvertFrom(context, culture, value);
}
//設計時判斷是否支持源類型到目標類型的轉換,缺少此方法的重載會使UI設計器異常
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
}
需要注意的是TypeConverter的ConvertFrom、CanConvertFrom兩個方法必須同時重載,只有前者會導致設計時界面異常、運行時數據轉換正常,只有后者會導致設計時界面正常、運行時數據轉換異常。
事件處理器
一個XAML標簽對應着一個對象時,這個標簽的一部分Atribute會對應這個對象的Property,還有一部分Attribute對應着對象的事件(Event)。
<Grid>
<Button x:Name="button1" Content="Button" HorizontalAlignment="Left" Margin="61,40,0,0" VerticalAlignment="Top" Width="75" Click="button1_Click"/>
</Grid>
注:右鍵Click="Button_Click"->“轉到定義”,會自動生成一個空的事件處理函數。
等效的C#代碼如下所示:
Button buttonl = new Button();
buttonl.Click += new RoutedEventHandler(buton1_Click);
由於C#的partial類和XAML標簽的x.Class特征,開發軟件時完全可以把用於實現程序邏輯的C#代碼放在一個文件里,把用於描述程序U1的XAML代碼放在另一個文件里,並且讓事件性Attribute充當XAML與C#之間溝通的紐帶。這種將邏輯代碼與UI代碼分離、隱藏在UI代碼后面的形式就叫作“代碼后置”(Code-Behind)。當然,事件代碼也可以使用標簽x:Code直接寫到XAML里面。
導入程序集及引用命名空間
在XAML中引用命名空間的語法是:
xmlns:映射名="clr-namespace:類庫中命名空間的名字;assembly=類庫文件名"
- xmlns是用於在XAML中聲明命名空間的Attribute,從XML語言繼承而來(XML Namespace的縮寫)。
- 冒號后的映射名是可選的,但由於可以不加映射名的默認命名空間已經被WPF的主要命名空間占用,所以所引用的命名空間都需要加上這個映射名。映射名可以自由選擇,建議使用類庫中命名空間的原名或者縮寫。
- 引號中的字符串值確定了要引用的是哪個類庫以及類庫中的哪個命名空間。
使用引用命名空間中的類的語法如下:
<映射名:類名>.…</映射名:類名>
C#中也可以給命名空間設置映射名,但沒什么意義,如:
using Cmn=Common;
using Cu=Controls;
XAML的注釋
XAML的注釋語法亦繼承自XML,語法是:
<!--需要被注釋掉的內容-->
- XAML注釋只能出現在標器的內容區域,即只能出現在開始標簽和結來標簽之闊。
- XAML注釋不能用於注釋標簽的Attribute。
- XAML注釋不能嵌套。