jk's notes
  • CH 01 WPF 概览

CH 01 WPF 概览

主要介绍 WPF 的基本概念, 同时作者建议不要跳走看有趣的章节. 建议读者看一看本章内容.

WPF 与传统开发有很多的不同, 本章会介绍什么是 WPF.

会介绍一些 WPF 的特性, 帮助你去理解其特定.

同时会介绍 WPF 项目类型的基础.

最后本章会帮助你理解后续章节, 本章会简要介绍一些术语. 例如: 资源, 样式, 控件模板等.

果壳中的 WPF

WPF 已经存在许久, 但依旧有很多人不知道. 仅仅以为它是一组控件到 Vista 的东西, 再到 XAML.

某种意义上说也不完全错误, WPF 确实包含很多控件, 也使用 XAML 来布局页面, 但不仅仅如此, WPF 还包含很多其他内容.

实际上 WPF 的特性也是得其被 "滥用", 做了一些过多动效, 但没有意义的应用.

如下图, 就是一个令人烦躁的案例

image-20250617094419841

本章请关注能做什么, 是什么, 而不是怎么做.

可以在网上下载本书所有示例

良好的设计与理念, 合理的使用动效可以做出优秀的应用.

使用动画的时候需要考虑是不是有必要的, 一味过度地使用动效, 使用后会令人反感. 例如每次点击按钮, 都会等待一波动画才能得到结果.

建议向 WPF 转型.

当然也不是多有应用都应该使用 WPF. 每个应用都有自身的执行特点. 例如服务器上的应用, 命令行还是最合适.

WPF 是什么

那么 WPF 是什么? 有人说他是库, 框架, 子系统, 一组控件, 语言, 或者是编程模型.

简单的可以理解为它是一组对象, 可以帮助你开发出很炫酷的应用. 这些对象中包括一些控件, 以及一些其他新的特性.

WPF 还包含丰富的对象来管理维护动画, 资源, 事件, 样式, 模板, 以及其它 WPF 新特性.

你的应用将组合使用这些对象来构建你的用户界面.

什么是 XAML

XAML 是可扩展标记语言 (eXtensible Application Markup Language). XAML 是扩展与 XML, 用来设计界面的 (借鉴与 HTML). 同时定义了特定的 token 来表示 windows, controls, resources, styles, 以及其他 WPF 对象.

程序可以加载含有 XAML 的文件来加载用户界面.

需要注意的是 创建界面不是必须使用 XAML, 也可以使用代码来创建.

逻辑上, 是辅助, 方便设计界面而来.

所有的 XML 规则都适用于 XAML. 特别的, XAML 文件, 必须有一个根元素. 至于是什么根元素, 取决于是什么项目.

例如在窗体应用中, 是 Window, 而在松散的应用中 (由其他窗口作为载体), 可以是布局元素, 例如 Grid 等.

控件先睹为快

控件会在后续详细加以介绍, 这里可以简单的了解一下. Grid 是按照表格的样式来布局应用, StackPanel 是按照水平或垂直方向来排列控件.

每一个标签必须又开头与结束, 标签放在尖括号中. 开头结束标签名相同, 结束标签使用 / 作为前缀. 例如, 下面代码定义了一个 StackPanel:

<StackPanel>
</StackPanel>

如果标签没有子元素可以简化, 使用斜线写在开头标签的结尾处, 那么就不用写结束标签了. 下面是一个实例:

<Image Margin="10" Width="75" Height="75" Source="Volleyball.jpg" />

上述代码还描述了属性, 属性写在开始标签中, 以键值对的形式存在. 上述 Margin, Width, Height, Source 都是属性, 其值分别是: 10, 75, 75, 以及 Volleyball.jpg

XAML 标签必须正确嵌套, 描述什么标签包含了什么标签. 下面是一个案例:

image-20250617100656083

然后作者简要介绍了应用的布局, 以及各个控件的起到的作用. 最终运行结果为:

image-20250617100757945

对象树

构成界面的标签形成一棵树结构.

image-20250617101016491

WPF 中有两个树的概念. 形如下面的树结构:

image-20250617101036467

逻辑树

逻辑树由对象之间的关系构成. 例如控件包含控件等.

其中还包含一些你可能不认为是独立对象的部分. 特别是使用设计工具时, 很难将其理解为独立的对象.

例如给 ListBox 添加数据, 数据会自动添加到 ListBoxItem 中. 这里 ListBoxItem 也是独立的对象.

可视树

可视树是可视对象的结构. 例如滚动条包含, 两端箭头, 中间的滚动区域, 以及控制条等.

为什么需要关心这些? 首先, 因为控件会在逻辑树上进行属性的继承. 例如涉及到字体, 字号等. 其次, 事件的传递遵循可视化树, 如果需要父级控件统一处理事件等.

非树结构

由于控件标签的结构, 很自然的可以将界面映射成 XML 树, 但并非所有都是如此. 例如下面更为复杂的案例:

image-20250617103121540

每一个图像周边都有阴影, 这就需要单独处理, 亦或将来需要修改这个阴影亦是如此.

