X/Motif 编程

本文将介绍在 X 和 Motif 中构建图形用户界面的基本概念。我将快速介绍 X Window 系统及其编程模型,然后介绍 Motif,并通过示例程序说明一些概念。最后,我们将介绍 Motif 编程的基本原则。
X Window 系统,或简称 X,最初是在麻省理工学院开发的,并得到了 Digital Equipment Corporation 的支持。X 的开发是为了满足雅典娜项目对分布式、硬件无关的用户界面平台的需求。
X 是一个主要用于 UNIX 系统的图形系统。它为显示窗口图形提供了一个固有的客户端/服务器导向框架。它提供了一种编写设备无关的图形和窗口软件的方法,这些软件可以很容易地从一台机器移植到另一台机器。
X 通过大量的子例程库提供功能。这些库可以从各种高级语言中调用。然而,它们最容易从 C 程序中调用。
服务器是控制显示的程序。它充当用户程序(即客户端或应用程序)和本地系统资源之间的桥梁。这些程序可以在本地或远程系统上运行。服务器执行以下职责
允许多个客户端访问。
解释来自客户端的网络消息。
允许二维图形显示。
维护本地资源,如窗口、光标、字体和图形。
X 中的客户端通常由两部分组成。一部分是图形用户界面,它使用 Xlib、Xt 或 Motif 中的一种或多种编写。另一部分是应用程序的算法或功能部分,其中从界面接收输入并定义处理任务。
Xlib 处理客户端应用程序和网络之间的接口。它是 X 软件架构的一部分。Xlib 的主要任务是将 C 数据结构和过程转换为 X 协议消息的特殊形式,然后发送出去。相反地,接收消息并将它们转换为 C 结构也是由 Xlib 执行的。
X Toolkit Intrinsics (Xt Intrinsics) 是一个工具包,允许程序员创建和使用 widget。工具包(如 Xt Intrinsics)实现了一组用户界面功能或应用程序环境,例如菜单、按钮或滚动条(称为 widget)。由于 Motif 构建在 Xt 之上,因此我们需要调用一些 Xt 函数。但是,我们不需要了解 Xt 的工作原理,因为 Motif 为我们处理了大部分事情。
客户端和服务器通过称为连接器的通信路径连接。这是通过称为 Xlib 的低级 C 语言接口实现的。诚然,许多应用程序可以仅使用 Xlib 编写,但总的来说,仅使用 Xlib 编写复杂的 GUI 程序将是困难且耗时的。已经开发了许多更高级别的子例程库(称为工具包)来解决这个问题。其中一个工具包是 Motif。
Motif 是由开放软件基金会及其成员公司在 1989 年左右开发并自此一直支持的一套被广泛接受的用户界面指南。这些规则规定了 X Window 系统应用程序应该“外观和感觉”如何。Motif 包括 Motif 工具包 (Xm),它在 X Toolkit Intrinsics (Xt) 之上强制执行策略。Xt 实际上是“机制,而不是策略”层,而 Xm 提供了特定的外观和感觉。例如,Xt 并不坚持窗口必须有标题栏或菜单,但它为特定工具包(Motif、OpenLook、Athena widgets)的开发人员提供了利用的钩子。Motif 还包括 Motif 风格指南文档,该文档详细说明了 Motif 用户界面应如何外观和行为才能“符合 Motif 标准”。
如图 1 所示,应用程序可以根据需要与窗口系统、操作系统和其他库的所有层进行交互。另一方面,应用程序的用户界面部分应尽可能限制自己使用 Motif、Xt 和 Xlib 库。
Widget 是图形用户界面 (GUI) 的基本构建块。对于在 Motif 中组装的大多数 GUI 来说,外观和行为相似是很常见且有益的。Motif 中的每个 widget 都提供了默认操作。Motif 还规定了应尽可能遵守的某些其他操作。有关 Motif GUI 设计的信息在 Motif 风格指南中提供。
每个 widget 都被定义为属于某个类。该类的所有 widget 都继承相同的资源和回调函数集。Motif 还定义了 widget 类的完整层次结构。有两个广泛的 Motif widget 类与我们相关。Primitive widget 类包含实际的 GUI 组件,例如按钮和文本 widget。Manager widget 类定义了包含其他 widget 的 widget。
Motif widget 可以被视为用户界面组件的通用抽象。Motif 为几乎每个常见的 GUI 组件提供 widget,包括按钮、菜单和滚动条。Motif 还提供了一些 widget,它们的唯一功能是控制其他 widget 的布局,从而可以轻松设计和组装相当高级的 GUI。
Widget 被设计为独立于应用程序运行,除非通过明确定义的交互(称为回调函数)。这从应用程序程序员那里消除了许多繁琐的 GUI 控制和维护工作。Widget 知道如何重绘和突出显示自己,以及如何响应某些事件,例如鼠标单击。一些 widget 更进一步;例如,Text widget 是一个功能齐全的文本编辑器,内置了剪切和粘贴以及其他常见的文本编辑功能。
Widget 非常有用,因为它们简化了 X 编程过程,并有助于保持应用程序的外观和感觉,从而使其更易于使用。
Motif 参考手册提供了关于 widget 行为和交互各个方面的定义。基本上,每个 widget 都被定义为一个 C 数据结构,其元素定义了 widget 的数据属性,或资源以及指向函数(例如回调)的指针。
每个 widget 的一般行为都定义为 Motif (Xm) 库的一部分。事实上,Xt 定义了某些 widget 的基类,这些基类构成了几乎所有基于 Xt 的 widget 集的通用基础。Motif 提供了一个 widget 集,即 Xm 库,它在 Xt 之上为大多数 GUI 需求定义了一整套 widget 类。
helloworld.c 程序解释了编写 Motif 程序时要遵循的步骤。
helloworld.c 创建一个窗口,其中包含一个按钮。该按钮包含字符串“Hello World!”。当按下按钮时,一个字符串(Hello to you too!)被打印到标准输出。该程序说明了 Motif GUI 和应用程序代码之间的简单接口。
该程序也会永远运行!这是事件驱动处理的一个关键特性。目前,我们将不得不使用操作系统退出我们的程序
使用 CTRL-c 从命令行退出。
使用窗口菜单“quit”选项。
按下鼠标右键,在窗口顶部周围按下,然后从菜单中选择“quit”选项。
helloworld.c 程序的完整程序清单在清单 1 中。helloworld.c 在屏幕上的显示效果将如图 2 所示。

