Motif/Lesstif 应用程序开发
本文面向那些在愉快的开发领域中度过时光,并且发现越来越难以忽视图形前端增长的人。我的观点是,作为商业 UNIX 应用程序的开发者,这些应用程序主要分布在商业版本的 UNIX 上。我有兴趣使这些应用程序易于移植到 Linux,并发展在跨操作系统边界有用的技能。如果可以选择,我更喜欢使用可以在商业和开源世界中轻松应用的技术。
我的目标是让您基本了解 X/Motif 应用程序的基本组件——足以开始开发您自己的应用程序。服务器端程序或命令行界面 (CLI) 的开发往往比图形用户界面 (GUI) 的开发更直接。人类是出了名的不可预测,尤其是当人们习惯于按照严格的协议和明确的边界进行开发时。与 GUI 的复杂交互通常最好由工具包或库来处理——其他人可以担心鼠标指针在按下左键时碰巧在哪里,以及如何在屏幕上的组件上渲染那些漂亮的 3D 效果的细节。虽然每个 UNIX 都附带库来处理这些细节(X、Xt),但它们仍然非常繁琐且耗时。Motif 是一个库,它提供了足够的与底层原语的隔离,使 GUI 的开发变得现实,同时仍然允许在您需要时轻松访问这些原语。
严格来说,Motif 是开放软件基金会 (OSF) 提出的一个标准,它描述了应用程序的预期行为以及外观和风格。应用程序可以被描述为符合 Motif 标准;OSF 甚至为符合 Motif 标准的应用程序提供品牌推广。该标准的实际效用值得商榷;然而,Motif 库提供的框架的好处是毋庸置疑的。您可以构建一个使用库但不符合标准的 Motif 应用程序;事实上,大多数程序员只是使用库而忽略了更复杂的标准规定。
因此,当我说 Motif 时,我指的是 Motif 库或任何一个各种 API 兼容的库(Lesstif、Mootif、Moteeth 等)。Motif(v. 1.2)和 Lesstif 之间的差异将被指出。虽然 Motif 2.1 已经发布,但我将重点关注版本 1.2,因为这是大多数已安装客户端的修订级别,也是最常见的开源替代方案 Lesstif。较新版本的 Motif 通常向后兼容,尽管 API 的某些技术和元素可能已被弃用。版本 1.2 和 2.1 之间的最大差异包括线程安全库和一些额外的窗口小部件,例如 Notebook。
我决定使用 Motif 是出于几个非常具体的考虑,其中一些考虑会引起那些参与商业软件开发的人的共鸣。我在商业工作中使用的工具必须
经过尝试和验证——风险越小越好
易于目标最终用户使用
不会对开发时间造成不必要的负担
具有合理的学习曲线(为了我自己)
具有合理的许可政策,最好不对最终用户收取额外的运行时费用(为了营销)
利用相对容易找到的开发技能组合(对你的经理仁慈点)
Motif 库是一个满足我要求的工具包。几乎每个商业 UNIX 都附带 Motif 的运行时许可证。免费的 UNIX 变体(包括 Linux 和 BSD)也可以访问 Motif 库(尽管需要付费);然而,一个名为 Lesstif 的 Motif“克隆”现在已经足够稳定和成熟,可以代替“真正的”Motif 库使用。存在替代的 GUI 工具包(GTK、Qt 等),对于那些避开商业世界的项目来说,它们可能是个不错的选择——对于我们这些靠为商业发行版编写软件为生的人来说,我认为 Motif 仍然是一个不错的选择。
大多数商业 UNIX 操作系统附带的通用桌面环境 (CDE) 构建于 Motif 2.1 之上,其目的是为构建于版本 1.2 之上的应用程序提供向后兼容的 API。我发现我可以在 Solaris(使用 Motif)和 Linux(使用 Lesstif)上的同一个项目上工作,定期来回移动源代码,但出乎意料地很少出现问题。
最后,最终产品看起来很出色。Motif 可以轻松获得专业外观的 GUI,而无需任何额外的努力。3D 效果、文本剪贴板操作(剪切、复制和粘贴)以及用户期望从即使是很小的应用程序中获得的其他特性都自动处理,因此您可以专注于应用程序的问题域。
可以从 Red Hat 软件 (https://#/) 或 Open Group (http://www.opengroup.org/desktop/motif/) 获得 Motif 开发许可证。如果您必须为开发许可证付费,我强烈建议您使用 Lesstif 并找到一台已安装 Motif 运行时许可证的商业工作站。很可能,您可以调整任何错误的代码而无需购买 Motif。如果您有一台备用机器可以安装用于 Intel 平台的 Solaris,Sun 现在以媒体成本(约 10 美元)提供 Solaris 用于非商业用途;这包括 Motif 的运行时许可证和开发所需的头文件。在 Linux/Lesstif 上进行开发,然后在 Solaris/Motif 下进行快速构建以检查兼容性是相当容易的。
但请记住,Lesstif 是 Motif 版本 1.2 的实现,对 Motif 2.0 的支持有限,而商业实现的 Motif 2.1 已经发布。好消息是绝大多数系统仍然使用 Motif 1.2。
可以从许多地方下载 Lesstif,首先是 http://www.hungryprogrammers.org/。二进制文件可以从该站点及其镜像站点下载,格式为 RPM 或 gzipped tar 文件,适用于 Linux(和一些其他 UNIX 版本)。在我的基于 Red Hat 的工作站上安装 Lesstif 非常简单,只需键入
rpm -i lesstif.rpm
除非您计划为 Lesstif 项目做出贡献,否则您不需要开发 RPM。
对于大多数商业 UNIX 系统,开发人员无需执行任何额外的操作即可开始使用 Motif 进行开发;例如,Sun 在 Solaris 中附带了 Motif 头文件和库。如果您为 Linux 选择商业 Motif 许可证,则需要按照软件包中概述的步骤进行操作。在 Slackware 或其他非基于 RPM 的系统上启动程序相当简单。libXm 文件应最终位于 /usr/X11R6/lib 目录(或 Solaris 上的 /usr/dt/lib)中,头文件应放置在 /X11 include 目录下的其自身目录中(名为 /Xm),例如 /usr/X11R6/include/Xm 或 /usr/dt/include/Xm。

图 1. 使用 Motif 访问 X
在 Motif 应用程序中访问 X Window 系统主要通过三个库完成:X 库(通常称为 Xlib),它提供最原始的接口;X 工具包 (Xt),Motif 构建于其之上;以及 Motif(见图 1)。如果您认真对待 GUI 开发,那么一本涵盖 Xt 的书,或者至少是Motif 编程手册,将是一项不错的投资。许多库选择构建在 Xt 而不是 X 之上,因此可以合理地期望 X 和 Xt 在 UNIX 版本之间保持一致,并且在任何最终用户配置上都可用。
X 库函数通常以“X”为前缀,X 工具包窗口小部件和函数以“Xt”为前缀。虽然工具包在 Motif 应用程序中被大量使用,但访问 X 库例程的需求很少见。Motif 提供的符号(函数、常量和变量)以“Xm”为前缀。
X 服务器通过窗口管理器负责与视频硬件交互,并将消息和事件分派给应用程序。重要的是要注意,在 X 下运行的应用程序是使用事件驱动的范例实现的。程序通常花费时间等待接收指示诸如鼠标移动、按键或暴露窗口等事件的消息——稍后会详细介绍。
用于构建 X/Motif 应用程序的图形组件称为窗口小部件。一些简单的窗口小部件是按钮、单选按钮和文本字段;更复杂的窗口小部件可能是文件选择对话框或 HTML 窗口小部件。许多图形组件是在例程或 C++ 类中耦合的窗口小部件的组合。您可能会花费您的职业生涯开发 GUI,而永远不会学习如何创建自定义窗口小部件。最好避免编写自定义窗口小部件,直到您非常熟悉 X/Motif 环境;即使那样,您也应该有充分的理由不使用更传统的技术。Motif 窗口小部件库以非常面向对象的方式实现(特别是对于完全用 C 编写的东西)。如果您对这类事情感兴趣,我强烈建议您研究窗口小部件库(请参阅 Lesstif 源代码)。有许多窗口小部件可用于实现 Motif 库未解决的功能。
可以使用许多通用例程来创建和更改窗口小部件的实例(例如,XtCreateManagedWidget);此外,窗口小部件通常会提供一些特定的例程(例如,XmCreateScrolledText)。窗口小部件的运行时行为通过资源来控制;也就是说,特定于窗口小部件单个实例的变量,这些变量确定其行为的各个方面,例如位置、颜色、按钮标签等。
掌握一项新技术的最好方法是查看使用它构建的东西。Motif 提供了丰富且可扩展的环境,要解释我将采取的步骤背后的所有细节需要很长时间;事实上,我只会触及可以使用 Motif 完成的事情的表面。我将用来演示 Motif 的应用程序是一个简单的文本文件查看器(见图 2)。您应该从 ftp.linuxjournal.com/pub/lj/listings/issue64/3392.tgz 下载源代码(很长)。当我解释 Motif API 的不同方面时,我会引用特定的行号。
Motif(和 X)开发利用“事件驱动编程”,使用事件分派循环。应用程序必须在将控制权交给处理事件的循环(通过 XtAppMainLoop)之前设置界面(或至少一些最小组件)。一旦调用事件循环,您的代码将仅响应事件而被调用。重要的是要记住,只要您的代码正在运行,您的应用程序就不会与 X 服务器通信。您必须注意定期将控制权返回给事件循环,尤其是在耗时的操作期间。回调函数被大量使用,回调函数旨在响应特定消息由事件循环调用。Motif 和 Xt 库(Motif 构建于其之上)处理应用程序可能接收到的大多数事件——您必须处理的唯一事件是默认行为不足以应对的事件。
编译 Motif 应用程序并不太复杂——您必须确保 Motif 头文件可用,并且库在链接命令中以正确的顺序列出。以下命令将在 Linux 上使用 Lesstif 或 Motif 构建示例应用程序
gcc -o xtxtvw xtxtvw.c -I/usr/X11R6/include\ -L/usr/X11R6/lib -lXm -lXmu -lXt -lX11
要在 Solaris 上构建应用程序,请发出以下命令
gcc -o xtxtvw xtxtvw.c -I/usr/dt/include\ -L/usr/openwin/lib -lXm -lXmu -lXt -lX11如果这些不起作用,请在包含您的 X Window 系统的目录树中查找名为 /include/Xm 的目录,并指定该目录。库通常位于 X 库目录中——搜索名为 libXm* 的文件。Motif 最常以共享库的形式提供;版本将因您的系统而异。如果您找不到其中一个或另一个(尤其是头文件),则您可能没有加载 X 或 Motif 开发包。
让我们首先检查用于初始化应用程序的代码,以便涵盖一些基本要素。为了使国际软件的开发人员的生活更轻松,已经做了相当多的工作;本地化和国际化通过 X 和 Motif 库中提供的许多功能来解决。我将完全避免处理这个问题,除了说您应该养成在所有 X 应用程序中包含对 XtSetLanguageProc 的调用的习惯。
我调用的第二个工具包函数是 XtAppInitialize(第 85 行),它有一些有趣的调用参数。此函数负责初始化 Xt 库,评估旨在用于工具包的任何命令行参数(例如几何图形),并初始化应用程序使用的 X 资源。context 是 X 工具包使用的返回参数;您通常不会对这个参数做太多事情,除了将其传递给主事件循环。它是库的工作区——它们在其中存储有关应用程序中窗口小部件实例的状态信息的位置。第二个参数是应用程序的class。它用于通过内部和外部客户端(例如 editres)引用属于应用程序的资源。
XtAppInitialize 的第五个和第六个参数是在命令行上传递的参数计数和参数列表。X 工具包将消耗任何旨在用于它的命令行选项,例如应用程序主窗口的位置和初始大小以及默认背景颜色。请参阅 X11 手册页,以更好地了解确切接受哪些选项。在此调用之后,您的应用程序可以处理剩余的选项。
另一个感兴趣的参数是回退资源字符串数组。这是可选的,但这是一个好主意,因为它指定了应用程序中窗口小部件使用的资源的默认值。命令行选项和任何硬编码值将覆盖此处列出的设置,但您应该养成至少指定背景色和前景色(以及可以说字体)的习惯,以帮助保持界面外观一致。
由 XtAppInitialize 创建的窗口小部件是应用程序外壳,其中包含在桌面上显示为应用程序的窗口。这是一个特殊的窗口小部件,它提供与窗口管理器的交互。如果需要多个主窗口,应用程序可以有多个外壳。此示例应用程序只有一个外壳,最终成为所有其他窗口小部件的父级。
应用程序和该应用程序中使用的窗口小部件根据其资源值显示在显示器上。即使是一个简单的文本标签也有一组 X 用来确定其外观的资源。最好将资源视为窗口小部件特定实例的数据成员。资源的一些示例是颜色、标签或文本输入窗口小部件的文本、大小、位置、字体和事件的回调例程。窗口小部件以层次结构构建:每个窗口小部件都有一个父窗口小部件,并且每个窗口小部件可以有零个或多个子窗口小部件。每个资源都是一个名称-值对。名称由应用程序类、层次结构中每个窗口小部件的名称以及实际资源名称按顺序组成。
X 和 Motif 定义了许多可用作资源值的原始类型,例如 XmString(Motif 显示字符串)、Widget(任何窗口小部件)、Position(位置)和 XtPointer(用于将任何类型的数据传递给回调函数)。可以使用 XtSetValue 和 XtGetValue 检索和设置资源值。(还有这些函数的 XtVa* 版本。)如果您为值指定了错误的类型,您将直到运行时才会发现,因此最好使用手册页或参考手册验证类型。密切关注应用程序的 STDERR 输出以查找来自 X 库的消息也很重要。
窗口小部件资源的名称示例为“.xtxtvw.title”(应用程序窗口标题栏中的文本)。它可以在 ~/.Xdefaults 文件中指定为应用程序资源“XTxtVw.xtxtvw.title”。我鼓励您使用 editres 探索资源名称(请参阅关于调试的部分)。X 服务器维护所有应用程序的资源名称数据库。这些规范来自许多来源,包括窗口管理器的初始化文件(例如 ~/.fvwmrc)、X 默认值 (~/.Xdefaults)、应用程序特定的默认值(在 Linux 上为 /usr/X11R6/lib/X11/app-defaults,在 Solaris 上为 /usr/openwin/lib/app-defaults)以及应用程序的运行时规范。
xrdb 命令提供了一种在运行时查看和更改数据库中资源设置的方法(更改仅影响之后创建的窗口小部件),尽管对于支持它的应用程序来说,editres 更容易使用。查看 xrdb 的手册页,但请注意它通常替换数据库设置。如果要合并其他设置,则必须使用 -merge 选项指定。
我在传递给初始化函数的回退资源中指定了一些应用程序资源。可以在 setMainTitle 例程中看到设置一些窗口小部件资源的简单示例。
一些窗口小部件旨在处理其他窗口小部件的布局和事件管理。这些称为管理器窗口小部件,包括 XmForm、XmBulletinBoard 和 XmRowColumn。许多从管理器窗口小部件派生的窗口小部件提供增强的行为,例如 XmSimpleMenubar。
XmBulletinBoard 提供了在特定位置放置窗口小部件的方法——您必须指定公告板中的 X 和 Y 偏移量。此窗口小部件通常不单独使用,而是用于派生其他更有用的窗口小部件,例如 XmForm。XmForm 允许您指定窗口小部件相对于表单中其他窗口小部件的位置。这可能是最有用和最常用的管理器,因为它自动处理运行时其子窗口小部件的大小调整和动态更改。
创建窗口小部件后,在管理它及其层次结构中的每个窗口小部件之前,它实际上不会显示。作为 XmForm 窗口小部件的子窗口小部件创建的窗口小部件在通过调用 XtManageChild 管理表单窗口小部件之前不会出现在屏幕上。这将导致表单中的每个托管窗口小部件突然出现。通常,您需要创建一个管理器窗口小部件作为顶级外壳的单个子窗口小部件,然后使用该窗口小部件作为其余 GUI 组件的父级(第 103 行)。
应用程序中使用的另一个窗口小部件是 XmLabel(第 157 行)。这用于简单地输出文本字符串和通常不与用户交互的图像。因此,标签通常创建为 Gadgets,这是一种特殊的窗口小部件,它将其大部分事件处理推迟到其父级。这意味着注册的回调函数更少,分派的事件更少,并且通常有助于提高性能。当我不希望窗口小部件与用户交互或当存在特定窗口小部件的许多实例(例如表中的行)时,我尝试使用小工具。按钮(第 173 行)派生自 XmLabel。
XmTextField 窗口小部件(第 164 行)用于提供单行数据输入字段或更灵活的输出机制。文本字段是一种专门的文本窗口小部件,适用于 XmText 窗口小部件功能过剩的情况。XmText 窗口小部件可以完成 XmTextField 可以完成的所有操作,甚至更多。XmScrolledText 窗口小部件(第 202 行)用于显示大量文本,并且实际上打包了足够的功能,可以用作不错的文本编辑器。事实上,您可以通过在示例应用程序中添加少于 20 行代码来实现文本编辑器。
Motif 对旨在用于显示器的字符串使用特殊的表示形式——通常是 XmString 类型。许多窗口小部件资源需要 XmString 类型的值,创建它们最常用的方法是通过调用 XmStringCreateLocalized(第 110 行)。请注意,为创建的字符串分配了存储空间,并且在不再需要时必须通过调用 XmStringFree 返回(第 124 行)。如果应用程序必须在稍后时间国际化,则以这种方式构造的字符串将以本地语言和字体显示。
也可以使用 XmStringGetLtoR 从窗口小部件资源中检索字符串(第 277 行);此调用使用 Xt 库(XtMalloc)中提供的内存管理例程为字符数组分配存储空间。使用此技术创建的字符串的存储空间必须使用 XtFree 释放(第 284 行)。Motif 提供的一些便利函数,例如 XmTextGetString(第 323 行),也使用 Xt 内存管理,其结果必须使用 XtFree 释放。
“普通”字符数组仍然在 Motif 中的许多地方使用,但重要的是要认识到 XmString 将会到处弹出。这是 C 的松散类型特性将再次暴露其丑陋之处的另一个领域。请注意释放与这些函数关联的存储空间——在使用这些函数的应用程序中,内存泄漏非常常见。
在第 114 行创建的菜单栏说明了一些重要问题;最明显的是函数接受的可变参数列表。Xt 和 Motif 函数严重依赖可变参数列表;这样做的好处是语义简化,缺点是没有编译时类型检查。防止错误的一个好方法是在源代码中仔细地将参数列表列成列——这有助于提供一个视觉检查,以确保正确构造了调用。参数列表中的某些错误会在运行时显示在 STDERR 上,而有些错误则会悄无声息地过去。
该函数的第一个参数是将成为正在创建的窗口小部件的父级的窗口小部件;第二个参数是窗口小部件的名称,因为它将显示在资源数据库中(尝试使用 editres 找到它)。其余参数是资源规范,形式为资源及其关联值的对。整个列表始终以 NULL 结尾。
从第 194 行开始使用的创建窗口小部件的另一种方法需要您生成参数列表。create 调用类似于可变参数列表,父级和窗口小部件名称是前两个参数,后跟列表中的参数数量和列表本身。通常的做法是定义类型为 Arg(来自 Xt)的数组,并在每次调用中重复使用它。虽然固定长度的参数列表有点麻烦,但有些窗口小部件不提供可变参数创建调用。此外,我在 gcc 2.7.2 和一些可变参数函数中遇到了问题——这与 Lesstif 使用的宏有关。如果您收到编译器对可变参数列表函数之一的奇怪投诉,您可能需要尝试将其转换为固定长度的参数列表函数。我没有在较新版本的 gcc 和 Lesstif 中遇到过这些类型的问题。
未在创建函数的调用中指定的资源将从 X 服务器维护的资源数据库和窗口小部件特定的默认值中获取值。您也可以稍后通过调用 XtSetValues(第 376 行)或 XtVaSetValues(第 181 行)来更改它们。事实上,这是用于在运行时更改界面外观的最常用方法。一些 Motif 窗口小部件还提供便利函数来完成相同的操作(例如 XmTextSetString)。它们具有提供编译时类型检查的明显优势,并且通常呈现更简单的语义。
窗口小部件通常创建为未管理的——这会将它们的显示推迟到它们被管理为止。事实上,默认情况下,窗口小部件未被管理,并且需要调用 XtManageChild 才能使它们实际显示。有一种构建从创建时起就已管理的窗口小部件的方法,即通过使用 XtVaCreateManagedWidget 和 XtCreateManagedWidget 函数(第 157 行)。由于这是对 Xt 库的调用,因此初始参数的顺序与 Motif 语法略有不同。第一个是窗口小部件的名称(通常是 Motif 创建函数中的第二个参数);第二个参数是窗口小部件类,这是一个常量,它与 Motif 创建函数中的名称匹配,第一个字符为小写。第三个参数是父窗口小部件,后跟资源名称和值。
Motif 提供了一个简单的菜单栏,它易于构建,但对菜单行为做出了一些假设。对于具有更灵活的小部件和更复杂的菜单,您将需要探索 XmMenuBar。请注意,菜单栏是作为表单的子级创建的。在第 114 行构建的菜单栏将单个菜单指定为级联按钮(第 115 行)。如果您想要多个下拉菜单,只需添加更多行即可。字符串是菜单在菜单栏中显示的名称,单个字母是导致菜单下拉的元键击(在本例中为 alt-F 或 meta-F)。
构造菜单栏后,可以将实际的下拉菜单创建为菜单栏窗口小部件的子级。第 133 行指定了“文件”菜单的父窗口小部件和名称;参数指示初始项和用户选择菜单项时调用的回调函数。随后的每一行都描述一个菜单项,每个项都以其类型开头。菜单可以包含的不仅仅是传统的按钮;有关更多信息,请参阅Motif 编程手册。下一个参数是为菜单项显示的字符串,后跟用户在显示菜单时可以按下的热键,然后是用户可以按下以调用菜单项而无需实际下拉菜单的加速键序列。该项的最后一个参数是加速键击的显示字符串——这将右对齐显示在菜单中。
可以通过为类型指定 XmSEPARATOR(不带关联值)将水平线(分隔符)放置在菜单中,如第 135 行所示。这是另一个将源代码列成非常好的主意的情况。很容易遗漏其中一项,从而导致运行时灾难性(或至少是奇怪的)行为。如果您不想指定其中一个选项,则必须使用占位符,例如空字符串。
一旦创建了所有下拉菜单,就必须管理菜单栏(第 142 行),以便在管理表单时,它可以自由显示菜单栏。在这种情况下(与某些特殊的管理器部件一样),不需要管理下拉菜单。记住,它们实际上只有在用户执行某些操作使其出现时才会显示。
我们实例化的下一个部件是文件选择对话框。它实际上不会出现,直到用户调用它(通过菜单选择或按钮按下),但我选择在这里构建它,因为它在逻辑上与文件菜单相关。Motif 提供了许多非常好的特殊对话框;其中最有趣的是文件选择对话框 XmFileSelectionDialog。这个部件是从 XmSelectionBox 派生出来的,所以当你在 Motif 参考手册 中查找某些资源时,你可能需要参考这两个部件。
有时,在创建部件之后分配回调可能更方便,要么是因为你当时没有所需的所有信息,要么是因为回调不能像我们的示例中那样容易地提供给创建函数。另一个原因是显式调用 XtAddCallback 使分配变得显而易见;此外,你还可以获得编译时类型检查的好处。
标准的 文件选择对话框 为你处理所有繁重的工作,包括文件名全局匹配、目录导航甚至文件名过滤。默认对话框还提供了一些我不会使用的额外按钮。为了避免混淆用户,请将其 XmNSensitive 资源设置为 false(使其灰显)或完全删除它们。Motif 为所有从 Selection Box 派生的部件提供了一个方便的函数,用于获取对话框中特定子部件的部件 ID(第 150 行),即 XmSelectionBoxGetChild。一旦我有了部件 ID,调用 XtUnmanageChild 将阻止该特定部件在对话框的其余部分显示时出现。
第 157 到 204 行的代码与将剩余组件添加到显示有关。我将使用这些部件来解释 XmForm 如何处理其子部件的布局。附件描述了部件与表单内其他部件的关系;可能需要一段时间才能习惯附件的工作方式。部件按照它们被创建的顺序出现在表单中——在编码时要记住这一点。我将描述表单附件最基本的方面。
每个由表单作为父对象的部件都具有顶部、底部、左侧和右侧的附件资源。我将介绍如何使用 XmATTACH_FORM 和 XmATTACH_WIDGET 以及附件偏移量。其他附件类型包括 XmATTACH_NONE、XmATTACH_SELF、XmATTACH_POSITION 以及一些基于表单和部件相对侧的附件类型。在第 116 行,你看到菜单栏的顶部、左侧和右侧都附加到表单上。这意味着每一侧都将“粘附”到表单的相应侧,根据需要拉伸部件。底部附件保留为 XmATTACH_NONE,因为我需要使用表单的剩余部分来显示其他元素。如果未指定附件,则菜单栏的大小和位置将默认为表单左上角一个相当难看的框。
在第 158 行,我指定标签的顶部附加到菜单栏部件,第 159 行指定此附件将保持距菜单 10 像素的距离。标签的左侧附加到表单,右侧和底部保持未附加。如果指定了附件并且标签的边框可见,你将看到它随着其他部件在调整大小操作期间位置的变化而拉伸。
附件中的错误可能会完全隐藏部件或产生难看的屏幕;在某些情况下,错误严重到会被打印到 STDERR 上(例如,循环依赖)。如果你指定了部件附件,你还必须指定该侧应附加到的部件。由于没有对这些东西进行编译时检查,因此你不会在运行时才发现你的遗漏。
我希望用于输入文件名的文本字段水平拉伸;因此,一旦创建了“浏览”按钮,我就向文本字段添加了最终附件(第 181 行)。这是使用 XmForm 进行布局的一个极好的强度示例。
一旦构建了接口,对应用程序的顶层 shell 调用 XtRealizeWidget(第 215 行)将导致由该 shell 作为父对象的层次结构中所有被管理的部件显示出来。部件可以在运行时构造和销毁。你不必在公开它们之前创建所有部件;事实上,在某些情况下,你将没有足够的信息来这样做,但应用程序主屏幕的大部分内容应在启动时显示。
调用 XtAppMainLoop(第 217 行)将控制权移交给事件分发循环。从这一点开始,应用程序中的代码执行的唯一方式是响应事件。许多事件(例如显示和隐藏)由 Xt 库自动处理。通常最好假设 XtAppMainLoop 永远不会返回。应用程序通常提供一个例程来处理关闭任务,例如关闭文件和清理临时工作区。例如,此例程将从“退出”菜单项中调用。
为各种部件安装的回调函数实际上将为你的应用程序赋予生命。诸如响应鼠标按钮按下和键盘输入之类的普通任务都由支持实例化的部件的库处理。这可能会使应用程序难以调试,因为控制流相当灵活,并且它强调了仔细设计的重要性。回调调用调用其他部件操作的其他函数——你明白了。没有简单的方法可以自动生成这种实现的调用图。
如果你的应用程序需要执行耗时的操作,最好使用协进程,或者至少定期调用 XmUpdateDisplay(如第 293 行所示)。这将防止 GUI 看起来被锁定。还有许多不同的方法可用于实现进度条和其他用户反馈。你必须记住,只有当你的应用程序未执行你的代码时,它才能对用户的输入做出反应。
回调最初可能让人感到难以处理,但它们只不过是指向放置在事件表中的函数的指针。当它们注册的事件发生时,事件分发循环会使用一些参数调用它们。由于回调传递的状态信息有限,因此必须从环境中检索大部分信息。
回调与事件显式或隐式关联。第 133 行对 XmVaCreateSimplePulldownMenu 的调用隐式注册 fileCB,以便在用户选择菜单项时调用它。第 147 行对 XtAddCallback 的调用注册 fileselCB,以便在文件选择对话框中的“确定”按钮被按下时调用它。
当 Xt 或 Motif 调用回调时,它会传递注册回调的部件、单个客户端指定的数据以及特定于回调被调用的部件和事件的回调数据结构。当通过 XtAddCallback 将回调添加到部件时,函数的最后一个参数是用户指定的数据,类型为 XtPointer。这为你提供了一个钩子,可以通过指向结构的指针传递信息。然后,用户数据元素将在回调中强制转换回原始类型——在这里要非常小心,除非你喜欢生成核心文件。
call_data 参数通常是从基本回调结构派生的回调结构的实例——所有 Xm*CallbackStruct 类型中的前几个元素都是相同的。对于 XmFileSelectionBox,结构定义为
typedef struct { int reason; XEvent * event; XmString value; int length; XmString mask; int mask_length; XmString dir; int dir_length; XmString pattern; int pattern_length; } XmFileSelectionBoxCallbackStruct;
每个元素的含义在 Motif 参考手册 中详细解释,我在第 275 行和 277 行使用了 reason 和 value 元素。reason 告诉你回调被调用的原因;这对于像文件选择框这样的东西尤其重要,因为通常为多个事件使用相同的回调。value 成员是实际选择的文件(其完整路径)。快速查看屏幕上的文件选择框实例将使你很好地了解其他元素是如何使用的。如果你不确定运行时的回调结构类型,则应将其强制转换为 XmAnyCallbackStruct 类型。这至少具有可用于确定调用性质的 reason 和 event 成员。
由于大量使用回调函数,调试 Motif 应用程序可能具有挑战性。我发现可靠的 printf 通常是找出异常的最快方法。库依赖类型转换来实现部件库的面向对象设计的方式并没有改善这种情况。参考函数签名的文档,除非你绝对确定你理解正确。
一个名为 editres 的程序提供了一种机制,可以在运行时快速调整资源设置。通过将程序指向你正在运行的应用程序,你可以检索部件树并检查或更改几乎每个部件实例的资源。这是尝试替代字体、颜色、附件和标签的好方法。正确链接的应用程序可以遵循 editres 协议,托管与外部世界的双向通信链接,而无需更改一行代码。
为了为你的应用程序添加对 editres 的支持,请添加以下两行
#include <X11/Xmu/Editres.h> XtAddEventHandler(g_topshell, (EventMask) 0,\ True,\ (XtEventHandler) _XEditResCheckMessages, 0);
并链接 Xmu 库——将其放在链接命令中 Motif 库之后。Kenton Lee 在 www.rahul.net/kenton/editres.html 上发表了一篇关于使用 editres 的好文章。editres 的源代码包含在 X 发行版中,因此任何运行 X 的机器都应该有该实用程序。
使用此处描述的技术编写应用程序有时可能有点乏味,尽管它可以为你提供对接口的最大灵活性和控制。开发 Motif 应用程序有很多不同的方法,包括包装器库和 GUI 构建器。最常见的两种是用户界面语言 (UIL) 和 Motif Tools 库。两者都减少了编码所需的工作量,并且各有不同的优点。
UIL 是一种类似 C 的描述语言,用于创建通过 Motif 和其他 Xt 部件实现的界面。UIL 文件被编译并在运行时馈送到 Motif 资源管理器 (MRM);然后 MRM 呈现界面、安装回调等。UIL 的主要优点是屏幕的快速布局、广泛的错误检查、更轻松的国际化和更简单的代码。UIL 的主要缺点是灵活性降低(由于语言的简单性),这可能需要在 Motif 中进行额外的编码、学习另一种语言以及不稳定性问题。对于有抱负的 Lesstif 开发人员来说,另一个问题是 UIL/MRM 尚未完全支持。
Motif Tools (Xmt) 是 O'Reilly 图书 Motif Tools (David Flanagan 著)附带的库。该库提供了一些额外的部件和大量支持函数,使你可以使用资源文件进行编程。这种方法的优点是提供了更大的运行时灵活性,因为资源文件是在运行时读取的。对 GUI 的细微更改可能不需要你重新编译,最终用户可以编辑资源文件以根据自己的喜好更改界面。此外,应用程序代码可以显着简化,因为你依靠 Xmt 来完成详细的实现工作。Xmt 确实具有相当陡峭的学习曲线,并且可能仍然缺乏直接使用 Motif API 提供的某些灵活性。
我在本文中坚持使用 C,因为我想涵盖的内容太多了;但是,C++ 也非常适合 Motif 开发。最大的问题是回调函数必须是静态成员函数或全局函数。事实上,用户界面特别适合 GUI 开发,因为你有可以由 C++ 对象表示的可视对象。此外,C++ 带来的封装可以使程序更模块化且更易于维护。
