在实际的WPF开发中遇到很多再用Winform写法来写WPF的开发人员,很多时候项目进度延期、出现非必要的BUG等等、大多是因为开发人员虽然是再写WPF。
但是没有好好的学过WPF,就导致无法发挥出WPF的优势、很多地方都是开个线程处理完成后一个回调UI线程函数里面套一整段代码,这种情况下不仅难处理多线程问题、也会出现很多偶发性的BUG,不好追踪问题在哪里。同时也会因为开发人员对WPF的理解参差不齐导致架构设计和使用过程中代码凌乱。所以今天开始hello world 来梳理WPF。
我们通过hello world理解以下几个关键内容来整理XAML相关的知识点:
一、使用XAML创建可以显示hello world的文本框。
二、理解XAML中的文法规范级命名空间
三、XAML中的的属性和事件
四、从生成过程日志查看XAML的编译。
一、使用XAML创建可以显示hello world的文本框
经常写winform窗体的同学一定深有感触、拿到UI给的图之后、我们如果想做的比较漂亮。大部分都是通过拖控件、然后把用户界面的后台代码中设置动画效果、大小、位置等等。如果需要适配多种分辨率是更难受的。特别是一些需要深度定制的东西、可能很多开发时间都会用在这里。解决一些稀奇古怪被测试出来的的显示问题。
而在WPF中提出了XAML的概念、我们可以使用XAML标签来设计和构成我们的用户界面。而XAML丰富的标签一旦正确的使用,则会大大提高我们写出更漂亮的界面的能力。
只写文字大家都会反感。我们通过讲解例子来认识XAML。首先创建一个WPF工程。
1、写下第一行Hello world
打开VS选择创建WPF项目,点击下一步。
设置项目名称为Hello World点击创建。就创建了我们第一个WPF项目。
通过解决方案标签项我们可以看到整个解决方案的目录结构。而项目中的MainWindow就是我们本次分析的重点。
我们这次的重点在MainWindow.xaml和其对应的cs,所以其他部分不是本次的关注重点,也就不讲了。
首先双击MainWindow.xaml,在Grid标签中添加<TextBox Text="Hello world!"/>节点。然后在VS中按下F5键。程序就会执行编译,如果成功的话就会跑起来,并且看到我们输入的Hello world!。
<Window x:Class="HelloWorld.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:HelloWorld"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<TextBox Text="Hello world!"/>
</Grid>
</Window>
从我们看到这个显示出来的hello world!的这一刻开始,就正式开始了本章内容的讲解。
二、理解XAML中的文法规范级命名空间
1 <Window x:Class="HelloWorld.MainWindow"
2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4 xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
5 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
6 xmlns:local="clr-namespace:HelloWorld"
7 mc:Ignorable="d"
8 Title="MainWindow" Height="450" Width="800">
9 <Grid>
10 <TextBox Text="Hello world!"/>
11 </Grid>
12 </Window>
为了方便理解,我加入了行号,如果想系统的学习XAML可以先学习一下XML,XAML是遵循XML文法规范的,但是对于我们入门来说了解嵌套、 属性的赋值和设置,就可以了。
我们的整体XAML结构大致为
<Window>
<Grid>
<TextBox/>
</Grid>
</Window>
我们当前的XAML根节点是一个Window节点,Window节点代表当前所显示的整个窗口、在Window节点内部有一个Grid节点,Grid节点是一个布局元素里面可以放置的所有的控件、Grid节点包含了一个内部的TextBox节点,而这个TextBox就是我们所说的控件,而我们看到的显示的内容是在Text=“”内赋值的。对于XAML中的内容。可以理解为把后台的.cs代码文件通过xaml可视化的方式进行设置,如果有不知道含义的地方,可以用鼠标点在对应的节点上然后按F12就可以跳转到对应的定义中。
比如我们把光标点在Grid上。按下F12。就会跳转到下图的位置。原来Grid继承自一个Panel。里面包含了Column、Row等等。我们就知道了。Grid是一个拥有横、列的网格面板。
而在TextBox上按F12之后,就会看到这是一个控件、显示或编辑文本的。
其他的元素同样的道理。把鼠标放置在对应的元素节点上按下F12就会显示对应的内容。
接下来我们来看命名空间。
名称空间看上去好像是一个URI的WEB位置,但实际上不是,URI格式的命名空间,不同组织就基本不会使用相同的名称空间创建不同的基于XAML的语言。
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 是WPF核心名称空间。 它包含了所有WPF类,包括创建界面的控件。 该名称空间没有使用前缀、所以它成为整个文档的默认命名空间。也就是说除非指明了名称空间,否则每个元素都直接使用这个名称空间。 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 是XAML名称空间,它包含各种XAML的特性,该命名空间被映射为前缀x。可以在元素名称之前放置x 来使用该名称空间。例如x:Name,x:Class="HelloWorld.MainWindow"。 xmlns:d="http://schemas.microsoft.com/expression/blend/2008"是旨在提供设计师支持,该名称空间使XAML元素上的这些属性仅影响XAML行为的设计方面。 而在运行过程中会忽略掉这些属性。比如d:DesignHeight="100" d:DesignWidth="100",这个在设计时看不到该控件时,在对应的控件上添加此属性特别有效。 在Blend下使用比较多的是针对集合的d:DataContext、d:DesignData。等等。感兴趣的可以看看Blend相关的。 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 标识使运行时XAML解析器能够忽略设计属性,比如mc:Ignorable:d。d前缀的在运行时XAML解析器解析时就会忽略了。 xmlns:local="clr-namespace:HelloWorld" 这个就是我们工程自己的名称空间了。 x:Class="HelloWorld.MainWindow" x:Class特性是告诉XAML解析器用指定的名称生成一个新类,该类继承自XAML元素命名的类,也就是我们的根节点Window。
三、理解XAML中的的属性和事件
XAML中的属性:
我们通过前面的讲解了通过跳转查看XAML节点的含义,比如<Grid>是布局用的Panel。<TextBox/>是显示和编辑无格式文本的。 那么现在我们来理解属性。 在元素中我们通过跳转了解到了XAML节点都是一个一个的类对象。里面定义了属性、方法及事件。那么在XAML中,我们可以在这里设置属性、事件。通过设置这些来修改样式及监听对应的事件消息。 那么在属性中分为简单属性和复杂属性。我们在Textbox上按F12跳转过去,然后我们找个属性来演示一下简单属性和复杂属性如何使用。
在Foreground上我们看到了该属性是一个Brush类型。而在WPF中Brush支持纯色、线性渐变色等等。我们设置代码如下。
这样我们就在第一个TextBox 上设置了背景色为一个枚举值。用于表示一个颜色值。 而当我们想设置复杂Brush的时候,我们就可以使用复杂属性,第二个TextBox通过属性的嵌套来创建一个复杂的线性画刷。2个TextBox显示效果如下:
但是新的问题又来了,现在大部分主流的App都支持更换皮肤,明暗主题什么的。而我们只能通过硬编码的形式设置属性,那岂不是要实现更换皮肤,每个控件都要设置一遍?
有没有什么办法能让XAML属性绑定一个变量,而我们去修改这个变量?
实现方法的办法之一就是使用扩展标记:假设我们的内容都放在一个windows里做演示:
添加window的资源。同时使用DynamicResource来绑定资源。就可以实现。一个资源修改,所有使用的地方都跟着变化。后面会详细讲到。如何使用扩展标记来进行主题切换。
而在WPF中提出的是依赖项属性的概念。这个和路由事件需要花费精力好好学一下。才能更好的使用和理解WPF。
XAML中的事件:
为了增强事件的传播能力,WPF中提出了路由事件的概念,路由事件可以在元素树中向上和向下传播,并且沿着传播路径被事件处理程序消费。我们在XAML中添加2个处理按下和抬起事件的代码。
后台的处理代码。
路由事件和依赖项属性这些主要会放在MVVM中讲。这里主要了解一下。XAML下如何创建事件处理程序。
四、从生成过程日志查看XAML的编译。
对于加载和编译XAML,有四种方式:
1、只使用代码来创建XAML对应的元素,类似于winform的写法;
2、使用代码和未经编译的XAML。
3、使用代码和编译过的XAML。
4、只使用XAML。
这里我们只去理解一个使用代码和编译过的XAML。其他的工作到目前未知,觉得用到的比较少,等真正用到了。只要理解了可以在去查如何使用。
当我们编译WPF程序时,编译过程分为了2个阶段,第一个个阶段是将XAML文件编译为BAML文件。我们使用的C#语言,所以会在临时文件生成对应的.g.cs文件
g代表generated。
当从XAML到BAML的编译结束后,编译器会编译代码和生成的部分类文件。编译过的代码会变成单个程序集。每个窗口的BAML都会作为独立资源被嵌入到程序集中。
我们通过修改VS的设置来查看编译过程。
在VS选择工具=>选项=>项目和解决方案=>生成并运行=>MSBuild项目生成输出详细程序=>设置为详细。而后点击编译。我们来观察输出的内容。
我们查看关键的几处
以上截图就是完整的编译过程。