上一章我們對XAML有個初步的認識了,知道XAML是用來設計UI的,那麼說怎麼設計,基本用法和語法分別是什麼呢?接下來我們就系統的簡單學習一下XAML的一些基本語法吧。 1 - XAML的結構 如果學習過Winform或者其他桌面設計的應該知道我們最終設計的是與人員交互的圖形界面。比如在Winfor ...
上一章我們對XAML有個初步的認識了,知道XAML是用來設計UI的,那麼說怎麼設計,基本用法和語法分別是什麼呢?接下來我們就系統的簡單學習一下XAML的一些基本語法吧。
1 - XAML的結構
如果學習過Winform或者其他桌面設計的應該知道我們最終設計的是與人員交互的圖形界面。比如在Winform當中你去設計界面之後,VS自動給你生成了design.cs,我們打開能夠看到裡邊首先是聲明瞭對應的類,然後設置了對應類的屬性。對於xaml而言也差不多,不過唯一的區別就是,xaml的結構相對於其他設計型而言是屬於樹結構。我們知道一棵樹有對應樹幹,樹幹有很多分支,分支上邊又可以有很多分支。這個就是樹結構。xaml就是如此。
在Winform當中比如我們設計一個
其中上邊控制項一般情況下是我們預設拖上去的,當我們查看design.cs中發現是沒有層級結構的
但是在xaml中如果要設計同樣的界面,是需要在樹結構的基礎上去設計對應界面
在WPF中基本摒棄掉了傳統Winform的拖拉控制項式佈局,上面界面的代碼如下:
1 <Window 2 x:Class="FirstWPFDemo.MainWindow" 3 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 4 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 5 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 6 xmlns:local="clr-namespace:FirstWPFDemo" 7 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 8 Title="MainWindow" 9 Width="225" 10 Height="250" 11 mc:Ignorable="d"> 12 <StackPanel Margin="15"> 13 <StackPanel Margin="5" Orientation="Horizontal"> 14 <Label Content="姓名:" /> 15 <TextBox 16 Width="120" 17 Height="30" 18 VerticalAlignment="Top" /> 19 </StackPanel> 20 <StackPanel Margin="5" Orientation="Horizontal"> 21 <Label Content="性別:" /> 22 <RadioButton VerticalAlignment="Center" Content="男" /> 23 <RadioButton VerticalAlignment="Center" Content="女" /> 24 </StackPanel> 25 <StackPanel Margin="5" Orientation="Horizontal"> 26 <Label Content="年級:" /> 27 <ComboBox Width="120" VerticalAlignment="Center"> 28 <ComboBox.Items> 29 <ComboBoxItem Content="請選擇" IsSelected="True" /> 30 <ComboBoxItem Content="一年級" /> 31 <ComboBoxItem Content="二年級" /> 32 </ComboBox.Items> 33 </ComboBox> 34 </StackPanel> 35 <Button 36 Width="60" 37 Margin="0,5,20,0" 38 HorizontalAlignment="Right" 39 Content="保存" /> 40 </StackPanel> 41 </Window>
我們能夠看到就是在Window的樹幹下邊有了StackPanel,在StackPanel下又有多個容器和按鈕。最終構成了對應的界面。不難看出這個就是在樹形結構的基礎上,延伸出來的構造。
2 - 為對象屬性賦值的語法
在上述Demo中,雖然你現在還不知道怎麼佈局,怎麼設置屬性。但是其中我們能夠觀察到最簡單的一些賦值,比如當我們給Button按鈕賦值文本的時候,我們直接設置的是Button的Content屬性。如<Button Conent="點我" />
這種設置。這種呢我們叫做簡單的字元串屬性設置。
2.1 - 簡單屬性賦值
比如我們現在需要設置一個寬度為150,高度為40,字體大小為15,字體顏色為藍色,預設文本是“你好,WPF”的文本輸入框那麼我們需要這麼設置
<TextBox Width="150" Height="40" FontSize="15" Foreground="Blue" Text="你好,WPF"/>
其中呢,Width為寬度,Height為高度,FontSize為字體大小,Text為文本內容。Foreground為字體顏色
實現內容為
此時不知道作為初學者的你發沒發現一個問題。我設置的所有屬性都是字元串類型。但是界面渲染的時候還是按照對應屬性進行的渲染,比如顏色。我設置的是字元串類型的值,後臺是如何知道是顏色呢?這就需要我們學習到一個知識點叫做:類型轉換器
2.2 - 類型轉換器
它可以將任何特定類型的數據轉換為其他類型,同理,也可以將其他任何類型轉換為特定的數據類型。比如剛纔咱們介紹的那種情況。賦值賦的是字元串類型,但是渲染出來還是顏色。其實XAML解析器通過兩個步驟查找到了對應的類型轉換器。
1)檢查對應的屬性聲明。比如Foreground屬性,查看是否存在TypeConverter特性。如果提供了,即證明將指定哪種類型進行轉換。【比如Width、Height等】如果要深究LengthConverter等是怎麼工作的之後可以看看WPF源碼,簡單介紹下,所欲的類型轉換器都繼承於TypeConverter 都重寫了CanConvertFrom CanConvertTo 和 ConvertFrom。賦值時會先判斷所輸入是否能夠完成轉換。其他的可以直接去看源代碼
1 [TypeConverter(typeof(LengthConverter))] 2 [Localizability(LocalizationCategory.None, Readability = Readability.Unreadable)] 3 public double Height 4 { 5 get 6 { 7 return (double)GetValue(HeightProperty); 8 } 9 set 10 { 11 SetValue(HeightProperty, value); 12 } 13 }
2)如果再屬性聲明中沒有TypeConverter特性,XAML解析器將檢查對應類的聲明,如,Foreground屬性使用了Brush對象,由於Brush使用TypeConverter(typeof(BrushConverter))特性聲明進行了修飾,因此Brush類及其子類使用BrushConverter類型轉換器。可以直接F12查看
1 [Bindable(true)] 2 [Category("Appearance")] 3 public Brush Foreground 4 { 5 get 6 { 7 return (Brush)GetValue(ForegroundProperty); 8 } 9 set 10 { 11 SetValue(ForegroundProperty, value); 12 } 13 }
我們寫個Demo來驗證下對應的映射關係
首先新建一個Person類
1 /// <summary> 2 /// Person 人員類 3 /// </summary> 4 public class Person 5 { 6 //人員姓名 7 public string PerName { get; set; } 8 //人員類別 9 public Person PerChild { get; set; } 10 }
此時我們在界面上使用新建的類(其中部分語法可能看不懂之後會說到)
1 <Window.Resources> 2 <local:Person 3 x:Key="per" 4 PerChild="李四" 5 PerName="張三" /> 6 </Window.Resources>
新建一個Button按鈕,當點擊的時候我們獲取到PerChild並彈窗
1 <Button 2 Width="120" 3 Height="50" 4 Click="Button_Click" 5 Content="點 我" />
1 private void Button_Click(object sender, RoutedEventArgs e) 2 { 3 var per = this.FindResource("per") as Person; 4 MessageBox.Show(per.PerChild.PerName); 5 }
我們編譯之後發現能夠編譯成功,但是有對應的提示這是為什麼呢?因為我們後臺定義的PerChild是Person類型的,我們是沒辦法直接從string類型的醫生轉換為對應類別類型的。怎麼去處理這個問題呢?
1)我們首先聲明一個類型轉換器StringToPersonTypeConverter
1 /// <summary> 2 /// string類型轉換Person轉換器 3 /// </summary> 4 public class StringToPersonTypeConverter : TypeConverter 5 { 6 public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) 7 { 8 //如果值為string類型的值時 9 if (value is string) 10 { 11 Person per = new Person(); 12 per.PerName = value as string; 13 return per; 14 } 15 return base.ConvertFrom(context, culture, value); 16 } 17 }
2) 聲明完成之後再Person上邊加上對應特性
1 [TypeConverterAttribute(typeof(StringToPersonTypeConverter))]
此時我們再重新運行的時候就可以彈出對應李四了。
2.3 - 複雜屬性賦值
有一些屬性是完備的對象,有自己的一組屬性。在簡單屬性直接賦值的基礎上衍生出對應的屬性元素語法。屬性元素指的是某個標簽的一個元素對應這個標簽的一個屬性。如
<ClassName> <CalssName.PropertyName> <!--為屬性賦值--> </CalssName.PropertyName> </ClassName>
比如,就簡單在界面上畫一個矩形
1 <Rectangle 2 Width="50" 3 Height="50" 4 Margin="0,50" 5 Fill="Blue" />
那麼說問題來了,眾所周知,對於WPF而言,渲染和動畫也是比較亮點的功能。假如此時有個需求背景不僅僅是純藍色,而是漸變色,怎麼去處理,只是使用簡單的屬性賦值Fill="Blue"
已經滿足不了當前需求了,這時候應該怎麼做呢?【先不用關心你不認識的對象後邊會介紹到】
1 <Rectangle 2 Width="50" 3 Height="50" 4 Margin="0,50"> 5 <Rectangle.Fill> 6 <LinearGradientBrush> 7 <LinearGradientBrush.GradientStops> 8 <GradientStop Offset="0" Color="Red" /> 9 <GradientStop Offset="0.5" Color="Yellow" /> 10 <GradientStop Offset="1" Color="Blue" /> 11 </LinearGradientBrush.GradientStops> 12 </LinearGradientBrush> 13 </Rectangle.Fill> 14 </Rectangle>
其中<Rectangle.Fill>中使用了線性漸變畫刷來填充【後續介紹Brush時會提及到】。此時此刻我們就能感覺到,簡單賦值完不成的使用複雜屬性賦值的方式的優勢就體現出來了。界面如下
2.4 - 標記擴展
正常情況下對於xaml而言,屬性賦值這塊已經比較好了,但是我們發現我們的賦值都是固定的,屬於硬性編碼複製,假如我想將一個對象的值賦值給某個屬性或者將一個控制項的值賦值給另外一個控制項時這時候我們怎麼處理?這時候就要用到我們所說的標記擴展了。標記擴展是一種非常規的方式設置屬性的語法。【其實就是一種特殊的簡單賦值】
設置語法:{標記擴展類 參數}
使用花括弧括起來。比如我們給文本框的字體顏色賦值
<TextBox Text="你好,WPF!" Foreground="Blue" /> <TextBox Text="你好,WPF!" Foreground="{x:Static SystemColors.ActiveBorderBrush}" />
其實兩者都實現了給對應前景色賦值,不過一個使用了簡單屬性賦值方式一個使用了標記擴展的方式。下者是獲取了系統顏色裡邊的控制項激活時的顏色。
我們寫一個Demo吧,這個Demo是實現當我們滑動控制項時變更Label字體的大小。
1 <Slider 2 x:Name="s" 3 Maximum="20" 4 Minimum="10" /> 5 <Label Content="Demo字體大小" FontSize="{Binding ElementName=s, Path=Value}" /> 6 <TextBox Text="{Binding ElementName=s, Path=Value}" />
實現效果如圖所示【先不需要關心Binding這些後續會有專門介紹】
2.5 - 附加屬性
附加屬性,顧名思義就是附加上去的屬性。在WPF中附加屬性一般都常用語控制項佈局方面【後續有專門介紹佈局章節】附加屬性始終使用包含兩部分的命名格式:定義類型.屬性名。
附加屬性其實根本不是真正的屬性。實際上被轉為方法來調用了,最終調用的是靜態方法DefiningType.SetPropertyName()。我們寫個小Demo就能看出來附加屬性的基本使用方法了。
1 <Grid> 2 <Grid.ColumnDefinitions> 3 <ColumnDefinition Width="50" /> 4 <ColumnDefinition Width="50" /> 5 </Grid.ColumnDefinitions> 6 <Grid.RowDefinitions> 7 <RowDefinition Height="50" /> 8 <RowDefinition Height="50" /> 9 </Grid.RowDefinitions> 10 <TextBox Text="你好,WPF!" Margin="5" Foreground="Blue" Grid.Column="0" Grid.Row="0" /> 11 <TextBox Text="你好,WPF!" Margin="5" Foreground="Orange" Grid.Column="1" Grid.Row="1" /> 12 </Grid>
我們定義了兩行兩列,分別放置了兩個TextBox。其中Grid.Row=“0”這個就叫做附加屬性。附加屬性是WPF的核心要素之一。比如將Row屬性定義為附加屬性,可以確保所有控制項都可以使用他,即使我們把TextBox控制項換成Button、Radio、ComboBox等等。
3 - 事件
以上我們介紹的所有特性都被映射為了Property屬性。其實特性也可以關聯事件處理程式。比如Button按鈕標簽有個特性叫Click的閃電形狀。它就是Button類的Click事件【如果對於寫過Winform的來說並不陌生】
語法:事件名稱=“事件處理程式的方法名”
<Button Content="點擊我彈窗" Click="Button_Click"/>
在Click事件名稱上點擊F12進入後臺事件編寫上邊
1 private void Button_Click(object sender, RoutedEventArgs e) 2 { 3 MessageBox.Show("你好呀!"); 4 }
在這個事件當中我們可以寫我們對應的代碼邏輯。在WPF中事件模型和其他類型.NET應用程式事件模型不通。WPF的事件模型依賴於事件。之後詳解。
其中WPF生成對應事件名稱規則,是如果對應對象有name屬性則生成name_事件名稱比如Click,name為btn。則生成的為btn_Click。如果沒有name屬性則預設生成類名_事件名稱比如Click預設為Button_Click。
4 - 引用命名空間
一般情況下我們寫的項目不可能只有一個,當我們需要引用其他程式集或者其他模塊的時候,我們需要引用對應的命名空間。我們在上一節已經介紹過了命名空間如何引用。語法為xmlns[可省略的首碼名]:"命名空間所在位置"
。
其中當我們需要引用對應程式集時,語法為
xmlns[首碼名]:"clr-namespace:Namespace;assembly=AssemblyName"
當我們實戰項目經驗多了,就會發現這個引用命名空間其實很簡單。
以上呢就是這節介紹的xaml的一些基本語法。其中一些demo還需要自己練習。下邊進入xaml的UI學習。