你可以定义样式, 然后将样式作用到图像上, 而不是为每一个图像使用重复的阴影代码 (可视树结构被破坏了). 如果要改变样式, 可以定义其他样式, 再将样式作用于图片.

类似的, 可以为文字也定义单独的样式.

不幸的是, 这些用样式可以作用到逻辑层中的不同层次的控件上, 这便破坏了树结构.

总之 XAML 使用层次结构来组织界面.

什么是 Silverlight

Silverlight 以前被称为 WPF/e, 这里的 e 是指 everywhere. 实际上 Silverlight 是 WPF 的限制版本, 旨在浏览器中运行.

为了保证 Silverlight 可以在浏览器中运行, Silverlight 是 WPF 的子集, 虽然缺少部分组件, 但本质相同, WPF 中的核心内容依旧适用于 Silverlight.

然后作者描述了一些差异, 以及 Silverlight 中的限制, 这里略.

项目类型

WPF 有三种项目类型:

  • 独立应用
  • XAML 浏览器应用
  • 松散的 XAML 页面

独立应用是编译后运行在本地计算机的应用, 可以拥有对计算机的绝对访问权. 可以读写文件, 操作注册表等. 可以使用 C#, VB 进行开发.

XAML 浏览器应用是受限的, 这里略.

松散的 XAML 页面也是浏览器使用的, 这里略.

除了这些类型, WPF 还提供了一些不同的导航模型. 还支持类似于浏览器的导航模型, 涉及到 Frame 和 NavigationWindow. 还有一个 PageFunction 类, 支持一个页面等待另一个页面的执行.

后续章节会详细讨论.

目标与优势

WPF 的重要目标包括:

  • 更好的利用图形硬件
  • 属性绑定来创建动画
  • 属性继承
  • 样式
  • 模板
  • 一致的控件约束
  • 界面代码分离
  • 新的控件
  • 声明式编程

下面详细说明

更好的利用图形硬件

WinForm 基于 GDI+(GDI 的 .NET 版本) 来构建. 虽然很强大, 但也有些过时. 它无法使用图形硬件. 如今即使最便宜的计算机其图形硬件也比早期电脑优秀.

WPF 则是基于 DirectX 库开发. 能更好的利用图形硬件.

因为 DX, WPF 处理图形能更快, 更灵活. DX 对多媒体, 变换, 矢量图等支持更好.

更好的多媒体支持

DX 内置对多媒体的支持, 下面代码在单击后播放音频:

<EventTrigger RoutedEvent="ButtonBase.Click">
  <SoundPlayerAction Source="speech_on.wav"/>
</EventTrigger>

MediaElement 控件使得播放视频也非常容易, 只需要设置 Source 属性, 控件会提供播放控制按钮.

然后作者展示了一个在同一个界面播放三段视频的案例:

image-20250617141615037

变换 (Transformations)

DX 提供了很多图形变换的方法, WPF 通过这些方法来实现图形变换. 不仅针对图形, 还可以对控件等作用同样的变换.

例如让 Label 控件向左旋转 90 度:

image-20250617143355768

还可以将变换作用于 MediaElement, 变换示例如下:

image-20250617143600472

如果有需要, 使用 WPF 可以轻松的实现类似效果.

3D 图形

现代计算机都搭载了图形硬件, 对 3D 图形的支持都比较好 (DX, 或其中的 Direct3D). WPF 也使用 Direct3D 来支持 3D 图形.

如果直接使用 Direct3D 需要编写大量代码进行初始化, 硬件查询, 以及错误处理. 但这一切 WPF 都帮你处理好了.

除了一些细节代码, WPF 还会帮助你完成基于本地 Direct3D 来进行一些适配操作.

不过涉及到 3D 应用的细节还是需要创建的, 例如灯光, 纹理等.

实际上要完成复杂的场景依旧是很困难的, 即使有了 WPF, 实际上此时使用代码比 XAML 更合适.

然后演示了一个 3D 应用的示例

image-20250617152138166

Retained-Mode 绘图

在3D绘图中有一个保留模式和立即模式. 保留模式会在内存中记录图形, 以便重复使用. 立即模式会实时创建图形 (2025年6月17日我的理解).

当控件放在窗体上时会被绘制, 如果被覆盖后再显示, 控件会自动重绘.

不同于 WinForm, 在 WPF 中所有可视对象均会在必要时, 自己进行重绘.

如果那你要更改对象的位置, 只需要修改其坐标, 然后它就会被重绘.

下面代码会绘制一个五角星, 不用编写任何重绘代码, 它会自动的重绘.

<Polygon Stroke="Red" StrokeThickness="5" 
         Points="20,20 120,40 30,70 80,10 110,90"/>

image-20250617161005286

高分辨率矢量图

绘图的一个方式是创建图片的 bitmap 图像, 然后显示 bitmap. 基于 bitmap 的绘图系统被称为光栅系统.

如果只是按照原始尺寸显示图片还好, 但是涉及到拉伸, 旋转等变换, 图像会被像素化, 方块化.

