使用 XForms 库进行编程

作者:Thor Sigvaldason

在本系列的前两篇文章中,我们学习了如何安装 XForms 并开始构建一个应用程序(博弈论模拟器)。在最后一篇文章中,我们将改进我们的程序,并介绍一些我们在上个月跳过的 XForms 功能。与往常一样,源代码和更多信息可以在本系列的主页 http://a42.com/~thor/xforms 上找到。

回顾我们目前的进展

如果您一直在密切关注,您可能还记得构建基于 XForms 的应用程序的总体框架

  1. 包含 forms.h 以访问 XForms 例程

  2. 尽快调用 fl_initialize()

  3. 通过创建表单来设置您的图形界面

  4. 通过设置回调将操作分配给相关对象

  5. 显示一个或多个表单

  6. 将控制权交给 fl_do_forms()

上个月,我们按照这个步骤使我们的基本博弈论模拟器启动并运行。虽然这为我们提供了控制和观察底层模拟所需的基本窗口,但仍存在一些缺点。我们无法保存游戏的任何特定运行的设置,没有下拉菜单,也没有像素图来使我们的程序看起来更专业。新版本的模拟器(称为 xgtsim2)添加了所有这些元素,并包含一些我们在本文中讨论的其他花哨功能。

然而,基本方法没有改变。如果您能够理解上个月的源代码,那么即使 xgtsim2 是一个更大的软件,您也应该没有理解上的困难。程序的核心是相同的,因为我们所做的只是添加了一些功能。额外的功能使程序更有用,而 XForms 的一大优势在于它提供了增强程序可用性的直接方法。

xgtsim2 概览

您可以在主页上的 Listing 1 中找到 xgtsim2 的源代码,但如果您从网站下载它,将节省您大量时间。保存为名为 xgtsim.c 的 ASCII 文件后,可以使用以下命令编译

gcc -lX11 -lforms -lm xgtsim.c -o xgtsim

在 X Window 系统中,您应该能够通过在您编译它的任何目录中输入 ./xgtsim2 来运行该程序。运行的程序应该看起来像图 1。

图 1. xgtsim2 的屏幕截图

正如源代码顶部附近指出的那样,自上个月的程序(即原始 xgtsim)以来添加到 xgtsim2 的所有内容都用字符串 *NEW* 标记。这应该使您更容易找到我们在下面讨论的代码段。

新功能

运行 xgtsim2 时首先要注意的是,起始窗口 (main_window) 比上个月大一些,并且现在包含三个下拉菜单(FileSettingsHelp)。使用 XForms 添加这些菜单非常简单。如果您查看 create_forms() 函数的代码,您会看到我们添加了一个 FL_UP_BOX 来容纳菜单项,然后调用了三次 fl_add_menu()。严格来说,不一定需要包含 FL_UP_BOX,但这确实使我们的菜单区域从主窗口的其余部分中脱颖而出。

在第一次 fl_add_menu() 调用中,我们创建了文件菜单。请注意,这仅涉及一个 FL_OBJECT,菜单条目(例如,AboutLoad 等)通过使用 fl_addto_menu() 函数分配给此对象。也就是说,我们只需要为整个菜单分配一个回调,我们通过告诉 XForms 我们希望在用户选择我们的 File 菜单中的任何条目时调用函数 file_menu_routines() 来做到这一点。

在源代码的稍早部分,我们可以看到 file_menu_routines() 使用对 fl_get_menu() 的调用来确定实际选择了四个可能的条目中的哪一个。然后它调用适当的函数。例如,如果用户从 File 菜单中选择 About,XForms 知道它必须执行 file_menu_routines 中包含的代码,因为那是我们用回调分配的函数。在调用此函数时,它将文件菜单对象传递给例程,因此对 fl_get_menu() 的调用返回 1(因为 About 是菜单项 1)。

我们对 SettingsHelp 菜单使用了非常相似的结构,为每个菜单创建一个函数来处理回调(settings_menu_routines()help_menu_routines())。只需几行代码,我们就为程序添加了一个相当完整的菜单系统。XForms 在这里完成了大部分繁重的工作,例如在用户单击菜单时实际绘制菜单、突出显示条目等等。这使我们可以自由地专注于程序的基础流程。

我们尚未实现的菜单设计的一个常见元素是键盘快捷键的使用。XForms 允许通过 fl_set_object_shortcut() 函数使用这些加速器,并提供在某些条件下灰显菜单条目、更改菜单的视觉外观等的机制。有关这些例程的更多信息,请参见 XForms 文档(请参阅“资源”)。

添加到 create_forms() 的其他内容是对 fl_set_object_resize()fl_set_object_gravity() 的调用。了解这些功能的最简单方法是运行原始 xgtsim 并使用鼠标指针调整主窗口的大小。如果您这样做,您会注意到按钮始终以与整个窗口相同的速率增长;将窗口放大,按钮变得非常巨大。这不是很美观,因此我们希望使用 gravityresize 参数来改善此行为。

XForms 中的几乎所有图形元素都具有默认的 resize 设置,这会导致它们与创建它们的窗口成正比地增长。我们在 create_forms() 中通过调用 fl_set_object_resize() 来更改此行为。此函数接受两个参数:它应用的对象和一个设置值,该值可以是 FL_RESIZE_NONEFL_RESIZE_XFL_RESIZE_YFL_RESIZE_BOTH。对于容纳下拉菜单的 FL_UP_BOX,我们使用 FL_RESIZE_X 选项,因为我们希望菜单栏始终是屏幕的宽度,但保持恒定的高度。同样,我们对按钮使用 FL_RESIZE_NONE,以便无论窗口如何更改,它们都保持相同的大小。