图 2. helloworld.c 显示
要编译 Motif 程序,我们必须将其与 Motif、Xt 和 Xlib 库链接。我用于 helloworld.c 的编译命令是
gcc helloworld.c -o helloworld -lXm -lXt -lXext\ -lICE -lSM -lX11
请注意,此编译行在我的环境中是必需的。在您的环境中可能有所不同。您应该查看本地系统文档以获取确切的编译指令。
成功编译 Motif 程序后,命令
./helloworld &
将运行它并在屏幕上显示 PushButton,如图 2 所示。
编写 Motif 程序时,您将显式调用 Motif 和 Xt 函数以及数据结构。为了区分各种工具包,X 采用了以下约定
Motif 函数和数据结构名称以 Xm 开头,例如 XmStringCreateSimple 和 XmStringFree。
Xt Intrinsics 函数和大多数数据结构以 Xt 开头,例如 XtVaAppInitialize 和 XtVaCreateManagedWidget。widget 数据结构是此规则的例外。
Xlib 函数和大多数数据结构以 X 开头。helloworld.c 中没有使用 Xlib 函数。但是,Xlib 函数调用的一个示例是 XDrawString 或 XDrawLine。
任何使用 Motif 工具包的应用程序都必须包含它使用的每个 widget 的头文件。每个 Motif widget 都有自己的头文件,因此我们必须包含 pushbutton widget 的 Xm/PushB.h 文件,drawing widget 的 Xm/DrawingA.h 文件等等。但是,我们不必显式包含 Xt 头文件,因为 Xm/Xm.h 会自动执行此操作。每个 Motif 程序都将包含 Xm/Xm.h,这是 motif 库的通用头文件。
我们现在将详细分析 helloworld.c 程序。几乎所有 Motif 程序都必须遵循六个基本步骤。这些是
初始化工具包
Widget 创建
管理 widget
设置事件和回调函数
显示 widget 层次结构
进入主事件处理循环
Motif 程序的第一步是初始化 Xt Intrinsics 工具包。在应用程序创建任何 widget 之前,它必须初始化工具包。有几种初始化工具包的方法。最常见的是 XtVaAppInitialize。当调用 XtVaAppInitialize 函数时,将执行以下任务
应用程序连接到 X 显示。
命令行被解析为标准 X 命令行参数。
如果有,则使用 app-default 文件创建资源。
创建顶层窗口。
XtVaAppInitialize 接受多个参数。
应用程序上下文:XtVaAppInitialize 的第一个参数是应用程序上下文的地址,应用程序上下文是 Xt 用于管理与应用程序关联的一些内部数据的结构。对于我们正在考虑的 Motif 程序,我们不需要了解任何关于这方面的信息,除了我们需要在我们的程序中设置它。
应用程序类名:第二个参数是一个字符串,它定义了应用程序的类名。它用于引用和设置应用程序甚至应用程序集合共有的资源。
命令行参数:第三个和第四个参数指定对象列表作为特殊的 X 命令行参数。第三个参数是列表,第四个参数是列表中的数量。这是高级 X 用法,本文将不再进一步考虑。只需将第三个参数设置为 NULL,将第四个参数设置为 0。第五个和第六个参数 &argc 和 argv 包含给定的任何命令行参数的值。这些参数可用于以标准 C 方式接收数据的命令行输入(例如,程序要读取的文件名)。请注意,命令行可用于设置 X 中的某些资源。但是,如果这些资源在传递给程序的其余部分之前已正确解析并采取了操作,则它们将从 argv 列表中删除。
回退资源 提供针对其他设置机制中错误的安全性。如果资源通过任何其他方式设置(即,使用 app-default 文件),则会忽略它们。回退资源是以 NULL 结尾的字符串列表。目前,我们将简单地将其设置为 NULL,因为没有指定回退资源。
附加参数:第八个参数是以 NULL 结尾的资源、值对列表的开头,这些资源、值对应用于 XtVaAppInitialize 返回的顶层 widget。目前,它是一个以 NULL 结尾的列表,因为没有资源设置。
创建 widget 被称为实例化它。您向工具包请求特定 widget 类的实例,可以通过设置其资源来自定义该实例。可以在 Motif 中创建 widget,方法是使用用于创建每个 widget 的特定函数,或使用用于通用 widget 创建的便捷函数,甚至可以通过对 XtVaCreateManagedWidget 的单个函数调用来创建和管理 widget。
通常,我们使用函数 XmCreatewidgetname 创建 widget。要创建 pushbutton widget,我们使用 XmCreatePushButton。类似地,要创建菜单栏,我们使用 XmCreateMenuBar。
大多数 XmCreatewidgetname 函数接受四个参数
父 widget (helloworld.c 中的 topWidget)
创建的 widget 的名称,一个字符串 (“Hello World! Push me” 在 helloworld.c 中)
命令行/资源列表 (helloworld.c 中为 NULL)
列表中的参数数量
参数列表可用于设置 widget 资源,例如 widget 的初始高度和宽度。
创建 widget 后,必须对其进行管理。XtManageChild 是执行此任务的函数。widget 的父级管理子级的尺寸和位置,确定子级是否可见,并且还可以控制对子级的输入。
发生这种情况时,widget 的所有方面都置于其父级的控制之下。其中最重要的一点是,如果 widget 未被管理,即使父级被显示,它也会保持不可见。这提供了一种机制,我们可以使用该机制来控制 widget 的可见性。请注意,如果父 widget 未被管理,则即使子 widget 被管理,子 widget 也将保持不可见。
但是,有一个函数实际上创建和管理 widget。此函数称为 XtVaCreateManagedWidget,可用于创建和管理任何 widget。
事件被定义为任何鼠标操作(例如单击按钮或菜单栏选项)或键盘操作(例如按下 ENTER)或任何输入设备操作。事件的效果是多方面的,包括窗口大小调整、窗口重新定位和调用 GUI 中可用的函数。
创建 widget 后,它将自动响应某些内部事件,例如窗口管理器更改大小或颜色的请求以及在按下时更改其外观。这是因为 Xt 和 Motif 使应用程序程序摆脱了拦截和处理大多数这些事件的负担。但是,为了对应用程序程序员有用,widget 必须易于附加到应用程序函数。Widget 必须通过回调资源连接到应用程序函数。
X 异步处理事件。它基本上获取连续的事件流,然后将它们分派给应用程序,然后应用程序采取适当的措施。
如果您使用 Xlib 编写程序,则有许多低级函数可用于处理事件。然而,Xt 简化了事件处理任务,因为 widget 能够为我们处理许多事件,例如自动重绘和响应鼠标按下。
widget 的功能包括其对用户事件的响应行为。每个 widget 都定义了一个事件表,称为转换表,它会响应这些事件。转换表将每个事件或事件序列映射到一个或多个操作。有关每个 widget 响应的完整详细信息,请参阅 Motif 参考资料。
转换和操作允许 widget 类定义事件和 widget 函数之间的关联。对于任何应用程序程序,Motif 将仅提供 GUI。应用程序的主体将附加到 GUI,并且函数从 GUI 内的各种事件调用。
为了在 Motif 中做到这一点,我们必须添加我们自己的回调函数。在 helloworld.c 中,我们有一个函数 pushButton,它打印到标准输出。
函数 XtAddCallback 是将函数附加到 widget 的最常用函数。XtAddCallback 有四个参数
要在其中安装回调的 widget (helloworld.c 中的 button)。
回调资源的名称。在我们的示例中,我们设置了 XmNactivateCallback。
指向要调用的函数的指针。
客户端数据可能会传递给回调函数。在这里,我们不传递任何数据;它被设置为 NULL。
除了执行像突出显示 widget 这样的工作之外,每个事件操作还可以调用程序函数。
回调函数具有以下格式
void functionNameCallback (Widget w, XtPointer client_data, XmPushButtonCallbackStruct *cbs)
回调函数参数为
函数的第一个参数是与函数关联的 widget(在我们的例子中是 button)。
第二个参数用于将客户端数据传递给函数。它在我们的示例程序中未使用。
第三个参数是指向结构的指针,该结构包含特定于调用该函数的特定 widget 的数据以及有关触发调用的事件的信息。我们使用的结构是 XmPushButtonCallbackStruct,因为我们正在使用 PushButton Widget。
Ibrahim Haddad (ibrahim@ieee.org) 是加拿大蒙特利尔康考迪亚大学计算机科学系的博士生。Ibrahim 最初在黎巴嫩美国大学接触到 Linux (0.99) 和 Motif。他的兴趣包括电子商务、Web 应用程序、分布式对象以及帮助他在 LinuxLeb.com (Linux Lebanon) 的朋友。