打破砂锅问-WPF原理-02-WPF中的XAML
@TOC
前言
做项目的时候,用到了WPF,总结下来,供以后参考,与诸君共勉。
一、XAML (Extensible Application Markup Language)
1.Extensible Application Markup Language (XAML) 是一种用于声明性应用程序编程的标记语言:
- Windows Presentation Foundation (WPF) 实现 XAML 处理器实现并提供 XAML 语言支持,怎么理解呢,其实呀:
- XAML 根元素和 XAML 命名空间 XAML root element and XAML namespace
- 一个 XAML 文件只能有一个根元素,这样才能同时作为格式正确的 XML 文件和有效的 XAML 文件。
- WPF 页的典型 XAML 文件的根元素,此根元素为 Page
1
2
3
4<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
</Page> - 根元素还包含特性 xmlns 和 xmlns:x
- 只有在每个 XAML 文件的根元素上,xmlns 特性才是绝对必需的。 xmlns 定义将适用于根元素的所有子代元素(此行为也符合 xmlns 的 XML 1.0 规范)
- 对于大多数 WPF 应用程序方案以及 SDK 的 WPF 部分中给出的几乎所有示例,默认的 XAML 命名空间均映射到 WPF 命名空间 http://schemas.microsoft.com/winfx/2006/xaml/presentation
- XAML 名称范围与 XML 名称范围的不同仅在于:XAML 名称范围还包含有关进行类型解析和分析 XAML 时名称范围的元素如何受类型支持的信息。
- xmlns:x 特性指示另一个 XAML 命名空间,该命名空间映射 XAML 语言命名空间http://schemas.microsoft.com/winfx/2006/xaml。
- x: 前缀 x: prefix
- 前缀 x: 用于映射 XAML 命名空间 http://schemas.microsoft.com/winfx/2006/xaml,该命名空间是支持 XAML 语言构造的专用 XAML 命名空间
最常用的 x: 前缀编程构造:
- x:Key:为 ResourceDictionary(或其他框架中的类似字典概念)中的每个资源设置唯一的键。 在典型的 WPF 应用标记中的所有 x: 用法中,x:Key 可能占到 90%。
- x:Class:向为 XAML 页提供代码隐藏的类指定 CLR 命名空间和类名。 必须具有这样一个类才能支持每个 WPF 编程模型的代码隐藏,因此即使没有资源,也几乎总是能看到映射的 x:。
- 在代码隐藏中编写的事件处理程序必须是实例方法,不能是静态方法。 这些方法必须由 x:Class 标识的 CLR 命名空间内的分部类定义。
- x:Name:处理对象元素后,为运行时代码中存在的实例指定运行时对象名称。 通常,经常为 x:Name 使用 WPF 定义的等效属性。
- x:Static:启用一个返回静态值的引用,该静态值不是与 XAML 兼容的属性。
- x:Type:根据类型名称构造 Type 引用。 用于指定采用 Type(例如 Style.TargetType)的特性,但属性经常具有本机的字符串到 Type 的转换功能,因此使用 x:Type 标记扩展用法是可选的。
- x:Code 是在 XAML 中定义的指令元素。
- x:Code 指令元素可以包含内联编程代码。 被定义为内联的代码可以与同一页上的 XAML 交互
- 代码位于 x:Code 元素内,并且必须括在 <CDATA[…]]> 中以转义 XML 的内容,以便 XAML 处理器(解释 XAML 架构或 WPF 架构)不会尝试将内容解释为 XML。
1
2
3
4
5
6
7
8
9
10
11
12
13<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="MyNamespace.MyCanvasCodeInline"
>
<Button Name="button1" Click="Clicked">Click Me!</Button>
<x:Code><![CDATA[
void Clicked(object sender, RoutedEventArgs e)
{
button1.Content = "Hello World";
}
]]></x:Code>
</Page>
- 咱们可可以在声明性 XAML 标记中创建可见的 UI 元素,比如按钮呀、下拉框呀、数字框呀…
- 然后使用代码隐藏文件(这些文件通过分部类定义与标记相联接)将 UI 定义与运行时逻辑相分离
- XAML 直接以程序集中定义的一组特定后备类型表示对象的实例化
- XAML 实现了一个工作流,通过此工作流,各方可以采用不同的工具来处理 UI 和应用的逻辑
- XAML 根元素和 XAML 命名空间 XAML root element and XAML namespace
- 以文本表示时,XAML 文件是通常具有 .xaml 扩展名的 XML 文件。
- 对象元素语法始终以左尖括号 (<) 开头。 后跟要创建实例的类型的名称,此后可以选择声明该对象元素的特性。 要完成对象元素标记,请以右尖括号 (>) 结尾。【也可以使用不含任何内容的自结束形式,方法是用一个正斜杠后接一个右尖括号 (/>) 来完成标记】
- 可通过任何 XML 编码对文件进行编码,但通常以 UTF-8 编码
1
2
3
4//创建 UI 中的按钮
<StackPanel>
<Button Content="Click Me"/>
</StackPanel>- 两个对象元素:
- 对象元素 StackPanel 和 Button 各映射到一个类名,该类由 WPF 定义并且属于 WPF 程序集
- <Button …/>(自结束形式,包含几个特性)
- 指定对象元素标记时,会创建一条指令,指示 XAML 处理创建基础类型的新实例。 每个实例都是在分析和加载 XAML 时通过调用基础类型的无参数构造函数来创建
- 两个对象元素:
- 特性语法对设置的对象属性命名,后跟赋值运算符 (=)
- 例如,以下标记将创建一个具有红色文本和蓝色背景的按钮,还将创建指定为 Content 的显示文本
1
<Button Background="Blue" Foreground="Red" Content="This is a button"/>
- 例如,以下标记将创建一个具有红色文本和蓝色背景的按钮,还将创建指定为 Content 的显示文本
- 属性元素语法 Attribute element syntax
- 对于对象元素的某些属性,无法使用特性语法,因为无法在特性语法的引号和字符串限制内充分地表达提供属性值所必需的对象或信息。 对于这些情况,可以使用另一个语法,即属性元素语法
- 属性元素开始标记的语法为 <TypeName.PropertyName>,该标记的内容是类型的对象元素,属性会将该元素作为其值
- 指定内容之后,必须用结束标记结束属性元素。 结束标记的语法为 </TypeName.PropertyName>
1
2
3
4
5
6
7
8
9
10
11
12//在前面的特性语法示例中设置的相同属性,但这次对 Button 的所有属性使用属性元素语法。
<Button>
<Button.Background>
<SolidColorBrush Color="Blue"/>
</Button.Background>
<Button.Foreground>
<SolidColorBrush Color="Red"/>
</Button.Foreground>
<Button.Content>
This is a button
</Button.Content>
</Button>
- 指定内容之后,必须用结束标记结束属性元素。 结束标记的语法为 </TypeName.PropertyName>
- XAML 语言包含一些优化,可以生成更易于阅读的标记
- 其中一项优化:
- 如果某个特定属性采用集合类型,则在标记中声明为该属性的值内的子元素的项将成为集合的一部分。 在这种情况下,子对象元素的集合是设置为集合属性的值。
1
2
3
4
5
6
7
8//用于设置 GradientStops 属性的值的集合语法
<LinearGradientBrush>
<LinearGradientBrush.GradientStops>
<!-- no explicit new GradientStopCollection, parser knows how to find or create -->
<GradientStop Offset="0.0" Color="Red" />
<GradientStop Offset="1.0" Color="Blue" />
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
- 如果某个特定属性采用集合类型,则在标记中声明为该属性的值内的子元素的项将成为集合的一部分。 在这种情况下,子对象元素的集合是设置为集合属性的值。
- 其中一项优化:
- XAML 内容属性 XAML content attributes
- XAML 指定了一个语言功能,通过该功能,类可以指定它的一个且仅一个属性为 XAML 内容属性
- 仅对内容属性而言,可以在 XAML 标记中设置该属性时省略属性元素,并在标记中生成更直观的父级/子级形式
1
2
3
4
5
6
7
8
9
10//例如,Border 指定 Child 的内容属性。 以下两个 Border 元素的处理方式相同。 第一个元素利用内容属性语法并省略 Border.Child 属性元素。 第二个元素显式显示 Border.Child
<Border>
<TextBox Width="300"/>
</Border>
<!--explicit equivalent-->
<Border>
<Border.Child>
<TextBox Width="300"/>
</Border.Child>
</Border>
- 仅对内容属性而言,可以在 XAML 标记中设置该属性时省略属性元素,并在标记中生成更直观的父级/子级形式
- XAML 内容属性的值必须完全在该对象元素的其他任何属性元素之前或之后指定
1
2
3<Button>I am a
<Button.Background>Blue</Button.Background>
blue button</Button>
- XAML 指定了一个语言功能,通过该功能,类可以指定它的一个且仅一个属性为 XAML 内容属性
- 文本内容 text content
- 类必须声明一个内容属性,并且该内容属性必须是可赋值给字符串的类型(该类型可以是 Object):
- 类型必须声明一个类型转换器,该类型转换器将文本内容用作初始化文本:
Blue 将 Blue 的内容值转换为画笔 - 类型必须为已知的 XAML 语言基元:
- 内容属性和集合语法组合 Content attribute and collection syntax combination
1
2
3
4<StackPanel>
<Button>First Button</Button>
<Button>Second Button</Button>
</StackPanel>- 每个 Button 都是 StackPanel 的子元素
- 省略了 StackPanel.Children 属性元素:StackPanel 派生自 Panel。 Panel 将 Panel.Children 定义为其 XAML 内容属性
- 省略了 UIElementCollection 对象元素:Panel.Children 属性使用类型 UIElementCollection,该类型实现 IList。 根据处理 IList 等集合的 XAML 规则,集合的元素标记可以省略。
- UIElementCollection 实际上无法实例化,因为它不公开无参数构造函数,这便是 UIElementCollection 对象元素显示为注释掉的原因
- 如果属性的类型是集合,则推断的集合类型不需要在标记中指定为对象元素。 相反,旨在成为集合中的项的元素被指定为属性元素的一个或多个子元素。
- 每个此类项会在加载期间计算为对象,并通过调用隐式集合的 Add 方法来添加到集合中。
- 比如,Style 的 Triggers 属性采用实现 IList 的专用集合类型 TriggerCollection。
- 不需要实例化标记中的 TriggerCollection 对象元素。 而是将一个或多个 Trigger 项指定为 Style.Triggers 属性元素中的元素,其中 Trigger(或派生类)是应作为强类型化的隐式 TriggerCollection 的项类型的类型。
1
2
3
4
5
6
7
8
9
10<Style x:Key="SpecialButton" TargetType="{x:Type Button}">
<Style.Triggers>
<Trigger Property="Button.IsMouseOver" Value="true">
<Setter Property = "Background" Value="Red"/>
</Trigger>
<Trigger Property="Button.IsPressed" Value="true">
<Setter Property = "Foreground" Value="Green"/>
</Trigger>
</Style.Triggers>
</Style>
- 每个此类项会在加载期间计算为对象,并通过调用隐式集合的 Add 方法来添加到集合中。
- 特性语法(事件) Attribute Syntax (Events)
- 特性语法还可用于事件成员,而非属性成员。 在这种情况下,特性的名称为事件的名称。 在 XAML 事件的 WPF 实现中,特性的值是实现该事件的委托的处理程序的名称。
1
2
3
4
5
6
7//以下标记将 Click 事件的处理程序分配给在标记中创建的 Button:
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ExampleNamespace.ExamplePage">
<Button Click="Button_Click" >Click Me!</Button>
</Page>
- 特性语法还可用于事件成员,而非属性成员。 在这种情况下,特性的名称为事件的名称。 在 XAML 事件的 WPF 实现中,特性的值是实现该事件的委托的处理程序的名称。
- XAML 中的大小写和空白 Case and whitespace in XAML
- 一般而言,XAML 区分大小写。 出于解析后备类型的目的,WPF XAML 按照 CLR 区分大小写的相同规则区分大小写。 以下情况下,对象元素、属性元素和特性名称均必须使用区分大小写的形式指定:按名称与程序集中的基础类型进行比较或者与类型的成员进行比较。 XAML 语言关键字和基元也区分大小写。 值并不总是区分大小写。 值是否区分大小写将取决于与采用该值的属性关联的类型转换器行为,或取决于属性值类型。
- WPF XAML 处理器和序列化程序将忽略或删除所有无意义的空白,并标准化任何有意义的空白。
- 标记扩展是一个 XAML 语言概念。 用于提供特性语法的值时,大括号({ 和 })表示标记扩展用法。 此用法指示 XAML 处理不要像通常那样将特性值视为文本字符串或者可转换为字符串的值
- redWPF 应用编程中最常用的标记扩展是 Binding(用于数据绑定表达式)以及资源引用 StaticResource 和 DynamicResource。
- 例如,以下标记使用特性语法设置 Style 属性的值。 Style 属性采用 Style 类的实例,该类在默认情况下无法由特性语法字符串进行实例化。
1
2
3
4
5
6
7
8
9
10
11//但在本例中,特性引用了特定的标记扩展 StaticResource。 处理该标记扩展时,将返回对以前在资源字典中作为键控资源进行实例化的某个样式的引用。
<Page.Resources>
<SolidColorBrush x:Key="MyBrush" Color="Gold"/>
<Style TargetType="Border" x:Key="PageBackground">
<Setter Property="Background" Value="Blue"/>
</Style>
</Page.Resources>
<StackPanel>
<Border Style="{StaticResource PageBackground}">
</Border>
</StackPanel>
- 类型转换器 type converter
- 对字符串如何转换为其他对象类型或基元值的基本本机处理取决于 String 类型本身,以及对某些类型(如 DateTime 或 Uri)的本机处理。 但是很多 WPF 类型或这些类型的成员扩展了基本字符串特性处理行为,因此可以将更复杂的对象类型的实例指定为字符串和特性
- Thickness 结构是一个类型示例,该类型拥有可使用 XAML 的类型转换。 Thickness 指示嵌套矩形中的度量,可用作属性(如 Margin)的值。 通过对 Thickness 放置类型转换器,所有使用 Thickness 的属性都可以更容易地在 XAML 中指定,因为它们可指定为特性
- 使用类型转换和特性语法来为 Margin 提供值:
- 使用类型转换和特性语法来为 Margin 提供值:
- Thickness 结构是一个类型示例,该类型拥有可使用 XAML 的类型转换。 Thickness 指示嵌套矩形中的度量,可用作属性(如 Margin)的值。 通过对 Thickness 放置类型转换器,所有使用 Thickness 的属性都可以更容易地在 XAML 中指定,因为它们可指定为特性
- 对字符串如何转换为其他对象类型或基元值的基本本机处理取决于 String 类型本身,以及对某些类型(如 DateTime 或 Uri)的本机处理。 但是很多 WPF 类型或这些类型的成员扩展了基本字符串特性处理行为,因此可以将更复杂的对象类型的实例指定为字符串和特性
- 事件和 XAML 代码隐藏 Events and XAML code-behind
- 为对象元素添加行为的主要应用程序级机制是使用元素类的现有事件,并为在运行时引发该事件时调用的该事件编写特定的处理程序。 在标记中指定事件名称以及要使用的处理程序的名称,而在代码隐藏中定义实现处理程序的代码。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ExampleNamespace.ExamplePage">
<Button Click="Button_Click" >Click Me!</Button>
</Page>
...
namespace ExampleNamespace
{
//代码隐藏文件使用 CLR 命名空间 ExampleNamespace 并将 ExamplePage 声明为该命名空间内的一个分部类。 这相当于 ExampleNamespace 的 x:Class 特性值。在标记根中提供的 ExamplePage。
public partial class ExamplePage
{
void Button_Click(object sender, RoutedEventArgs e)
{
Button b = e.Source as Button;
b.Foreground = Brushes.Red;
}
}
}- WPF 标记编译器将通过从根元素类型派生一个类,为编译的任何 XAML 文件创建一个分部类。 在提供定义同一分部类的代码隐藏时,将在与编译的应用相同的命名空间和类中合并生成的代码。
- 内联代码是一种通用性较低的方法,具有很多的限制
- 为对象元素添加行为的主要应用程序级机制是使用元素类的现有事件,并为在运行时引发该事件时调用的该事件编写特定的处理程序。 在标记中指定事件名称以及要使用的处理程序的名称,而在代码隐藏中定义实现处理程序的代码。
- 路由事件 Routed events
- 路由事件使一个元素可以处理另一个元素引发的事件,前提是这些元素通过树关系连接在一起。
- 使用 XAML 特性指定事件处理时,可以对任何元素(包括未在类成员表中列出该特定事件的元素)侦听和处理该路由事件。 这是通过以所属类名限定事件名特性来实现的。
- StackPanel / Button 示例中,父 StackPanel 可以在 StackPanel 对象元素上指定特性 Button.Click,并将处理程序名用作特性值,从而为子元素按钮的 Click 事件注册一个处理程序
- 类型和类继承的成员 Type and class inherited members
- 显示为 WPF 类型的 XAML 成员的属性和事件通常是从基类型继承的
- <Button Background=”Blue” …/>中,Background 属性不是 Button 类中的立即声明属性。 相反,Background 从基类 Control 进行继承
- 对象元素语法,它实例化 Button 类的新实例的,还指定 Name 特性以及该特性的值
- XAML 内容属性语法的对象元素语法。 其中包含的内部文本会用于设置 TextBox XAML 内容属性 Text
This is a Text Box
- 显示为 WPF 类型的 XAML 成员的属性和事件通常是从基类型继承的
二、Windows Presentation Foundation (WPF) 样式设置和模板化、内联样式和模板 Inline styles and templates
……开个头,继续看
巨人的肩膀
- https://learn.microsoft.com/zh-cn/dotnet/desktop/wpf/advanced/?view=netframeworkdesktop-4.8
- https://cloud.tencent.com/developer/article/1342886
- 官方手册:https://learn.microsoft.com/zh-tw/dotnet/desktop/wpf/advanced/wpf-architecture?view=netframeworkdesktop-4.8
- Head First 设计模式
- 设计模式之禅
- 公众号啦、OSCHINA啦、上面的有关设计模式的文章
All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.


