WPF笔记(一)之初识XAML
WPF笔记(一)之初识XMAL
本人对Java更熟悉,所以写的时候会不自觉与Java对比
XAML原理
XAML是XML的扩展,在WPF中XAML用于绘制UI,可以理解为用XAML去定义C#的UI类,在XAML中声明了一个标签,就意味着创建了一个对象。
比如WPF项目刚创建好时,XAML为
<Window x:Class="MyWpfApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="450" Width="800">
<Grid>
</Grid>
</Window>
上面例子中有<Window>
和<Grid>
两个标签,就意味着创建了System.Windows.Window
和System.Windows.Controls.Grid
的对象,然后<Window>
标签上定义了一些属性。
一般来讲,XAML中的属性叫Attribute,对象中的属性叫Property,对象中声明的私有变量叫字段field。Java中没有属性,而是使用Get/Set方法进行了代替。
然后是标签的属性,一行一行来说。
首先是Title、Height、Width这一行,对应Window对象的标题和大小,不再多说。
然后是xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
,其中xmlns
指的是名称空间,代表引用哪些名称空间。这里值看似是一个网址,其实是硬编码到XAML编译器中的,XAML解析时看到这个网址的名称空间就会自动把一些UI相关程序集和名称空间引入进来。
接着是xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
这里xmlns:x
加了一个x
的区分,用来表示不同名称空间,加了区分之后,使用这个名称空间的内容时就需要把区分作为前缀添加的标签上。这一行引用的名称空间里面主要是一些XAML解析需要的程序集。
然后是x:Class
这一行,代表在MyWpfApp命名空间中创建一个MainWindow的类,和MainWindow.xml.cs中的MainWindow是同一个类,继承自Window。
可以在MainWindow.xml.cs中看到MainWindow使用了partial来声明,这个关键字用来在不同文件中声明同一个类,Java中没有这个功能
了解上面的信息之后,对一个最简单的XAML程序的结构就清楚了。
XAML语法基础
XAML中有两种方法给对象的属性赋值:
- 使用字符串进行简单赋值
- 使用元素属性(Property Element)进行复杂赋值
使用简单字符串进行赋值的例子:
<Rectangle x:Name="rectangle" Width="200" Height="150" Fill="Blue"/>
这是一个矩形的控件,其中有一个Fill属性用来填充内部的颜色,Rectangle类的Fill属性是Brush类型,Brush的子类很多,比如SolidColorBrush:单色笔刷。所以上面的属性Fill="Blue"
就相当于下面的C#代码
...
SolidColorBrush sBrush = new SolidColorBrush();
sBrush.Color = Color.Blue;
this.rectangle.Fill = sBrush;
...
可以看到Blue
这个字符串最终被翻译成了一个SolidColorBrush对象,并赋值给了rectangle。
那么为什么是SolidColorBrush而不是其他Brush子类呢?并且如果要设置的对象比较复杂的时候,字符串不就很复杂、很难写了吗?
使用TypeConverter类将XAML中的Attribute转换为对象的Property
对于第一个问题,字符串如何解析的逻辑由TypeConvert决定。
举个例子,现在有一个可以在XAML中声明的类Human:
public class Human
{
public string Name { get; set; }
public Human Child { get; set; }
}
想要在XAML中创建出这个类的对象,并且给Child属性赋值,Child的名字叫“ABC”:
<Window.Resources>
<local:Human x:Key="human" Child="ABC"/>
</Window.Resources>
直接这么写是编译不过去的,因为XAML解析器并不知道怎么把字符串转化为Human类型,此时就需要TypeConvert类决定如何转换:
public class StringToHumanTypeConverter: TypeConverter
{
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value is string name)
{
Human h = new Human();
h.Name = name;
return h;
}
return base.ConvertFrom(context, culture, value);
}
}
Visual Studio 中重写方法时,直接public override就会有提示(IDEA用多了总想着Ctrl+i、Ctrl+o)
然后在Human类上添加上特性:
[TypeConverter(typeof(StringToHumanTypeConverter))]
public class Human
{
public string Name { get; set; }
public Human Child { get; set; }
}
这样,XAML解析器就知道怎么把“ABC”这个字符串转化为Human对象,然后赋值给Child属性了。
C#中类上面被中括号包起来的叫特性,类似Java中的注解
使用属性元素方式进行属性赋值
XAML中,非空标签都有自己的内容(Content),内容中的子级标签都是副标签内容的一个元素(Element),那么属性元素的意思就是把一个元素当做属性。
也就是上面矩形的例子用属性元素改写一下,将会是这样:
<Rectangle x:Name="rectangle" Width="200" Height="150">
<Rectangle.Fill>
<SolidColorBrush Color="Blue"/>
</Rectangle.Fill>
</Rectangle>
<Rectangle.Fill>
标签代表要给Rectangle的Fill属性赋值,赋给的值就是标签括住的内容。
与之前的代码效果完全一致,这种写法主要用来设置属性复杂的对象。
比如给矩形设置一个渐变的颜色
<Rectangle x:Name="rectangle" Width="200" Height="150">
<Rectangle.Fill>
<LinearGradientBrush>
<LinearGradientBrush.StartPoint>
<Point X="0" Y="0"/>
</LinearGradientBrush.StartPoint>
<LinearGradientBrush.EndPoint>
<Point X="1" Y="1"/>
</LinearGradientBrush.EndPoint>
<LinearGradientBrush.GradientStops>
<GradientStopCollection>
<GradientStop Offset="0.2" Color="LightBlue"/>
<GradientStop Offset="0.7" Color="Pink"/>
<GradientStop Offset="1.0" Color="Blue"/>
</GradientStopCollection>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
可以看到这样自由度非常高。
标记扩展
大多数的属性赋值都是为属性生成一个新的对象,但是有时候我们需要把一个对象赋值给两个对象的,或者一个对象的属性依赖于其他对象的属性,这时候就要使用啊扩展标记了。
比如这里有一个文本框,以及一个滑动条,想要的效果是吧滑动条的值显示在文本框里:
<TextBox Text="{Binding ElementName=slider1, Path=Value, Mode=OneWay}"/>
<Slider x:Name="slider1" Margin="5"/>
上面的代码中,Text="{Binding ElementName=slider1, Path=Value, Mode=OneWay}"
就是标记扩展,这是XAML特有的语法:
- 当XAML编程器遇到这句代码时会把花括号的内容解析成相应的对象
- 对象的数据类型是紧邻左花括号的字符串(这里就是Binding)
- 对象的属性由一连串逗号连接的子字符串进行初始化(属性值不再加引号)
这个写法与C#3.0的对象初始化语法非常相似:
Binding binding = new Binding() { ElementName=slider1, Mode=BindingMode.OneWay };
几个小点:
标记扩展是可以嵌套的:Text={Binding Source={StaticResource myDataSource}, Path=PersonName}
标记扩展有一些简写语法,例如{Binding Value, ...}
和{Binding Paht=Value, ...}
是等价的,{StaticResource myString, ...}
和{StaticResource ResourceKey=myString, ...}
是等价的。第一种称为固定位置参数,第二种是具名参数。固定位置参数实际上就是类的构造器的位置。
导入程序集
在XAML中引入类库或其他程序集的语法是:
xmlns:映射名="clr-namespace:类库中名称空间名;assembly=类库文件名"
比如MyLibary.dll中有两个名称空间,在XAML中引用是:
xmlns:common="clr-namespace:Common;assembly=MyLibary"
xmlns:controls="clr-namespace:Controls;assembly=MyLibary"
其中:
- xmlns是XAML中用来声明名称空间的属性,是XML namespace的缩写。
xmlns:
后面的映射名是自定义的,所有引用的名称空间都要加上映射名(无映射名的默认名称空间已经被XAML占用)- 引号中的字符串决定引用那个类库的那个名称空间(Visual Studio可以自动提示)。
x名称空间
x:Class
:指定XAML编译成的类与那个类结合(该类必须声明为partial)x:Name
:为XAML中的标签对象创建引用变量(也就是起个名字,接着就可以在xaml.cs的类中使用了)x:FieldModifier
:指定x:Name
的作用域(public或者private之类的)x:Key
:在XAML中,可以把要复用的资源放在资源字典中(Resource Dictionary),然后通过key检索出来进行复用(在XAML对应的文件中也可以使用String str = this.FindResource("aStr") as String;
)。x:Type
:在C#中,Type类类似Java中的Class类,而x:Type
就是专门给这种类型的属性赋值的(有了Type类之后就可以使用反射了)。x:Null
显式的给属性赋Null值(可以去除默认值或者同一赋值时排除个别控件)。x:Array
提供一个List(一般用作静态的DataSource)x:Static
引用静态成员(字段和属性都可以)
举几个例子:x:Key举例
<Window x:Class="MyWpfApp.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:MyWpfApp"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d"
xmlns:tc="clr-namespace:MyWpfApp.TestClass"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<!-- 设定静态资源的Key -->
<sys:String x:Key="aStr">Hello WPF</sys:String>
</Window.Resources>
<Grid>
<!-- 在这里引用(如果及设置Text属性,又在标签内添加文字,运行结果是字符串拼在一块,但是在设计窗口中体现不出来) -->
<TextBlock Text="{StaticResource ResourceKey=aStr}"></TextBlock>
</Grid>
</Window>
x:Type举例
首先创建一个被反射的类:
class ClassForType : Window
{
public void SayHello()
{
MessageBox.Show("Hello");
}
}
然后创建一个自定义的Button类:
public class MyButton : Button
{
// Type属性,在XAML中被设置
public Type SetMe { get; set; }
// 重写Button的click时间
protected override void OnClick()
{
base.OnClick();
// 反射创建本地类型的对象(dll中的对象需要使用Assembly类进行反射创建)
ClassForType instance = Activator.CreateInstance(this.SetMe) as ClassForType;
instance.SayHello();
}
}
最后在XAML中给属性进行赋值:
<!-- tc是我自定义的名称空间 -->
<tc:MyButton SetMe="{x:Type TypeName=tc:ClassForType}"/>
x:Array
一个把x:Array作为数据源的ListBox举例
<ListBox>
<ListBox.ItemsSource>
<x:Array Type="sys:String">
<sys:String>Tom</sys:String>
<sys:String>Tim</sys:String>
<sys:String>Mac</sys:String>
</x:Array>
</ListBox.ItemsSource>
</ListBox>
搜了一下关于x:Array
的讲解并不是很多,看这个介绍好像用起来也不是特别方便,估计一般用来在XAML中写一些静态的值
x:Static
首先添加一个静态成员:
namespace MyWpfApp
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
// 静态字段
public static string Title = "高山月小";
// 静态只读属性
public static string AText { get { return "水落石出"; } }
...
}
}
在XAML中使用:
<Window x:Class="MyWpfApp.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:MyWpfApp"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d"
xmlns:tc="clr-namespace:MyWpfApp.TestClass"
Title="{x:Static local:MainWindow.Title}" Height="450" Width="800">
<Grid>
<TextBox Text="{x:Static local:MainWindow.AText}"/>
</Grid>
</Window>
VS2017中,XAML不能识别第一个字段,但实际运行是有效果的。对C#开发规范来说,字段公开时必须以属性形式,因此不用担心这个问题。