XForms:评测与教程
在本文中,我想向您介绍 XForms,这是一个用于 C 和 C++ 的图形用户界面 (GUI) 工具包,我几个月前发现的。我一直在为多个平台(当然包括 Linux)编写程序,最近一直在寻找一个适用于 X 的优秀 GUI 工具包:一个可以工作、可以免费获得,并且(最重要的是)您可以立即使用,而无需任何 X 内部机制知识的工具包。然后我听说了 XForms,下载并试用了它。我在不到一天的时间里编写了我的第一个 X 程序。令人印象深刻!
如果您曾经看过关于裸 X 库的程序员书籍,您就会理解我所说的困难。仅使用 X11 本身编写“hello world”程序绝非易事。即使是最小的程序也需要数百行 C 代码。
这就是工具包的用武之地。GUI 工具包是一个函数库,它使用户界面的创建和维护对程序员更加友好。当然,更知名的软件包之一是 Motif。如果您幸运的话,您购买的 Motif 软件包附带一个代码生成器,使您可以定义一个简化的 GUI,然后将其转换为 C 代码。如果您非常幸运,您的软件包会有一个交互式 GUI 构建器,您可以在其中看到您的界面在编码后的外观。然而,许多 GUI 工具包都很昂贵;而且许多 GUI 工具包对于 X 编程新手来说一点也不友好。
XForms GUI 软件包不仅免费(用于非商业应用),而且它还可以制作美观的程序,拥有出色的交互式设计构建器,附带良好的文档,并且非常易于新手使用。
XForms 由 T.C. Zhao 和 Mark Overmars 编写。
您可以通过匿名 ftp 在以下位置获取 XForms
ftp://bloch.phys.uwm.edu/pub/xforms 和ftp://ftp.cs.ruu.nl/pub/XFORMS
您应该下载文档和 XForms 工具包存档本身。文档包含在 DOC 子目录中,并提供单面或双面 postscript 和 dvi 格式。自 1995 年 7 月以来,Linux ELF 可执行文件格式以及 a.out 格式都得到了支持。要下载 a.out 格式的工具包,请从 linux 目录中获取文件 bxform-075.tgz。基于 ELF 的库位于 linux/elf 中。该软件包也适用于其他平台;请参阅文档。
相关的 WWW 页面位于 http://bragg.phys.uwm.edu/xforms/ 和 www.uwm.edu/~zhao
最后,您可以加入邮件列表。发送消息至 listserv@imageek.york.cuny.edu 并在邮件正文中输入消息
每个使用 XForms 进行用户界面的程序都管理一个或多个“表单”。表单只是 X 下的一个窗口:带或不带边框,带或不带调整大小功能,带有一些窗口标题等。表单是一个框,您可以在其中放置“对象”:按钮、拨号盘、输入字段等等。对象是许多其他工具箱所称的“部件”。每个对象都是给定“类”之一。例如,XForms 支持对象类“button”,其中存在多种风格(一个简单的按钮,上面有一个灯泡,圆形按钮,复选按钮,单选按钮等)。大多数程序可以使用 XForms 的内置类编写,尽管 XForms 支持自由对象类来处理特殊情况。
XForms 的概念,如类和对象,类似于面向对象编程(尤其是 C++)。然而,XForms 代码严格来说是 C,这意味着您可以将其用于 C 和 C++。更重要的是,XForms 构建在裸 X11 库之上。链接 XForms 程序仅需要 XForms 库、X11 库和数学库;不需要其他库。
您必须与 XForms(实际上,与任何 X 应用程序)一起使用的编程范例与您可能习惯的有所不同。当编写一个仅从一个源(例如,键盘)读取输入的交互式程序时,您可能会使用以下方案
/* forever do: */ while (1) { /* fetch user's input */ input = get_input (); /* determine what it is and * take appropriate action */ if (input == one_thing) do_one_thing (); else if (input == other_thing) do_other_thing (); . . . /* if `quit` signaled: we're * all done here */ else if (input == all_done) break; }
相反,当您为 X 构建用户界面时,您可能会创建多个输入源。例如,您可以有多个按钮,按下这些按钮时,会激活程序代码的某些部分。输入源甚至可以是用户不可选择的对象,例如超时的计时器。表示这一点的编程范例在 X 中通过回调实现。您创建多个按钮,每个按钮都有其自己的特定回调函数。然后,主程序循环包括轮询各个输入源的事件,并在发生某些事情时激活适当的回调。这是 XForms 采用的方法,我们稍后将看到一个带有回调的典型程序。
通常,XForms 程序有两种类型的代码:使用 XForms 工具包的用户界面和执行某些有用操作的代码,即,当例如按下按钮时,您希望程序执行的操作。程序的用户界面部分包括初始化代码、表单的定义以及将表单放到屏幕上的命令。然后,程序激活 XForms 的主循环。此循环执行上述事件轮询。
例如,一个只显示“quit”按钮的小程序如下所示
#include <forms.h> /* Call back function, called when `quit` * button is pressed */ void quit_button_cb (FL_OBJECT *obj, long data) { exit (0); } void main (int argc, char **argv) { FL_FORM *myform; /* form to display */ FL_OBJECT *quitbutton; /* temp variable */ /* Initializer code: */ fl_initialize (argv [0], "XMyprog", 0, 0, &argc, argv); /* Form definition: */ myform = fl_bgn_form (FL_FLAT_BOX, 200, 150); quitbutton = fl_add_button (FL_NORMAL_BUTTON, 50, 50, 100, 50, "Quit"); fl_set_object_callback (quitbutton, quit_button_cb, 0); fl_end_form (); /* Putting the form on-screen: */ fl_show_form (myform, FL_PLACE_FREE, FL_FULLBORDER, "Button program"); /* Main XForms loop: */ fl_do_forms (); /* Never reached.. */ }
可以在此列表中看到函数 quit_button_cb() 的定义。此函数是回调,当用户按下 quit 按钮时激活。所有后续代码都在 main() 函数中:初始化、表单定义、将表单放置在屏幕上以及激活主 XForms 循环。初始化部分由函数 fl_initialize() 完成,该函数连接到 X 服务器,解析命令行等。在此示例中,fl_initialize() 不需要解析任何特定于应用程序的标志;因此有两个零参数。
表单定义由 fl_bgn_form() 和 fl_end_form() 括起来。通常,您不会手动编写此代码,而是将其留给设计器(如下所述)。表单通过声明框类型和表单的大小来启动。在 fl_bgn_form() 和 fl_end_form() 语句之间,可以将对象添加到表单中。使用 fl_add_...() 添加对象。在此示例中,只有一个按钮放置在表单中,其 x 和 y 坐标为 50,50(相对于表单的左下角),大小为 100x50 像素。按钮文本为 Quit。
在按钮定义之后,分配按钮的回调。通常,您甚至将此定义留给设计器。在我们的例子中,激活按钮会导致调用 quit_button_cb()。
请注意,要添加按钮,使用了局部变量 quitbutton。在下一个语句中使用相同的变量来分配回调。这些是唯一需要变量的语句;换句话说,可以创建对象(通常在由 fdesign 生成的专用函数中),而无需使用全局变量。通过最小化全局变量的使用,通常可以提高可读性和可维护性。稍后,当调用对象的回调函数时,您始终可以找到该对象——回调的第一个参数始终是指向相关对象的指针。
在表单定义之后(由 fl_end_form() 终止),使用 fl_show_form() 将表单放置在屏幕上。此处声明了放置类型(调整大小功能等)、边框类型和窗口标题。在此之后,调用 XForms 的主循环 fl_do_forms()。此函数永远不会返回;程序通过回调终止。
XForms 软件包包含一个名为 fdesign 的优秀设计器,用于创建和维护图形用户界面。设计器允许您创建表单,向表单添加对象,设置对象的属性,例如颜色等,并定义对象的回调。
设计器可以进行微调。我强烈建议您阅读文档中关于该主题的章节。在调整设计器时,您可以指定生成的代码类型,设计器是否应发出 main() 函数和/或回调模板,设计表单时对象的大小和位置的像素步长等等。
实际上,您永远不应修改设计器生成的代码;如果您想添加特殊的 fl_...() 调用来修改对象的外观,请在生成的代码之外执行此操作。这样做的原因是,您可能稍后想要更改程序的外观,例如,通过更改一些颜色。然后,您所要做的就是启动设计器,执行更改,保存文件并重新制作。如果您修改了生成的代码,则一旦您重新保存设计器文件,更改就会丢失。(当然,我通过艰苦的方式得出了这个结论——我犯了修改 fdesign 输出的错误。)
包含 XForms 工具包的存档当前名为 bxform-075.tgz,075 是版本号。在您获取存档后,更改目录到源目录(例如 /usr/src)并使用 tar xvzf bxform-075.tgz 解压存档
存档会展开到 xforms 子目录中,您将在其中找到 Makefile。默认情况下,make install 会将库 libforms.a 和头文件 forms.h 分别复制到 /usr/lib 和 /usr/include。设计器 fdesign 进入 /usr/local/bin。如果您想使用其他路径,请在 make install 之前编辑文件 mkconfig.h。
如果您想查看演示,请键入 make/并等待(这需要一些时间才能完成)。然后 cd DEMOS 并键入 ./demo。
在介绍了 XForms 之后,我想向您展示创建真实 XForms 程序的步骤。为了简单起见,我展示了一个仅管理两个表单的程序。其中一个表单有一个输入对象,用户可以在其中输入文件名。此表单还有一个退出按钮来终止程序。另一个表单有一个浏览器对象,其中显示文件的内容。使用浏览器中的滚动条,用户可以查看文件的不同部分。
至于程序的行为,我们决定只有带有输入对象和退出按钮的表单最初显示在屏幕上。当用户输入文件名时,带有包含文件内容的浏览器的表单出现。当输入空文件名时,带有浏览器的表单再次消失。
首先,我们应该决定一些名称。表单将分别称为 name_form 和 browser_form。在 name_form 内部将有两个对象:一个输入对象,允许用户键入文件名,以及一个退出按钮。这两个对象都需要回调来执行适当的操作:函数将分别是 input_cb() 和 exit_cb()。这些对象可以保持匿名,这意味着设计器不会生成全局变量来寻址它们。
browser_form 只需要一个对象:浏览器对象。此对象不需要回调,因为浏览器不会接受用户交互(除了滚动条的移动,这由浏览器内部处理)。但是,我们将需要此浏览器的全局变量,因为第一个表单的输入对象的回调必须能够用给定文件的内容填充浏览器。我们将浏览器称为 file_browser。
现在我们已经确定了表单和对象的名称,我们可以设计表单了。XForms 设计器可以使用 fdesign design & 启动,告诉它将其输出保存在文件 design.fd(设计本身)、design.c(生成的 C 代码)和 design.h(生成的头文件)中。与号在后台启动 fdesign。
一旦设计器启动,单击“新建表单”按钮启动新表单并输入表单名称 name_form。您可以将出现的窗口重新缩放到合适的大小。在此之后,将所需的对象放入表单中:一个输入对象和一个按钮。可以在设计器的“对象”菜单中选择这些对象。一旦将对象放置在表单中,您可以使用鼠标调整其大小或移动它。“对齐”按钮允许您定义对象缩放或移动的像素步长。按 F1 激活“属性”窗口,您可以在其中定义对象属性(例如字体、颜色、回调)。
就 name_form 而言,您应该创建一些看起来像 图 1 和 图 2 中的屏幕截图。图 1 显示了设计器窗口本身、名称表单和输入对象的属性窗口。(是的,我大约在中午编写了这个程序。)在此图中,输入对象是“活动的”,可以通过其周围的红色框看到。
图 2 显示了退出按钮及其属性。
要定义浏览器表单,请再次单击“新建表单”以启动第二个表单。输入 browser_form 作为表单名称,并将表单窗口调整为您喜欢的大小。然后从“对象”菜单中选择“browser”对象并将其添加到表单中。表单、其浏览器对象和浏览器的属性显示在 图 3 中。
定义表单后,我们可以让 fdesign 生成其代码。单击“选项”按钮,然后切换“Alt 格式”条目以点亮此菜单条目前面的按钮。这指示 fdesign 生成简单的代码,而不是将表单和对象变量放入结构中。对于此应用程序,简单的代码就足够了。然后单击“文件”按钮,保存文件并退出 fdesign。
我们可以依靠 fdesign 来生成所有表单和对象的定义代码。这给我们留下了两个任务:初始化代码和回调代码。
初始化代码如清单 1 所示。此代码定义了 main() 函数,其中初始化 XForms,创建表单,将第一个表单放在屏幕上,然后进入主 XForms 循环。您可以看到对 create_the_forms() 的调用;此函数的代码由 fdesign 在文件 design.c 中生成。
/* xviewfile.c -- main function of xviewfile */ #include \<>forms.h #include "design.h" void main (int argc, char **argv) { /* initialize XForms, parse arguments */ fl_initialize (argv [0], "XViewfile", 0, 0, &argc, argv); /* create the forms (fdesign-generated function) */ create_the_forms (); /* show the name input form */ fl_show_form (name_form, FL_PLACE_MOUSE, FL_FULLBORDER, "Enter a name"); /* enter XForms loop */ fl_do_forms (); /* not reached... */ }
现在是第二个任务,回调代码。回调函数如清单 2 所示。回调函数的名称已在设计规范中提及;因此,重要的是在编写函数时使用确切的名称。
由对象激活的每个回调都传递两个参数。第一个参数是指向 FL_OBJECT 的指针。此参数按定义指向调用回调的对象。我们将在 input_cb() 函数中看到如何使用此参数。第二个参数是 long int——可以在设计器中定义回调时将该值设置为任何数字。例如,您可以有两个不同的对象调用相同的回调函数,但提供不同的数字参数;在函数内部,您可以通过检查第二个参数来区分。我们不会在此应用程序中使用这种方法。
由退出按钮激活的回调称为 exit_cb()。这是简单的那个——它所做的只是 exit(0)。输入对象的回调 input_cb() 需要执行更多任务。首先,必须检索用户键入的名称。这由 XForms 的函数 fl_get_input() 完成。然后,我们必须决定如何处理输入。如上述程序描述中所述,空名称应导致从屏幕上删除浏览器窗口。非空名称应解释为查看文件的请求。
为了实现这一点,input_cb() 使用静态 int 变量来标记浏览器表单是否已在屏幕上。当输入非空名称且浏览器表单尚未在屏幕上时,调用 fl_show_form() 以显示浏览器。类似地,当输入空名称且浏览器表单在屏幕上时,调用 fl_hide_form() 以删除浏览器表单。(除了使用额外的静态 int 之外,您还可以检查字段 int visible,它是 FL_FORM 结构的一部分。我将此类优化留给读者。)
浏览器本身使用特定于浏览器的函数 fl_clear_browser() 和 fl_load_browser() 进行操作。
/* callbacks.c --contains the callback routines */ #include <\<>forms.h> #include "design.h" void exit_cb (FL_OBJECT *obj, long data) { exit (0); } void input_cb (FL_OBJECT *obj, long data) { char const *name; /* entered filename */ static int browser_on_screen = 0; /* is browser on-screen yet ? */ /* determine the entered name */ name = fl_get_input (obj); if (name && *name) /* a name was entered */ { if (! browser_on_screen) /* make sure browser is there */ { fl_show_form (browser_form, FL_PLACE_CENTER, FL_FULLBORDER, name); browser_on_screen = 1; } fl_clear_browser (file_browser); /* clear previous contents */ fl_load_browser (file_browser, /* load in file */ name); } else /* empty input was given */ { if (browser_on_screen) /* remove browser from screen */ { fl_hide_form (browser_form); browser_on_screen = 0; } } }
最后,没有 Makefile 的自动维护描述,程序是不完整的。这是一个例子
# Makefile -- makefile for xviewfile. # Used objects: OBJ = xviewfile.o callbacks.o design.o # Compilation flags: CFLAGS = -c -O2 -Wall # How to make the program: xviewfile: $(OBJ) $(CC) -o xviewfile $(OBJ) -lforms -lX11 -lm -s # How to clean up the mess. clean: rm -f $(OBJ)
清单仅显示了 XForms 可能性的冰山一角。为了简洁起见,我没有讨论诸如颜色定义或字体选择之类的主题。
您想要尝试的一件事是制作可调整大小的表单。表单是否可以调整大小在调用 fl_show_form()(显示表单)中定义。表单如何调整大小在表单定义本身中定义。同样,您应该使用 fdesign 来设置这些选项。例如,您可能有一个表单,顶部有一个按钮和一个输入对象,下方有一个浏览器。当这样的表单被调整大小时,您可能只希望浏览器扩展或收缩,而不是按钮和输入对象。这些功能通过对象的调整大小选项和重力设置。XForms,以及设计器,为此目的支持“对象组”:您可以对对象进行分组,并定义应用于整个组的调整大小选项和重力。
任何 X 程序的另一个有用属性是能够让用户通过命令行标志、通过 xrdb 设置的资源或通过应用程序默认文件设置的资源来覆盖程序的设置。XForms 支持所有这些;fl_initialize() 可以与 fl_get_resources() 结合使用,以扫描有意义的标志或资源设置。实现资源识别并非完全微不足道,但超出了本文的范围。如果您有兴趣,请查看一些使用 XForms 构建的程序。XForms 发行版以及 WWW 资源都包含指向有用且说明性程序的指针。
当然,还有许多其他有用的 XForms 可能性。我将其留给您,在一个安静的夜晚,伴着一杯(虚拟的)啤酒,通读优秀的文档。
不用说,我对 XForms 非常热情。即使您以前根本没有 X 编程经验,XForms 工具包的设置(使用表单、对象、类)也非常直观。但是,您确实需要良好的 C 语言知识。
XForms 中的对象类范围如此之广,以至于我(尚未)需要定义自由对象类。如果您计划编写日常程序,XForms 可能具有适合您需求的类,并且这些类有大量专用函数,使对象以您喜欢的方式显示。
就设计器而言,我已经开始珍视这个工具,即使我最初认为它只是一个华而不实的附加功能。通过扩展现有表单以容纳例如额外的按钮,您可以轻松地调整程序以执行额外的任务,这真是令人难以置信。不再需要编辑代码来调整所有部件的坐标!
当然,也有缺点。我认为令人遗憾的是 XForms 未随附完整源代码一起分发。XForms 可以免费用于非商业应用这一事实当然是一个巨大的优势,但是,我还是希望手头有源代码。如果您愿意付出代价,则可以克服这种不满:作者确实出售包含源文件的许可协议。
另一个缺点是,如果您不使用基于 ELF 版本的 XForms,则必须将您的程序链接到静态 libforms.a。没有用于 XForms 的共享的基于 a.out 的库。因此,除非您迁移到 ELF 可执行文件格式,否则即使是最小的程序也会变成大约 130KB。(另一方面,我也使用静态链接的 Motif 程序。相比之下,130KB 是微不足道的。)
XForms 库对程序员来说“友好”,当您是新用户或正在开发程序时,这是一个优势。例如,当程序尝试从屏幕上删除最初未在屏幕上的表单时,XForms 将弹出一个警告窗口。然后,您将看到三个选择:忽略该操作、中止程序或禁止此类警告。当您测试程序时,此功能非常方便。但是,此类安全网会向程序添加“多余”的代码。在我看来,一旦程序经过测试,就不再需要此类预防措施,因此只会成为负担。理想情况下,我更喜欢 XForms 库的两种风格:具有完整安全功能的开发人员版本和没有安全功能的生产版本。在作者方面,这只需要代码中的一些 #ifdef/#endif 编译指令即可。
总的来说,我发现 XForms 的优点远多于缺点。我现在使用 XForms,并且计划继续这样做。在我使用 XForms 自身的功能无法克服的 X 编程工作中,我仍然没有遇到任何障碍。而且我什至还没有探索 XForms 的所有可能性。
Karel Kubat 居住在荷兰北部,目前正在攻读博士学位论文。他是一位 Linux 狂热者,因此假装对该主题无所不知,因为这就是成为狂热者的全部意义。可以通过电子邮件在格罗宁根大学联系到他,电子邮件地址是 karel@icce.rug.nl。