语言简介
- XAML(Extensible Application Markup Language) 是用来写界面的
- XAML是大小写敏感的
- WPF是前后端分离的,前端用XAML实现,后端用C#写(注意能用XAML写的都能用C#实现,但是用XAML更加直观,我们在本篇教程里,一般只说明使用XAML的实现,如果大家需要在后台更改界面,可以自行搜索XAML对应的C#实现)
命名空间
格式
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- http:// 并不是一个网址,而是十几个命名空间的集合,这样做,既保证了这个Uri不会于用户自定义的命名空间重复(因为schemas.microsoft.com是微软所有的)也降低了文档的复杂程度,不用写十几个命名空间了。
- xmlns:x 表示这个命名空间的别名是x,在使用的过程中可以通过x:Name使用这个命名空间下的对象
- presentation 是WPF核心命名空间,没有前缀,默认(像Button这种)都在这个命名空间之内
- xaml 是XAML的命名空间
不同项目中的命名空间的引用
xmlns:Prefix="clr-namespace:Namespace;assembly=AssemblyName"
注意AssemblyName是程序集名称,只要在该项目中引用过即可使用。如果就在本程序集中,则可以忽略这个选项。
// 把当前项目程序集设置为local
xmlns:local="clr-namespace:MyNamespace"
类名 部分类
x:Class="Novc.ViPlex.Express.View.AboutView"
这个声明使得XAML生成了一个部分类,与后台的部分类一起构成了完整的窗体类定义
XAML加载
InitializeComponent方法
- 这个方法的作用是加载XAML并且构建用户界面
- 当自定义构造函数的时候必须写上这个方法
事件和属性
XAML加载的顺序是先设置Name属性,再给元素挂载事件,最后加载属性。所以,如果监听了某个属性的变化的事件,在第一次设置该属性的时候(加载属性的时候),事件都会触发一次。
属性的类型转换器(TypeConverter)
在XAML中,属性的设置全部是字符串类型的,但是属性的类型却是多种多样的,比如Image的Source是一个BitmapImage类型,VerticalAlignment是枚举类型。为了解决这个问题,.NET引入了类型转换器的概念
类型转换器存在于
- 需要设置的属性中,比如 Background 属性
- 属性设置的值中,比如Background需要使用一个Brush对象,XAML也会检测Brush中是否有类型转换器
属性的设置
-
直接设置属性(使用类型转换器)
HorizontalAlignment="Left"
-
设置复杂属性
属性元素语法,语法规则为Parent.Property,用于设置一个复杂的,对象类型的属性值。<Grid HorizontalAlignment="Left" Height="100" Margin="196.617,195.39,0,0" VerticalAlignment="Top" Width="118.795"> <Grid.Background> <LinearGradientBrush> <GradientStop Offset="0.00" Color="Red"></GradientStop> <GradientStop Offset="0.50" Color="Indigo"></GradientStop> <GradientStop Offset="1.00" Color="Violet"></GradientStop> </LinearGradientBrush> </Grid.Background> </Grid>
-
使用标记扩展设置属性(不太常用,知道即可)
当我们要给一个属性设置为一个已经存在的属性的时候使用,例如,A类中有一个静态的Brush,B.xaml中需要使用A中的Brush,需要注意的是,A中的Brush必须是静态的,即通过类名就可以访问。
<Button Foreground="{x:Static SystemColors.ActiveCaptionBrush}"/>
注意上面的语法,Static是StaticExtension的简写,即静态扩展,在后台中这样写
cmdAnswer.Foreground = SystemColors.ActiveCaptionBrush;
作为嵌套属性这样写,注意需要使用Member:
<Button.Foreground> <x:Static Member="SystemColors.ActiveCaptionBrush"/> </Button.Foreground>
-
设置附加属性
简单说,附加属性就是一些属性只有在某种特性情况下才能被设置,比如控件在Grid中的时候才能设置Grid.Row。
这里我们多分析两句,想必设置Grid.Row人人都会,但是为什么它叫附加属性呢?因为TextBox中本来没有这个属性,Background,TextAlignment这种都属于控件的固有属性,而Grid.Row这种是添加进去的。这个属性不在FrameworkElement这样的基类中,而在DependencyObject中有一个字典,这个字典中保存所有扩展属性的Key和Value,因为保存它的是一个字典,所以它可以扩展,那么设置Grid.Row就等价于下面的代码
txtQuestion.SetValue(Grid.RowProperty, 1)
为什么不写在基类中,要写成这样呢?很简单,因为不是所有的组件都需要这个属性,只有在Grid中的组件有可能需要设置这个属性,所以,做成可扩展的就不扰乱基类,也可以方便扩展。
元素树
我们知道元素是以嵌套的方式进行构建的,构建的方式是:
- 如果父元素实现了IList接口,就调用IList.Add
- 如果父元素实现了IDictionary接口,就调用IDictionary.Add
- 如果父元素使用了ContentProperty修饰,子元素设置对应的属性
我们最需要关注的就是ContentProperty属性,因为ContentControl,ItemsControl,Panel基类都使用ContentProperty特性,这就是说几乎所有的控件都使用ContentProperty,而IList接口的多是属性等,在设置属性的时候可以设置一个集合。
对于Grid中加入各种元素的情况,并不是因为Grid是IList的集合,Grid支持的是ContentProperty,而能加入很多元素是因为Grid继承自Panel,Grid使用Panel渲染ContentProperty属性,实现可以添加多个元素。Panel有一个Children属性是一个集合,添加元素就是往Children中加入元素,看以下代码
txtQuestion = new TextBox();
grid.Children.Add(txtQuestion);
那什么父元素是IList的呢?之前就说过了,比如之前的GradientStopCollection,里面可以添加很多个GradientStop
对于Grid我们说过了,那对于简单元素,就更简单了。TextBox的ContentProperty渲染的是Text属性,Button的ContentProperty渲染的是Content属性,这里我们要讨论以下Text属性和Content属性的区别了。
Text 属性和 Content属性
当我们指定控件中的文字的时候,我们发现有一些控件需要指定Text属性,有一些控件却是要指定Content属性,那么这两个属性有什么区别呢?其实区别很简单,Text属性只能接受string类型的值,那么控件中就只能添加文字,Content属性就比较丰富多彩,可以放各种各样的东西。看下面的例子说明一下。
<Button>
<Button.Content>
<Image Source="/Image/1.jpg"></Image>
</Button.Content>
</Button>
实体字符集
< > " & 这些作为XAML的关键字,不能在XAML中直接显示,当我们要显示这些字符的时候需要用到实体字符标记。
<Button>
<Click>
</Button>
// 显示出来的是<Click>
注意在代码中设置Content不用遵循这个规则,而是需要遵循C#的转义规则。
空白处理
默认
折叠所有空白(打多少空格都只显示一个),如果想要显示所有的空格,有下面三种办法
- 在代码中设置的空格多少个都会被保留
- 使用 xml:space=“preserve” 属性
- 在属性中设置的多少个空格都会被保留
// 空格会保留
<TextBlock Text="[1 3 5 7]" />
// 空格会保留
<TextBox xml:space="preserve">
1 2 3 4
</TextBox>
// 空格不会保留
<TextBox>
1 2 3 4
</TextBox>
使用代码控制XAML
// 加载XAML XamlReader
using(FileStream fs = new FileStream(xamlFile. FileMode.Open))
{
rootElement = (DependencyObject)XamlReader.Load(fs);
}
// 查找元素方法1
button1 = (Button)LogicalTreeHelper.FindLogicalNode(rootElement, "button1");
// 查找元素方法2
FrameworkElement frameworkElement = (FrameworkElement)rootElement;
button1 = (Button)frameworkElement.FindName("button1");
注意事项
- 在XAML中加载的类,最好都能有一个无参数的构造函数,这样的类适合XAML的加载