第一章 XAML概览
1.1 XAML是什么
WPF出现前设计师和程序员之间的协作关系:
- 需求结束后程序员对照需求设计一个用户界面(User Interface UI)的草图,然后集中精力实现软件功能。
- 设计师对照需求、考虑用户使用体验(User Experience UX)使用特定的设计工具(如PhotoShop)设计出优美而实用的UI。
- 最后,程序员按照设计师绘制的效果图,使用编程语言实现软件完整的UI。
经验告诉我们,即便是优秀的设计师团队和优秀的开发团队合作,花费在沟通和最终整合上的精力也是巨大的。经常出现的问题有:
- 设计师的设计跟不上程序逻辑的变化
- 程序员未能完全实现设计师提供的效果图
- 效果图与程序功能不能完全匹配
- 从效果图到软件UI的转化花费过多的时间
问题的核心在于:设计师与程序员的合作是“串行”的。只要存在分工,合作的成本就不可能为零。如果设计师和程序员“并行”工作并直接参与到程序的开发中来,问题就能够解决。
解决的方案:借鉴网络程序开发经验(基于HTML、CSS和JSP直接生成UI,程序员在这个UI产生的同时实现背后的功能逻辑),微软创造了XAML。(设计师使用Blend设计UI,程序员使用VS开发后台逻辑代码,两者使用XAML沟通交流)
1.2 XAML的优点:
- XAML可以设计出专业的UI和动画----好用
- XAML不需要专业的编程知识----易学
- XAML使设计师能直接参与软件开发,随时沟通,无需二次转化----高效
- XAML帮助开发团队实现UI与逻辑的剥离,实现UI和逻辑解耦----重点
UI代码与逻辑diamagnetic紧耦合带来的缺点:
- 无论软件功能还是UI设计有所变化或者出bug,都将导致大量代码的修改
- 会让逻辑代码更加难以理解,导致修改比重写更困难(因为修改前需要先读懂),导致代码维护越来越困难
- 无法实现逻辑代码的重用
第二章 从零起步认识XAML
2.1 新建WPF项目 ### 略
2.2 剖析最简单的XAML代码
XAML是XML派生的
为了表示同类标签中的某个标签与众不同,可以给它的特征(Attribute)赋值,赋值语法如下:
- 非空标签:
Content - 空标签:
Property和Attribute的区别
Property属于面向对象理论范畴,类中用来表示事物状态的成员就是Property。
Attribute则是编程语言文法层面的东西,Attribute只与语言层面上的东西相关,与抽象出来的对象没什么关系。
xmlns是XML-Namespace的缩写,语法为:
xmlns[:可选的映射前缀]=”名称空间”
xmlns后面可以跟一个可选的映射前缀,用冒号分隔。如果没有使用可选的映射前缀,那就意味着所有来自这个名称控件的标签都不用加前缀,这个没有映射前缀的名称空间称为”默认名称空间”----默认名称空间只能有一个。
XAML中引用外来程序集和其中.NET名称空间语法与C#不一样。如果要添加System.Windows.Controls名称空间里的Button类,在XAML中首先需要添加对指定程序集的引用,然后在根元素的起始标签中添加如下代码:xmlns:c=”clr-namespace:System.Windows.Controls;assembly=PresentationFramework”。
c是映射前缀,换成其他字符也是可以的。因为这里Button来自前缀c对应的名称空间,所以使用Button的时候要写成
第三章 系统学习XAML语法
3.1 XAML文档的树形结构
在用户眼中UI是一个平面的结构。
在传统的VC++、Delphi、VB或者WinForm程序员的思维中,UI也是一个平面结构。
XAML用树形逻辑结构来描述UI。
WPF基本类库为程序员准备了VisualTreeHelper和LogicalTreeHelper两个助手类。
选择哪种方法实现UI的依据:
设计师在XAML上给出UI的布局是软件的一个静态快照(Static Snap),这个静态快照需要加上用户有可能执行的动态操作才能构成选择布局实现方法的完整依据。
3.2 XAML中为对象属性赋值的语法
XAML中为对象属性赋值共有两种语法:
- 使用字符串进行简单赋值
- 使用属性元素(Prperty Element进行复杂赋值)
3.2.1 使用标签的Attribute为对象属性赋值
使用Attribute=Value语法赋值的时候,由于XAML的语法限制,Value只可能是一个字符串,这就引出了两个问题:
- 如果一个类能使用XAML语言进行声明,并允许它的property与XAML标签的Attribute互相映射,那就需要为这些Property准备适当的转换机制。
- 由于Value是一个字符串,所有其格式复杂程度有限,尽管可以在转换机制里包含一定的按格式解析字符串的功能以便转换成较复杂的目标对象,但这会让最终的XAML使用者头痛不已。因为他们不得不在没有编码辅助的情况下手写一个格式复杂的字符串以满足赋值要求。
第一个问题的解决方案是使用TypeConverter类的派生类,在派生类里重写TypeConverter的一些方法,第二个问题解决办法是使用属性元素(Property Element)。
3.2.2 使用TypeConverter类将XAML标签的Attribute与对象的Property进行映射
略 见书P19
3.2.3 属性元素
- XAML中,非空标签都有自己的内容(Content)。
- 标签的内容指的是夹在起始标签和结束标签之间的一些子级标签,每个子级标签都是父级标签内容的一个元素(Element),简称父级标签的一个元素。
- 属性元素指的是某个标签的一个元素对应这个标签的一个属性,即以元素的形式来表达一个实例的属性。
简化XAML技巧:
- 能使用Attribute=Value形式赋值的就不使用属性元素
- 充分利用默认值,去除冗余。
- 充分利用XAML的简写方式。
3.2.4 标记扩展
标记扩展:所谓标记扩展,实际上是一种特殊的Attribute=Value语法,其特殊地方在于Value字符串是由一对花括号及其括起来的内容组成,XAML编译器会对这样的内容做出解析、生成相应的对象。
例子见P25
Text=”{Binding ElementName=slider1, Path=Value, Mode=OneWay}”分析如下:
- 当编译器看到这句代码时就会把花括号里的内容解析成相对应的对象
- 对象的数据类型名称是紧邻左花括号的字符串
- 对象的属性由一串以逗号连接的字符串负责初始化,对象的属性值不再加引号
只有MarkupExtension类的派生类(间接或者直接都可)才能使用标记扩展语法来创造对象
3.3 事件处理器与代码后置
XAML标签对应一个对象时,标签一部分Attribute会对应对象的Property,另外一部分Attribute对应对象的事件(Event)。
事件处理器(Event Handler):为对象的某个事件指定一个能与该事件匹配的成员函数,当这个事件发生时,.Net在运行时会去调用这个函数,即标识对这个事件的响应和处理。
WPF支持在XAML里为对象的事件指定事件处理器,方法是使用事件处理器的函数名为对应对象事件的Attribute进行赋值。格式为:
代码后置(Code-Behind):标识UI的XAML文件和C#代码分别表示前台和后台,前台和后台用事件Attribute来沟通链接。
]]>
可以用此关键字把C#代码嵌入到XAML文件中(不推荐使用)
3.4 导入程序集和引用其中的名称空间
.NET的模块称为程序集(Assembly)
一个解决方案是一个完成的程序,解决方案中会包含若干项目(Project),每个项目是可以独立编译的,它的编译结果就是一个程序集。
XAML引用名称空间的语法:
- xmlns是用于在XMAL中声明名称空间的Attribute,它从XML语法继承而来,是XML-Namespace的缩写
- 冒号后面的映射名是可选的,但由于可以不加映射名的默认名称空间已经被WPF的主要名称空间占用,所以所引用的名称空间都需要加上这个映射名,映射名可以根据喜好自由选择
- 引号中的字符串值确定了你要引用的是哪个类库及类库中的哪个名称空间。
- 使用引用的类格式:<映射名:类名>……映射名:类名>
3.5 XAML的注释
第四章 x名称空间详解
x名称空间的x是映射XAML名称空间时给它取得名字;x名称空间里的成员是专门写给XAML编译器看,用来引导XAML编译器把XAML代码编译成CLR代码的。
4.1 x名称空间里都有什么
x名称空间映射一般为 http://schemas.microsoft.com/winfx/2006/xaml
XAML也有自己的编译器,XAML语言会被解析并编译,最终形成微软中间语言存储在程序集中
x名称空间中包含的工具列表见P31
x名称空间中的Attribute
4.2.1 x:Class
这个Attribute作用是告诉XAML编译器将XAML标签的编译结果与后台代码中指定的类合并,在使用x:Class时必须遵循以下要求:
- 这个Attribute智能用于根节点
- 使用x:Class的根节点的类型要与x:Class的值所指示的类型保持一致
- x:Class的值所指示的类型在声明时必须使用partial关键字
- x:Class已经在剖析最简单的WPF程序时讲过,此处不在赘述
4.2.2 x:ClassModifier
这个Attribute作用是告诉XAML编译由标签生成的类具有怎么样的访问控制级别
使用x:ClassModifier时需要注意事项:
- 标签必须具有 x:Class Attribute
- x:ClassModifier的值必须与x:Class所指示类的访问控制级别一致
- x:ClassModifier的值随后台代码的编译语言不同而有所不同,具体参看TypeAttributes枚举类型
4.2.3 x:Name
XAML标签是对象,一个XAML变迁对应着一个对象,这个对象一般是一个控件的实例。
不带名称的XAML对象声明只负责声明对象而不负责为这些对象声明引用变量。
如果我们需要为对象准备一个引用变量以便在C#代码中直接访问就必须显示告诉XAML编译器,为这个对象声明引用变量就要用到x:Name
x:Name的作用有两个:
- 告诉XAML编译器,当一个标签带有x:Name时除了为这个标签生成对应实例以外还要为这个实例声明一个引用变量,变量名就是x:Name的值
- 将XAML标签所对应对象的Name属性(如果有)也设置为x:Name的值,并把这个值注册到UI树上以方便查找
问题:在XAML代码中应该使用Name还是x:Name
Name属性定义在FrameworkElement类中,这个类是WPF控件类的基类,所有的WPF控件都有Name这个属性。当一个元素具有Name属性时,使用Name或者x:Name效果是一样的。对于那些没有Name属性值的元素,为了在XAML声明时也创建引用变量以便在C#代码中访问,我们只能使用x:Name。
4.2.4 x:FieldModifier
x:FieldModifier用来在XAML里改变引用变量访问级别
使用x:Name后XAML标签对应实例具有自己的引用变量,而且这些引用变量都是类的字段。默认情况下,这些字段的访问级别按照面向对象的封装原则被设置成internal。有时候我们需要从一个程序集访问另外一个程序集中窗体的元素,这个时候需要把访问控件的引用变量改为public级别。
注意:因为x:FieldMOdifier是用来改变引用变量访问级别的,所有使用x:FieldModifer的前提是这个标签同时使用x:Name,否则无法通过Name来引用变量。
4.2.5 x:Key
在XAML文件中,我们可以把需要多次使用的内容提取出来放在资源字典(Resource Dicitonary)里,需用使用这个资源的时候用Key把它检索出来,x:Key的作用就是为资源贴上用于检索的索引。
Resources在WPF中非常重要,需要重复使用的XAML内容,例如:Style、各种Template和动画等都需要放在资源里。
4.2.6 x:Shared
x:Shared一定要与x:Key配合使用,如果x:Shared为true,那么每次检索到这个对象时,我们得到的都是同一个对象,否则得到的是这个对象的一个新副本,默认XAML会为资源隐式定义为true。
4.3 x名称空间中的标记扩展
x:Type的值应该是一个数据类型名称
在XAML中想表达某个数据类型时就需要使用x:Type标记扩展
代码示例见书P39
4.3.2 x:Null
显示的对一个属性赋予空值
4.3.3 标记扩展实例的两种声明语法
标记扩展也是标准的.NET类,所以我们也可以使用XAML的标签来声明标记扩展的实例。
代码示例:
第一种表示法:
第二种表示法:
4.3.4 x:Array
x:Array的作用是通过它的Item属性向使用者暴露一个类型已知的ArrayList实例。
ArrayList内成员类型由x:Array的Type指明。
WPF中把包含数据的对象称为数据源(Data Source)
把一个x:Array实例作为数据源提供给一个ListBox示例代码:
添加引用:
xmlns:sys="clr-namespace:System;assembly=mscorlib"
代码:
Tim
Tom
Victor
例子具体内容见P43
4.3.5 x:Static
x:Static的功能是在XAML文档中使用数据类型的static成员
例子见P44
如果一个程序需要国际化支持,一般会把需要显示的字符串保存在一个资源类的static属性中,所以支持国际化程序的UI中对x:Static的使用非常频繁
4.4 XAML 指令元素
XAML指令元素有两个:
- x:Code
- x:XData
x:Code的作用是在XAML中嵌入C#代码,一般不会使用
x:XData是一个专用标签
WPF中把包含数据的对象称为数据源
用于把数据源中的数据提供给数据使用者的对象称为数据提供者(Data Provider)
XmlDataProvider用于提供XML化的数据
如果想要在XAML里声明一个带有数据的XmlDataProvider实例,那么XmlDataProvider实例的数据要放在x:XData标签的内容里。
代码示例见P64
第五章 控件与布局
5.1 控件到底是什么
WPF能够称得上是新一代工具的关键点:
- 之前几代GUI方法论只能使用编程语言进行UI设计,而WPF具有专门用于UI设计的XAML。
- 前几代在UI与数据的交互方面是由Windows消息到控件事件一脉相承,始终把UI控件放在主导地位而把数据放在被动地位,用UI来驱动数据的改变。WPF在事件驱动的基础上引入了数据驱动界面的理念,让数据重归核心地位,UI回归到数据表达者的位置。
核心观念:WPF中是数据驱动UI,数据是核心、是主动的。UI从属于数据并表达数据,UI是被动的。
UI的功能是让用户观察和操作数据,为了让用户观察数据:
- 我们需要用UI元素来显示数据
- 为了让用户操作数据,需要用UI元素响应用户的操作
WPF把那些能够展示数据、响应用户操作的UI元素称为控件(Control)
控件所展示的数据,我们称为控件的“数据内容”
控件在响应用户的操作后会执行自己的一些方法或以事件(Event)的形式通知应用程序,我们称之为控件的“行为”或“算法内容”
WPF中控件扮演双重角色:Control是数据和行为的载体而无需具有固定的形象。
控件的数据和行为有控件自身来定义,控件的样子(或者叫做呈现形式)由它的Style和Template来决定。
常见控件:
- 布局控件,共同父类Panel
- 内容控件,共同父类ContentControl
- 带标题内容控件:相当于内容控件,但是有一个标题(Header),共同父类HeaderedContentControl
- 条目控件:共同基类ItemsControl
- 带标题条目控件:相当于一个条目控件加上一个标题显示区,共同父类HeaderedItemsControl
- 特殊内容控件
5.2 WPF的内容模型
把控件想象成一个容器,容器里面装的东西就是它的内容。控件的内容可以直接是数据,也可以是控件,当控件的内容还是控件的时候就形成了控件的嵌套。所以WPF的UI会形成一个树状结构。
如果不考虑空间内部的组成结构,只观察由控件组成的“树”,那么这棵树称为逻辑树(Logical Tree)
如果连控件本身的树叶考虑在内,则称为可视元素树(Visual Tree)
控件是内存中的对象,控件的内容也是内存中的对象,控件通过自己的某个属性引用着作为其内容的对象,这个属性称为内容属性(Content Property)。
“内容属性”是个统称,具体到每种控件上,内容属性都有自己确切的名字----有的叫做Content有的叫做Child,有些控件的内容是集合,其内容属性叫做Items或Children。
控件的内容属性与XAML标签的内容存在一定的对应关系。
5.3 各类内容模型详解
把符合某类内容模型的UI元素称为一个族,每个族用它们共同基类来命名
5.3.1 ContentControl族
特点:
- 均派生自ContentControl类
- 它们都是控件(Control)
- 内容属性的名称为Content
- 只能由单一元素充当其内容
5.3.2 HeaderedContentControl族
特点:
- 它们都派生自HeaderedContentControl类,HeaderedContentControl是ContentControl类的派生类
- 它们都是控件,用于显示带标题的数据
- 除了用于显示主体内容的区域外,控件还具有一个显示标题(Header)的区域
- 内容属性为Content和Header
- 无论是Content还是Header都只能容纳一个元素作为其内容
5.3.3 ItemsControl族
特点:
- 均派生自ItemsControl类
- 它们都是控件,用于显示列表化的数据
- 内容属性为Items或ItemsSource
- 每种ItemsControl都对应有自己的条目容器(Item Container)
本类控件有特色的一点就是会自动使用条目容器对提交给它的内容进行包装。合法的ItemsControl内容一定是一个集合,当我们把这个集合作为内容提交给ItemsControl时,ItemsControl不会把这个集合直接拿来用,而是使用自己对应的条目容器把集合中的条目逐个包装,然后再把包装好的条目序列当作自己的内容。这种自动包装的好处就是允许程序员向ItemsControl提交各种数据类型的集合,如果需要进行增加、删除、更新或者排序,那么直接去操作数据集合就可以了,UI会自动将改变呈现出来。
5.3.4 HeaderedItemsControl族
特点
- 均派生自HeaderedItemsControl类
- 它们都是控件,用于显示列表化数据,同时可以显示一个标题
- 内容属性为Items、ItemsSource和Header
5.3.5 Decorator族
本类元素是在UI上起装饰效果的,例如可以使用Border元素为一些组织在一起的元素加个边框,如果需要组织在一起的内容能够自由缩放,则可以使用ViewBox元素
特点
- 均派生自Decorator类
- 起UI装饰作用
- 内容属性为Child
- 只能由单一元素充当内容
5.3.6 TextBlock和TextBox
这两个控件主要功能是显示文本
TextBlock只能显示文本,不能编辑,又称静态文本,具有丰富的印刷级格式控制标记,能够显示专业的排版效果
TextBox允许用户编辑其中内容
TextBox不需要太多格式显示,所以它的内容是简单字符串,内容属性为Text
TextBlock需要操作格式,所以内容属性是Inlines,同时TextBlock也保留一个名为Text的属性
5.3.7 Shape族元素
Shape族元素专门用来在UI上绘制图形,没有自己的内容,使用Fill属性为它们设置填充效果,使用Stroke属性为它们设置边线的效果。
特点:
- 均派生自Shape类
- 用于2D图形绘制
- 无内容属性
- 使用Fill属性设置填充,使用Stroke属性设置边线
5.3.8 Panel族元素
所有用于UI布局的元素都属于这一族
特点:
- 派生自Panel抽象类
- 主要功能是控制UI布局
- 内容属性为Children
- 内容可以是多个元素,Panel元素将控制它们的布局
5.4 UI布局
WPF作为专门的用户界面技术,布局功能是它的核心功能之一。
5.4.1 布局元素
传统WinForm布局一般把窗体或者页面做一个以左上角为原点的坐标系统,窗体或者页面都依靠这个坐标系统来布局。控件和控件之间的关系要么相邻要么叠加。
WPF控件有个Content概念:控件和控件间多出一种关系--包含。这种以窗体为根的包含关系,整个WPF的UI系统形成一个树形结构,我们称之为可视化树(Visual Tree)。
Content只能接收一个元素作为自己的Content,如果想在这些控件中包含多个控件需要使用布局元素,形成布局元素和控件的嵌套。
** WPF中的布局元素有如下几个: **
Grid 可以自定义行和列并通过行列的数量、行高、列宽来调整控件的布局。
StackPanel:栈式面板,可将包含的元素在竖列或者水平方向上排成一条直线,当移除一个元素后,后面的元素会自动移动填充空缺。
Canvas:内部元素可以使用以像素为单位的绝对坐标进行定位。
DockPanel:泊靠式面板,内部元素可以选择泊靠方向,类似在WinForm中设置控件的Dock属性。
WrapPanel:自动折行面板,内部元素排满一行后自动折行。
5.4.2 Grid
Grid元素会以网格的形式对内容元素进行布局。
Grid的特点如下:
- 可以定义任意数量的行和列,非常灵活
- 行高和列宽可以使用绝对值、相对比例或自动调整的方式进行精确设定,并可以设置最大和最小值
- 内部元素可以设置自己的所在行和列,还可以设置自己纵向跨几行、横向跨几列
- 可以设置Children元素的对齐方向
Grid适用场合:
- UI布局的大框架设计
- 大量UI元素需要成行或者成列对齐的情况
- UI整体尺寸改变时,元素需要保持固有的高度和宽度比例
- UI后期可能有较大变更或者扩展
** 定义Grid的行与列 **
定义列:ClolumnDefinitions
定义行:RowDefinitions
行高和列宽的单位:
- 默认单位 像素 后缀:px
- 英寸 后缀:in 1in = 96px
- 厘米 后缀:cm 1cm = (96/2.54)px
- 点 后缀: pt 1pt = (96/72)px
行高和列宽的三类值:
- 绝对值:数值后面加单位后缀
- 比例值:数值后面加一个星号
- 自动值:设置成 Auto
** 使用Grid进行布局 **
P81