WPF学习笔记02-XAML语法
上一章我们对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学习。