对象 gravity 是一个相关的概念,它决定了对象应如何定向到绘制它们的窗口。再次以菜单栏为例,即使在用户将窗口调整为非常大的情况下,我们也不希望菜单向下漂移。函数 fl_set_object_gravity() 需要一个相关对象的参数和两个后续值,这些值决定了方向行为。第一个值确定当底层窗口更改时,对象的左上角应朝哪个方向移动。第二个值设置右下角的行为。由于我们始终希望 Help 菜单出现在菜单栏的右边缘(并保持在窗口的顶部),因此我们使用以下函数形式

fl_set_object_gravity(obj, FL_NorthEast, FL_NorthEast)

相反,我们希望 File 菜单保持在左侧,因此我们在该调用中将 FL_NorthEast 的两个实例都替换为 FL_NorthWest。只需程序员稍加思考,XForms 就可以轻松地拥有以有吸引力的方式调整大小的窗口。这可以为任何图形应用程序增加大量的润色,并使其在各种情况下都可用。

为了稍微修饰我们的程序,我们在 About 窗口中插入了一个像素图。实际的像素图数据存储在名为 xgtsim_logo 的变量中,该变量包含在源代码的末尾。然后我们需要两次调用来创建保存像素图的对象并将我们的数据分配给它

obj = fl_add_pixmap(FL_NORMAL_PIXMAP, 13, 13,
        70, 55,"")
fl_set_pixmap_data(obj, xgtsim_logo)

我们在 fl_add_pixmap() 调用中声明了像素图图像需要多少空间,但实际上是第二个函数分配数据。由于此像素图仅用于装饰,因此我们无需声明任何回调。要像 Netscape 等程序那样将像素图用作按钮,您需要查看 XForms 文档中的 fl_add_pixmapbutton() 函数。

徽标不特别艺术化的事实不应被视为 XForms 的缺点。我相当胜任编写 C 代码,但即使配备了 The GIMP,我也不是毕加索。

使用实用程序

通过包含菜单系统、调整大小参数和一些装饰性像素图,基于 XForms 的应用程序(如 xgtsim2)可以轻松地打磨成用户友好、有吸引力的软件。XForms 还提供了一系列易于添加的程序元素,称为“实用程序”。

一个实用程序的示例出现在 help_menu_routines() 的代码中。如果用户从帮助菜单中选择 Use,他会获得一个窗口,其中显示有关如何使用 xgtsim2 的信息。由于该程序只是一个示例,我们实际上没有为其编写任何帮助文件——我们只是想显示一个窗口,说明帮助尚未(尚未)实现。我们可以手动创建此窗口,添加一些文本对象、一个“OK”按钮等等;但是,这只是为了说明没有可用的帮助而做了大量工作。相反,我们使用一个名为 fl_show_alert() 的实用程序。此函数接受三行文本作为参数,以及一个整数值来确定随后的公告的位置(值 1 只是告诉 XForms 将窗口放置在显示器的中心)。通过一行代码,我们就可以轻松地向用户显示文本消息,而无需自己设计新窗口。

一个更强大的实用程序示例是 XForms 提供的文件请求器。从头开始编写一个这样的请求器可能需要花费大量时间,因为我们需要创建一个带有某种浏览器、打开和关闭按钮的窗口,实现一个过滤机制等等。fl_show_fselector 为我们完成了所有这些工作,并允许 xgtsim2 中的 load_config()save_config 函数非常简洁。该函数的完整形式如下

fl_show_fselector(const char *message,
        const char *directory,
        const char *pattern,
        const char *default)

四个字符串参数允许我们设置选择器的消息、要从中开始的特定目录、过滤模式,甚至默认文件名。所有这些都通过单个函数调用发生。文件选择器的一个有点微妙的功能是存在六个这样的选择器,如果 *directory 字符串的长度为 0,则每个选择器都会记住最后一个目录。在 xgtsim2 中,我们使用了其中两个,一个用于加载,另一个用于保存。在每种情况下,我们都在调用 fl_show_selector() 之前通过调用 fl_use_selector() 来声明显示哪个选择器。这样,如果用户从一个目录加载数据并将其保存在另一个目录中,他们就不需要在每次想要访问文件时都在目录之间来回单击。

还有一些机制可以将可配置按钮添加到选择器、设置窗口标题等等。任何设计过让用户加载和保存文件的方法的人都会欣赏为此小部件付出的思考和计划。

XForms 还提供了许多其他实用程序,包括从用户获取输入的例程 (fl_show_input)、其他消息显示例程 (fl_show_question),甚至还有一种快速简便的方法来获取颜色选择 (fl_show_colormap())。

自行尝试

本系列中我们尚未涉及 XForms 的许多方面,但 XForms 随附的文档非常出色,可以解释所有可用的资源。只需程序员稍加努力,该库就可以实现快速程序开发和专业外观。XForms 包中甚至包含一个表单设计器,使您可以使用鼠标设计界面。这使得创建复杂窗口变得轻而易举,并且该软件产生的输出可以轻松地合并到您的源代码中。

即使您永远不会使用 XForms 创建“杀手级应用程序”,放置 GUI 元素、分配回调和显示窗口的基本课程也可以合理地移植到其他编程环境和库。这些文章应该为您提供创建 X 程序所需的基本知识。套用 Donald Knuth 的话,前进并创造伟大的软件。

资源

Thor Sigvaldason 是统计程序 xldlas 的作者,该程序使用 XForms 库(参见 Linux Journal,1997 年 2 月)。他正在努力完成经济学博士学位,可以通过 thor@netcom.ca 与他联系。

加载 Disqus 评论