应该就是会变得不清晰. 变得到处是方块的感觉.

要处理这个问题, WPF 使用了不同的方法. 它使用矢量图代替了基于 bitmap 的图. 矢量绘图系统存储了绘制需要的关键点位, 而不是记录图像本身. 在需要变换时, 会根据关键点位, 基于变换重新计算获得需要的绘制点位. 来获得高清图像.

例如下面的示例, 第一行使用 XAML 对象:

image-20250617161923506

矢量图被放大时, 所有东西都放大了, 包括线宽. 只是会更为平滑.

属性绑定以及动画

使用 WPF 的另一个不同是对属性的处理. 期初, 可以像传统 WInForm 属性一样看到 WPF 中的属性.

然后作者例举了线性画刷的例子:

LinearGradientBrush bg = 
  new LinearGrandientBrush(Color.White, Color.Green, 90);
grdMain.Background = bg;

然而不同的是, WPF 大量使用的依赖属性. 它是 WPF 属性系统中特有的属性. 依赖属性具有传统属性不支持的功能. 包括默认值, 继承(下一节会介绍), WPF 风格的数据绑定, 以及属性修改通知等.

更改通知允许在属性发生变化时 WPF 系统进行通知, 以便进行处理. 另外 WPF 风格的数据绑定, 允许为属性值依赖于其他值. 这两个特性使得属性的变化可以动画化.

然后作者演示了一个案例, 让按钮有动效 (现代 Web 应用这个特性很常规)

image-20250617163528591

属性继承

WPF 控件会从其容器中继承属性值. 例如 Grid 设置字体后, 放在 Grid 中的 Button 就会有该字体.

它不同于 OOP 中的继承

属性值实际上依赖于一个可能源的序列. 是其共同起作用的.

jk: 按照一个树来搜索, 获得一组属性值, 然后就近原则起作用.

下面描述了优先级. 顶部具有最高优先级, 例如自己的属性可以覆盖继承的属性. 动画可以覆盖本地值, 至少在动画运行时是如此.

  • 动画值
  • 本地值
  • 样式
  • 继承
  • 默认

更为详细的优先级可以参考文档.

样式

样式允许你定义一组属性值, 将其打包后晚些时候使用.

样式让你可以很容易的定义一组通用的外观. 同时在需要修改的时候, 直接修改样式, 那么作用该样式的控件会自动被更新. 这对管理控件样式非常友好.

样式也可以被服用. 基于某个样式定义新样式.

有些控件会阻止继承, 例如 Button 和 ListBox 控件不会继承 Background 属性.

模板

WPF 有好几种模板.

控件模板允许自定义组件以及行为. 然后作者简要举了个例子, 一个怪异形状的按钮的描述.

另一个类型的模板是数据模板. 数据模板可以定义在绑定数据时该怎么显示. 然后作者说可以让 StackPanel 作为 ListBox 中每一项数据的容器.

样式可以修改控件的表现形式. 模板可以修改控件的结构与行为.

一致的控制 (Consistent Control Containment)

多数 WinForm 控件仅可以包含单个类型的内容. 例如 TextBox, Label, GroupBox 等控件都有 Text 属性, 用于展示显示时的文本.

而 WPF 控件可以直接或间接的包含任何内容. 这里的间接任何作者加以说明, 例如 Button 只能含有一个子元素, 但是它可以是 StackPanel, 然后就可以是任意元素了.

然后使用了一个包含 Image 和 Label 的 Button 的案例:

image-20250617174158361

可见 WPF 比起 WinForm 灵活许多.

分离用户界面与后台代码

界面与代码分离是 WPF 的一个壮举. 界面使用 XAML 设计, 使用 C# 或 VB 代码来编写应用逻辑.

这样有利于设计人员使用设计工具来开发界面, 而程序员可以专注与代码的逻辑与实现.

实际上大多数公司不会将其分给两组人员开发 (考虑到成本), 但是分离依旧使得开发更为容易 (逻辑上是清晰的).

但是也存在缺点, 高级的一些特效, 仅仅只懂得设计, 而不明白代码也是不行的.

新控件

WPF 带来了一些编排子元素的容器控件. 例如 StackPanel, DockPanel, WrapPanel, 以及 Grid, 并一句话概括了这些控件的作用.

主要介绍了 FlowDocument 相关的控件, 其他略.

声明式编程

很多人认为声明式编程就是 XAML, 但实际上不完全. 很多高级特性 XAML 是晦涩难懂的.

声明式编程是 XAML 的一个很好的特性, 但其优缺点是不明确的.

缺点 disadvantages

学习曲线陡峭. 使用 XAML 控制单个控件简单, 但是统一控制就难了.

WPF 包含很多不一致的特性. 例如属性继承的异常, XAML 使用分层格式来存储不总是分层的信息等. 这就表示它必须包含一些特殊的语法.

WPF 提供了很多新控件(相较于 WinForm), 也移除了一些控件.

然后是稳定性.

最后是设计工具, 也存在一定缺陷.

小结

略

Last Updated:
Contributors: jk