本页内容采用倒叙方式,且看:
当前基于 Win32 的 Windows UI 图形子系统(创建于 Windows® XP 中)已经使用了将近 20 年。它不仅过时了而且有很多限制,从而在某种程度上桎梏了用户界面开发。
Windows Presentation Foundation (WPF)(内置于 .NET Framework 之中)为开发应用程序提供了新技术,并可以更好地使用当前的硬件和技术。本文,我们将为您展示使 WPF 优于其 Win32®“前辈”的十大优势。虽然某些特定细节在 WPF 最终版本中可能更改,但我们预期关键的概念保持不变。有关目前如何开始使用 WPF 的详细信息,请参阅提要栏“”。
10. 高级图形
在过去的几年中,Windows 中的绘图功能业已稳步提升。在 WPF 之前,GDI+ 代表了 Windows 中 2D 图形的“顶峰”。GDI+ 提供了完全可伸缩的绘图基元,包括贝塞尔路径、基数样条、文本和多种形状。它支持多种高级渐变和纹理填充风格。而且它提供对部分透明度和反混淆的完全支持。
增强的绘图功能 基于其基本绘图功能,WPF 看起来更象是它的一大进步。它使您能够更好地使用基础图形硬件,因此它比 GDI+ 的执行速度快得多。它提供一组丰富的绘图功能,同时还引入了几个新功能。例如,虽然 GDI+ 在绘图时允许应用任意的剪辑区域,但是 WPF 甚至进一步增强了该功能:透明度蒙板 (Opacity Mask) 功能使您不仅能够将输出剪辑为任何形状,还能修改任何内容的透明度。
图 1 重复模式以用作透明度蒙板
例如,请考虑图 1 中的重复图形模式。您可以创建一个画刷(使用一些可扩展应用程序标记语言 (XAML) 代码),它使您能够使用该模式进行绘图。(顺便说一句,XAML 是一种基于 XML 的、用于生成 .NET 对象树的语言。它提供一种创建 WPF 对象的简洁方法。)以下代码显示定义这种画刷所用的 XAML 代码:
该画刷可用作任何用户界面元素的 OpacityMask。图 2 显示一个渐变填充背景顶部的某个黄色文本,使用该蒙板可以调节通明度。
图 2 应用了透明度蒙板的文本
更少的控件约束 具有这些新绘图功能已经是一件很好的事,但 WPF 进行了一些更深入的更改。更深入的区别是,绘图不再仅限于单个控件。请考虑任何基于 Win32 的应用程序 — 任何使用 Windows 窗体、MFC、Windows 模版库或 Visual Basic® 生成的、基于 Windows 的应用程序,以及任何直接使用 Win32 API 的应用程序。
如果您现在想使用 WPF,可以将 WinFX® 运行库和 SDK 下载并安装到您的计算机上。这是预发布软件,未经交付 Microsoft 软件所需的常规测试。因此,不要将它安装在您日常工作所用的计算机上。
本文基于 2005 年 9 月社区技术预览 (CTP) 版的 WinFX,它是 Beta 1 之后的版本。September CTP WinFX 运行库可以从 下载,而相应的 WinFX SDK 可以从 下载。此外,您也可以从 下载 Visual Studio Extensions for WinFX。
请注意,September 2005 CTP of WinFX 只与 Visual Studio 2005 Beta 2 兼容。它与更新的版本不兼容。有关如何安装和运行 WinFX 的 CTP 版本的更多信息,请参阅 。
使用 Win32 以及相关的 GDI 和 GDI+ 绘图 API,该 UI 中的每个控件均具有自己的窗口部分。这些控件将窗口分成一组分离的区域,这些区域通常(但并不一定)是矩形。如果选取窗口上的任何单个像素,则肯定会有一个控件负责绘制该像素。
该方法的问题在于,它使某些 UI 效果不可能出现。关于 Windows 窗体的一个常见问题是:“我如何绘制一些能够出现在窗体上所有控件顶部的内容?”由于每个控件在该屏幕上都具有其自己的独占部分,因此没有达到该效果的直接方式。虽然 GDI+ 环境中有一些实际的工作区,但如果您想使用部分透明的绘图效果,它们却不起作用。GDI+ 提供针对绘画操作的部分透明性和反混淆,但是仅当在单个控件的边界内执行时才会如此。
WPF 打破了这一限制。在 WPF 应用程序中,单个控件或 UI 元素均不独占任何窗口部分。您可以创建部分透明的 UI 元素,或者创建外形上没有约束为逻辑矩形的控件。例如,一个控件可以将阴影投射到其逻辑区域之外。
例如,请参见图 3。该图像显示两个按钮。顶部的按钮是半透明的,而底部的按钮通过顶部按钮可见。另外,还混入了一个部分透明的椭圆形。这种重叠设计对于 Windows 窗体或其他基于 Win32 的系统而言是不可能的。(Windows 窗体提供伪透明,它可以解决其中一些限制,但是它只适用于非常基本的设计。)
图 3 控件和透明度
在效果上,整个窗口变成了一个由该 UI 的每个部分组成的单个绘图界面。这有助于启用另一个重要的 WPF 图形功能:分辨率独立。
分辨率独立 平板显示器的分辨率一直在逐渐增高。150 dpi 显示器是再普通不过的,200 dpi 显示器轻而易举即可得到,而且分辨率有可能继续增高。可伸缩性对于启用高 dpi 显示器而言至关重要。如果无法伸缩一个应用程序的 UI,则该 UI 只是在分辨率增高时变得更小,通常在高于 150 dpi 时不可用。
虽然 GDI+ 启用了分辨率独立的绘图(GDI 也如此),但是使用这些技术编写可视化可伸缩的应用程序非常困难。如果您使用这些 API 应用伸缩或旋转转换,它只会影响这些图画,而不会影响该窗口中控件的大小和位置。从技术上讲,该问题在于,虽然 GDI 和 GDI+ 是可伸缩的,但 USER32 却不然。您可以伸缩图画,但不能轻松地伸缩控件划分窗口空间的方式。
由于 WPF 将窗口中的所有控件作为单个图画包含在内,而不是将每个控件分隔到它自己的区域,因此将伸缩或旋转转换作为一个整体应用到 UI 非常容易。因此,由于我们可以按比例增大或缩小任何 WPF UI,所以 WPF 应用程序是有效的分辨率无关。图像仍保持清晰,而不会像您预期的那样产生简单位图伸缩带来的模糊感。
内置动画和视频 WPF 提供对动画和视频的内置支持,使您能够更轻松地生成动态 UI。虽然您可以使用 GDI 或 GDI+ 为 UI 的某些部分制作动画,但是该过程需要您设置一个计时器并编写代码,以便有规律地更新显示内容。使用 WPF,您可以指示系统自动为元素制作动画。
对动画的支持是很广泛的,从而允许您为用户界面的任何部分制作动画。该示例显示一个矩形,它的宽度每五秒钟在 20 到 200 之间重复变化:
(该示例也解释了另一个新功能 — Rectangle 元素使用 WPF 的用于呈现图画的对象模型。)
WPF 也使您能够将视频与单个元素合并,如下所示:
该用户界面元素与其他用户界面元素类似。您可以将一个 MediaElement 放在可能放置按钮或椭圆的任何位置。您可以应用所有常用的可视化操作,例如,伸缩、旋转或剪辑。它刚好是一个显示视频剪辑的 UI 元素。
9. 绘制对象模型
图 4 在 Win32 中重新绘制
WPF 图形系统提供全新的编程风格。使用基于 Win32 的 UI 技术,如果您想绘制自定义图形,则必须编写响应重新绘制请求的代码,从而按需绘制细节。该代码有效地直接绘制到屏幕。如果屏幕的这一部分由于另一个窗口而变得模糊,并且之后暴露出来,Windows 会发送一个请求重新绘制它的消息。如果您想更改图形外观,则需要指示 Windows 使该屏幕的相关区域无效,以便初始化一个重新绘制。图 4 阐释了该系统。
图 5 在 WPF中重新绘制
虽然 WPF 仍然支持该模型,但是它提供一个更简单的替代方案。在 WPF 中,您可以创建一些对象来表示要使用的绘画基元并将它们添加到 UI。您可以使用 XAML 或传统的应用程序代码进行此操作。当这些对象处于用户界面元素树中之后,它们由 WPF 呈现 — 无需编写任何代码来处理重新绘制请求。(图 5 显示该系统。)此外,当您更改此类对象的属性(例如,Rectangle 的 Width 属性)时,显示内容自动更新。换句话说,您能够使用绘图基元(与使用控件的方式相同)— 只需创建它们并设置其属性,从而使它们将其自身绘制到屏幕上。
中的代码创建了 10 个 Rectangle 元素,并将它们添加到一个 Window。无论该 Window 何时接收到鼠标滚轮事件,它都调整所有 Rectangle 元素的 Width(请参见图 7)。
请注意,OnMouseWheel 方法不显式触发任何重新绘制。实际上,看不到重新绘制或重新绘图处理程序。在基于 Win32 的程序中,需要提供此类处理程序并使该窗口无效,以便使显示内容更新。在 WPF 中,这是不必要的。
图 7 调整矩形宽度
该绘图对象模型使得将图形元素集成到应用程序中比以往更容易。但是良好的呈现并非只与图形有关 — 纹理呈现的质量同样重要。
8. 丰富的应用程序文本
Windows 应用程序中的文本长期受困于 Web 应用程序提供的文本功能。HTML 和层叠样式表(cascading style sheet,CSS)提供具有大量文本布局和格式化功能的应用程序。在另一方面,Windows 应用程序具有的支持很少。
当然,可以使用 Win32 显示丰富的格式化文本(毕竟,Microsoft® Internet Explorer 和 Microsoft Word 都是 Windows 应用程序),但是该编程任务意义重大。您也可以嵌入 MSHTML 控件并用它显示文本。但是,该技术并不完全将文档性质的文本呈现直接集成到应用程序中,这意味着某些应用程序区域(例如,一个按钮标签中的文本风格)无法从 MSHTML 中获益。遗憾的是,由 MSHTML 托管的文档显示与应用程序的其余部分之间形成了鲜明的对比。
WPF 使得将丰富的格式化文本集成到用户界面中更加容易。甚至最简单的文本处理元素(TextBlock 控件)也使您能够使用字体风格、大小、颜色和字体的任意组合。以下是一个由单个 TextBlock 元素组成的示例:
Hello, world! This is asingle <TextBlock/>element .
正如您在图 8 中看到的那样,即使在这个元素中,我们也可以获取很多风格。与 Windows 窗体中的普通 Label 控件相比,它是相当强大的。即使您只需要可使用 Label 获取的纯文本,TextBlock 仍然是非常易于使用的。要放弃特殊的格式化,您只需为它提供一个简单的字符串以显示出来。
图 8 具有丰富格式化的 TextBlock
对于稍大的文本,WPF 提供 TextFlow 控件 — 提供类似文档的功能。例如,您可以包含编号列表或项目符号列表以及浮动图形。TextFlow 提供一个类似于 HTML(但不类似于 HTML)的功能集,它旨在与 WPF 控件无缝集成。
WPF 弥合了文档显示与其他应用程序功能之间的硬边界,从而可以更容易地生成具有更高级文本和版式用法的应用程序。由于 TextFlow 元素是 WPF 的一个集成部分,因此,您可以将该元素放在 UI 中的任何位置。而且您可以随意混合用户界面元素,例如,将按钮直接放在文本中间(请参见图 9)。
图 9 混合控件和文本
此外,对于需要显示大量文本的应用程序而言,WPF 提供了高级文本布局功能(当文本呈现是应用程序的主要目的时,它们特别有用)。例如,DocumentViewer 控件显示文本文档,从而使用高级文本布局探测法针对可用空间和分辨率优化文本的可读性。该文本布局技术结合了 Microsoft 在屏幕可读性方面的广泛研究成果。
使文本具有很好的外观对于生成漂亮的应用程序至关重要。但更重要的是,确保应用程序的所有可视部分以正确的大小显示在正确的位置,而无论用户如何选择排列其窗口。
7. 可修改的 UI 布局
多数应用程序设计人员都不知道其程序的运行大小。不同 PC 间的屏幕大小差别很大。此外,一些用户以最大化方式(充满整个屏幕)运行应用程序,而其他用户使用较小的窗口以便可以同时看到若干个应用程序。
这并没有妨碍一些设计人员设计只以一种大小工作的 UI(出于某种原因,这在 Web 上很普遍)。但是,如果您想满足用户的需要,就应该生成一个具有可以将自身修改为可用空间的布局的 UI。如果您需要使应用程序可访问且可本地化,这种适用性也非常有用。一个足够适用的 UI 无需重新设计,即可以一种屏幕的低分辨率呈现,或者可以转换为所有单词都是两倍长度的语言。
WPF 提供功能强大的布局元素工具箱,这些元素使得生成具有适应性布局的 UI 更加容易。Windows 窗体开发人员所熟知的简单停靠和锚定技术是可用的。使用一个流风格布局也是可能的,其中 UI 元素的排列方式就像单词在文本中的排列方式一样:从左到右,然后在当前行满时进入下一行首。WPF 也提供了一个 Grid。这是一个启用类似网格或表布局的强大布局元素 — 用过 HTML 表元素的人会非常熟悉它。由于其通用的适用性,Grid 的使用可能比所有其他布局面板的使用更多。
作为一个简单的用例, 显示一个具有两行两列的网格的代码。在 Grid 元素内部,ColumnDefinition 和 RowDefinition 元素定义 Grid 中行和列的属性。然后,每个 UI 元素都有一个行和列属性以指明它在 Grid 中显示的位置。图 11 显示结果。
图 11 呈现的 Grid
本示例也显示了无需约束为网格中单个单元格的项。Title TextBlock 使用 Grid.ColumnSpan 属性来跨越这两个列。这就是它设法填充网格整体宽度的方式。
此外,该示例还阐释了自动且成比例地调整大小的用法。第一行的 Height 已设置为 Auto,这会通知网格使行足够高以便容纳且刚好容纳该行内容。第二行负责填充其余可用空间。列宽使用了一个不同的技术;它们已经设置为 * and 5*。* 语法指示行或列应该共享可用空间。该数字指示用于分配空间的比例。具有宽度 5* 的列获取五倍于具有宽度 * 的列的空间。(该技术类似于在 HTML 表中分配百分比。)您也可以使用十进制数,例如,1.2*。
可以嵌套布局元素,从而使得生成极其复杂的布局成为可能。此外,如果您需要某些在内置布局类型中不可用的类型,则可轻松编写新的布局元素类型。虽然 Windows 窗体可以比它的“前辈”提供强大得多的自动化布局实用工具(特别是在 .NET Framework 2.0 中),但是 WPF 将 UI 布局功能带入到一个全新的层次。
6. 灵活的内容模型
许多控件提供针对某种特定于应用程序的内容的占位符。例如,按钮通常具有使用户了解按钮功能的标签。一些控件有若干个占位符:选项卡控件允许标题放在每个选项卡以及选项卡体的内容中。但是,多数 UI 系统对于可用作标题或标签的内容有些限制。通常仅允许纯文本。在某些情况下,一个控件将尽可能允许您在旁边或文本中的某处显示位图。
WPF 提供一个完全不同的方法。相反,它的内容模型允许任何内容不受限制地用在此类占位符中。例如,Button 控件无需您提供文本 — 您可以在其中放置任何内容。您可以放置一个位图、一个图画,甚至是一个包含若干图画的布局面板,如以下代码所示:
从技术上讲,甚至将菜单或文本框用作 Button 的内容都是可能的,虽然这对可用性而言不是什么好事。图 12 显示一些可能性,虽然这个特定实例阐释了一些更糟糕的选择。
图 12 使用该内容模型
虽然该示例没有什么用,但是它阐释了内容模型的灵活性。由于它缺少限制,因此促生了很多可视化设计的可能性,在过去,这些可能性通常需要您编写自绘制代码。该新模型为可视化设计人员提供了一个非常理想的功能 — 设计人员现在可以原型化那些曾经需要开发人员输入的设计。
5. 无外观控件
虽然该内容模型使您能够自定义控件内容,但是 WPF 针对控件的 lookless 方法使您能够自定义一个控件的完整外观。这种任务通常要求编写自定义控件,因为外观一直是控件的组成部分。在许多 UI 技术中,控件的确切定义基本上是一个可视化组件。但是在 WPF 中却并非如此。
WPF 采取了将外观从控件中分离的基本步骤。多数 WPF 控件是没有外观的,这意味着它们只提供行为。该外观以模板形式单独提供。控件通常提供内置模板以便它可以具有一个默认外观。更好的是,它可以提供若干个模板 — 可能一个用于匹配 XP Luna 主题,另一个用于与 Aero 之间进行调和,第三个用于匹配传统的 Windows 2000 主题。但是,定义控件默认外观的模板可以更换,以便更改控件外观。
显示更换按钮模板的示例。ContentPresenter 元素是一个占位符,它通知 WPF 该按钮的内容应该在何处添加到结果中(如果没有它,该按钮的标题将不显示)。
当开发一个可重用的 UI 元素时,您需要仔细考虑它是什么类型的元素。如果您习惯于使用 Windows 窗体,您可能自然地倾向于使所有可视对象从 Control 派生。然而,这在 WPF 中通常是错误的选择。如果组件的目的是提供特定的交互(例如,Button 的可复选行为,或 ListBox 的显示和选择行为),则它应该是一个控件。但如果它的工作是提供一个具有非固有行为的特定可视化外观,它不应该是一个控件;相反,它应该从 FrameworkElement 派生,就像 Ellipse 和 Rectangle 通过 Shape 类派生一样。
控件具有行为以及(通常情况下)一个可更换的外观。这使得 WPF 控件比 Windows 窗体或 Win32 UI 组件灵活得多 — 您可以任意更改控件外观而不会丢失它的任何功能。
4. 数据驱动UI
我们已经讨论了 WPF 内容模型如何使您能够将所需的任何内容放置在某种控件中。在示例中,我们只嵌入了一些固定的内容。但是硬编码内容是相当枯燥的。创建一个动态更改显示内容的应用程序岂不是更令人兴奋。
您也可以完成这一任务。支持 WPF 内容模型的控件使您能够将任何东西用作内容,甚至是非 UI 对象。这意味着您可以编写如 所示的代码。这看起来可能比较特殊;您可能预期一个按钮的内容包含文本或某些 UI 元素。但是在该代码中,我们要将按钮设置为一个自定义类的实例。
诚然,该默认行为不是非常有用的,如图 15 中所示。WPF 不知道如何处理该对象,因此它只是调用了 ToString。这看起来可能没有进展。但是,即使默认情况下 WPF 不知道如何处理该对象,我们却可以通过提供一个数据模板告诉它如何做。
图 15
数据模板基本上是一组通知 WPF 如何显示对象的指令。您可以在一个特定上下文中显式分配一个数据模板。例如,在本例中,您可以设置 Button 的 ContentTemplate 属性,以便从 Person 对象使用 Name 属性,如下所示:
您无需在特定控件上设置该数据模板。如果您喜欢,也可以将一个数据模板与一个特定类型相关联,这表示无论 WPF 何时遇到作为内容的该特定类型,都应该使用该指定的模板。这里要将该模板放入资源部分(请参见)。请注意,该模板的功能更加强大。它显示来自源数据的所有信息,如图 17 所示。
默认情况下,该窗口中任何将 Person 对象用作其内容的控件都将采用该数据模板。如果您要将列表框的内容设置为 Person 对象数组,则该列表的每一行将使用该模板。
图 17 更丰富的数据模板
数据模板充当对象和 UI 之间的“桥梁”。这在管理数据呈现方式上为设计人员带来了极大的灵活性。使用基于 Win32 的技术获取相同的效果需要开发人员进行大量工作。
3. 一致的风格
如果您曾经使用过单词处理器,则您可能比较熟悉风格的概念。风格是一组属性,这些属性可以应用于文档的某个部分以便获取特定的外观。WPF 中的风格与此非常类似,但是它们可以应用于用户界面中的任何元素,而不仅仅是文档的某些部分。此外,WPF 风格可用于设置任何属性。例如,您可以创建一个将 Slider 控件的 Maximum 属性设置为 200 的风格。
虽然风格可用于设置任何属性,但是使用风格通常是为了确保跨应用程序的可视一致性。如果您想让应用程序传送一个旧式外观,则可以创建一个风格以便一致应用适当的属性。例如,该代码定义一个称为 oldeWorlde 的风格,该风格将字体设置为 Old English:
该风格也可以应用于您喜欢的任何控件:
oldeWorlde 风格是一个命名风格,而且仅应用于在其 Style 属性中显式请求它的控件。还可以定义一个在默认情况下应用于指定控件类型的风格。以下风格将自动应用于窗口中的所有按钮:
...
请注意,风格实际上就是属性和值列表。这意味着风格可用于应用模板 — 毕竟,Template 就是另一个属性。这提供了一个通过整个窗口或应用程序更改特定控件外观的简单方式。例如,我们在前面定义了一个用于按钮的自定义模板。实际上,我们可能不仅仅想将该模板应用到一个按钮上 — 如果我们在多个标准按钮中设置一个自定义按钮,那么我们的应用程序看起来将非常奇怪。通过一种风格,我们可以将该模板应用于所有的按钮上,如 所示。
这将使该窗口中的所有按钮使用该自定义模板。要更改所有窗口中的所有按钮,只需将该风格放在应用程序级的资源中。当您在 Visual Studio® 2005 中创建一个 WPF 项目时,该项目模板包括一个 MyApp.xaml 文件,该文件包含一个 Application.Resources 部分。您在该部分中定义的任何风格将应用于应用程序中的所有窗口。
这些风格提供了一个有用的机制,即在独立于应用程序行为和结构的情况下管理应用程序的外观。该分离使开发人员可以专门致力于定义应用程序的行为,而使设计人员专门致力于设计它的外观。
2. 触发器
触发器是一个声明性机制,用于指定控件应该如何响应某个操作。触发器可以在风格或模板内声明。例如,您可以使用触发器来指示按钮,当鼠标在其上时应该始终变成绿色:
触发器可用于激发动画。例如,您可能想使一个按钮在鼠标位于它上面时变成另一种颜色。动画在本质上与属性不同,原因是它们是暂时的。虽然我们可以将属性设置为一个值,但是我们无法将动画设置为一个值 — 相反,可以将动画设置为开始和停止。因此,动画触发器由事件驱动,而不是属性驱动的。
阐释如何使用 EventTrigger 元素以启动动画。该示例表明,当 Mouse.MouseEnter 事件发生时,Opacity 属性必须从其当前值更改为值 1。同样,当 Mouse.MouseLeave 事件发生时,属性必须从其当前值更改为值 0.5。
触发器是 WPF 的声明性方法的一个重要部分。这是能为设计人员提供更多帮助的另一个功能,它使开发人员能够设计一个无需开发人员的代码就能够响应操作的 UI。
1. 声明性编程
WPF 中最醒目的体系结构主题也许是声明性编程的用法。在声明性编程中,重点在于“做什么”而不是“如何做”。您声明要系统做什么,而不是列出它为此必须执行的一组操作。SQL 是一个规范的示例 — 您构造声明要检索信息的 SQL 查询;至于如何执行该查询则由数据库解决。
我们已经为您展示了依赖于声明性方法的几个示例。要提供自定义图形,您可以创建表示图形的对象,然后 WPF 会处理将这些图形呈现到屏幕的过程。包括在属性更改时更新该屏幕。同样,我们解释了如何创建特定的对象(声明如何为 UI 的某些部分制作动画),然后 WPF 将执行实际的动画。您也看到了布局元素如何用于声明 UI 的排列方式 — 无需以手动方式定位元素或者编写代码来确定它们的位置和大小。
声明性风格有很多优势。最重要的一点是,它通常可以产生更简单、更易于读取的程序。只声明需要做什么的代码通常比执行实际工作的代码短得多。请考虑前面显示的 Grid 示例,它以行和列定义开头,如下所示:
这是布局要求的一个简洁表达方式。它通知 Grid 元素我们需要两行和两列。它指示列应该按比例调整大小,而且第二列可用列宽应该为第一列可用列宽的五倍。对于这两行,它通知网格基于第一行的内容自动获取第一行的高度,并使第二行填充其余所有可用空间。想象一下,如果您要手动编写这些内容,将需要多少代码?
由于它显式阐明它的意图,因此计算出该声明性代码的操作通常更加容易。因此,无需维护开发人员进行大量工作来推断该代码的意图。声明性代码也可以使设计工具的工作更为出色。由于您明确提出了自己的意图,因此设计工具知道您要达到什么样的效果。此外,使用声明性编程,该框架在执行该工作时具有很大的自由度。这可以潜在启动重要的优化。
由于 WPF 允许以声明方式定义一个 UI 的行为的许多方面,因此用 XAML 表达这些行为非常简单。虽然布局和动画是应用程序运行时行为的一些方面,但是您可以使用 XAML 以声明方式定义这些行为,最终将该功能提供给设计人员,即使他们不是程序员也无所谓。