鼠标与键盘之间的代码编写,第一部分
编写 GUI 应用程序的日子,那时你和你最喜欢的文本编辑器与编译器以及 make 苦苦挣扎,早已一去不复返了。即使没有像 KDevelop 这样的集成开发环境 (IDE),许多辅助应用程序也承诺使 C++ GUI 编程尽可能轻松。
任何期望 KDE 验证过的 Qt 工具包是一个包含一个库的 tarball 文件的人,当从 ftp.trolltech.com/pub/qt/source 下载超过 14MB 的内容时,将会感到失望。尽管 Qt 以其以前版本中的良好质量而闻名,但 Qt 3.0 有太多的错误,自 2001 年 10 月中旬首次出现以来,已经发布了第四个维护版本。预计还会有更多版本陆续发布。因此,强烈建议选择最新版本。
qt-x11-free-3.0.x.tar.gz(我们使用了 Qt 3.0.4) 存档不仅包括类库和 HTML 格式的文档,还包括几个工具,这些工具承诺让参与 GUI 应用程序项目的每个人都更轻松。
让我们选择新版本的 Qt Designer 来创建简单文本编辑器应用程序的图形界面。[此编辑器的完整代码可在 www.trish.de/pub/linuxjournal/ljeditor_qt3.0.4 获取。] 它附带了用户界面编译器 uic,它将 Designer 的 XML 输出格式转换为 C++ 代码。在本文的第二部分中,我们将使用您最喜欢的编辑器实际编写一些 C++ 代码,并为 GUI 框架注入活力。
然后,我们将使用 Qt 家族的新成员 Qt Linguist 来本地化程序。与 Qt Designer 类似,这是一个 GUI 应用程序,使用 XML 格式进行输入和输出。为了创建需要翻译的短语的 XML 列表,它附带了一个名为 lupdate 的命令行工具。一旦它们被翻译,另一个命令行工具 lrelease 会将 XML 转换为应用程序运行时所需的二进制格式。
为 Qt 应用程序编写 Makefile 并不是一件容易的事情。旧版本的 Qt 没有附带辅助应用程序,但 Qt 供应商 Trolltech 提供了一个名为 tmake 的工具供单独下载。Qt 3.0 tarball 包括新的 qmake 实用程序,它在为项目创建 Makefile 时非常方便。这我们再次留到本文的第二部分。
有了 g++ 编译器,我们拥有了所有必要的工具,应该考虑文本编辑器应用程序本身。它应该能够做什么?
显然,我们希望它提供通常的任务:打开一个新的编辑器窗口、打开文件、保存其内容、使用不同的文件名保存、关闭当前编辑器窗口和退出整个应用程序。此外,它应该支持复制、剪切和粘贴、撤消和重做。我们希望程序能够切换字体特征为斜体、粗体、下划线以及这些的任意组合。在关于 (About) 小部件中,编辑器(我们称之为 ljedit)应该显示一些关于它自身的信息。
除了更改字体特征外,所有任务都应该通过“文件 (File)”、“编辑 (Edit)”或“帮助 (Help)”菜单提供。斜体、粗体和下划线切换应该通过工具栏中的子菜单完成,工具栏中填充了用于打开和保存文件、撤消、重做、剪切、复制和粘贴的附加图标。
当用户将鼠标悬停在每个图标上时,每个图标都应该打开气球帮助。除了“另存为 (Save As)”和“退出 (Exit)”操作外,所有其他任务都应该通过键盘快捷键提供。
为了避免用户意外丢失数据,打开和关闭文件以及退出应用程序都应该由用户对话框支持,该对话框询问是否应该保存旧数据或丢弃旧数据,或者用户是否想保留旧文件。虽然上述大多数任务都可以使用 Designer 完成,但最后一部分将不得不等到下一期。
如果系统上安装了多个版本的 Qt,则应首先将 QTDIR 变量设置为包含相关 Qt 版本的目录。接下来,是时候在 shell 中使用 designer & 启动 Designer 了。如果 $QTDIR/bin 目录未包含在搜索路径中或有多个 Qt 版本可用,则必须将绝对路径添加到命令中。要启动新项目,请从“文件 (File)”菜单中选择“新建 (New)”。在即将出现的对话框中,单击“C++ 项目 (C++ Project)”图标,然后单击“确定 (OK)”按钮确认您的选择。现在我们有机会通过选择名称、位置和添加项目描述来创建一个新的 qmake 项目文件(图 1)。

图 1. 第一步:创建项目
接下来,我们再次从“文件 (File)”菜单中选择“新建 (New)”条目,以便创建我们编辑器的第一个(并且仅用于本文目的)GUI 小部件。因此,“主窗口 (Main Window)”是从“新建文件 (New File)”对话框中选择的正确条目。我们对默认的“插入 (Insert)”条目感到满意,这使其成为我们新项目的一部分。
这将打开“主窗口向导 (Main Window Wizard)”。在第一个问卷调查(见图 2)中,我们感觉是的,Designer 应该为我们创建菜单和工具栏。它应该为我们提供用于函数和连接这些函数到相关操作的代码框架。
“下一步 (Next>)”按钮将我们带到工具栏的设置对话框。从“文件 (File) 类别”中,我们选择“打开 (Open)”和“保存 (Save)”,并使用右侧的箭头将它们添加到“工具栏 (Toolbar) 列表”中。然后我们选择“编辑 (Edit) 类别”,并添加“撤消 (Undo)”、“重做 (Redo)”、“剪切 (Cut)”、“复制 (Copy)”和“粘贴 (Paste)”操作,以及“分隔符 (Separators)”(图 3)。最后一个向导对话框没有给我们留下任何工作要做;它只是完成了小部件表单。这给我们留下了一个看起来像图 4 的 Designer。
所有新的小部件为了看起来像一个合适的编辑器,所缺少的就是窗口标题中的一个好名字(当前显示为 Form1)和文本编辑画布。为了解决第一个任务,我们需要查看“属性编辑器 (Property Editor)”窗口中的“属性 (Properties)”选项卡。它总是用当前小部件(即,最后单击表单上的小部件)的特性填充自身;首先,这是表单小部件。
通过将 name 属性更改为 “ljeditor”,我们定义了我们正在创建的小部件的类名。另一方面,caption 定义了窗口标题,应该设置为新应用程序的名称 “ljedit”。
现在,让我们添加编辑器画布。单击“富文本编辑器 (Richtext Editor)”图标(在图 4 中,从右侧数第五个图标,标记为 “abcde”),然后单击 ljeditor 表单的栅格背景,新的编辑器画布可以通过蓝色句柄点拉伸成形来调整大小。让我们在“属性编辑器 (Property Editor)”中将其命名为 “TextEdit”。
我们现在要做的就是调整功能。首先,我们从“操作编辑器 (Action Editor)”中删除我们不想实现的操作(见图 5),方法是单击剪刀图标或右键单击标记的操作时在上下文菜单中可用的“删除操作 (Delete Action)”条目。在本例中,我们可以不用 helpIndexAction、helpContentsAction、editFindAction 和 filePrintAction。

图 5. “操作编辑器 (Action Editor)”
作为回报,编辑器需要一些新的操作来更改字体特征。在从“操作编辑器 (Action Editor)”的“创建新操作 (Create New Action)”图标(左侧的小纸片)下降的菜单中,我们选择“新建下拉操作组 (New Dropdown Action Group)”作为它们的容器,并编辑其属性。首先,操作组在“属性编辑器 (Property Editor)”的名称行中命名为 “fontCharacter”。然后,我们使用单击的 iconSet 属性旁边的 “...” 按钮选择合适的图标。使用 “添加 (Add...)” 按钮,可以添加存储在文件系统某处的图标。
它的 text,自动用于 menuText 和各种提示(见词汇表),应该读取 “Font Characteristics”(字体特征)。我们将工具提示 (tooltip) 和状态提示 (statustip) 更改为 “Choose font characteristics”(选择字体特征),最重要的是,我们将 exclusive 属性设置为 False。这意味着用户将能够根据需要组合斜体、粗体和下划线字体。独占操作组一次只允许其中一个。
在“操作编辑器 (Action Editor)”中的 fontCharacter 操作组上右键单击,然后在上下文菜单中选择 “新建操作 (New Action)”,我们添加了以 Ctrl-I 作为快捷键和 “斜体 (Italics)” 作为文本的斜体操作。因为用户可以打开和关闭斜体,所以 toggleAction 属性读取 True 非常重要。首先,斜体应该关闭;因此,on 属性必须具有值 False。
我们以相同的方式定义 fontCharacter 的其他两个子操作,粗体 (Ctrl-B) 和下划线 (Ctrl-U)。
要将整个 fontCharacter 操作组添加到工具栏,我们只需将其从“操作编辑器 (Action Editor)”拖到表单上,然后将其放到工具栏上。在工具栏中右键单击允许我们添加分隔符(“插入分隔符 (Insert Separator)”)。
到目前为止,当使用按 Ctrl-T 可用的预览时,这些操作没有任何作用。为了让它们实际执行它们承诺的操作,我们再次在“操作编辑器 (Action Editor)”中标记斜体操作,单击红蓝色“编辑连接 (Edit Connections)”图标,然后从“信号 (Signals)”列表中选择 toggled(bool) 信号。我们没有将其连接到 ljeditor 插槽,而是在 “插槽 (Slots)” 下拉菜单中选择 TextEdit,然后在 TextEdit 所属的 QTextEdit 类的插槽列表中设置 setItalic(bool)。无需额外单击;随着连接在“连接 (Connections:)”窗口中的出现,一切都已完成(见图 6),“确定 (OK)” 按钮是我们的朋友。
然后我们对粗体操作的 toggled(bool) 信号和 TextEdit 的 setBold(bool) 插槽重复相同的过程。我们将下划线操作连接到 TextEdit 的 setUnderline(bool) 插槽。在此之后,预览会对 Ctrl-I、Ctrl-B 和 Ctrl-U 做出想要的反应。
这鼓励我们编辑预定义操作的连接;这些不能切换。相反,它们在激活时(即,单击或选择)启动用户命令,例如 “保存当前数据”。这就是为什么我们将 activated() 信号连接到适当的插槽。
对于 editRedoAction,这是 TextEdit::redo()。我们断开与默认 ljeditor::editRedo() 的连接;如果我们不想依赖 QTextEdit 的 redo() 函数,这将是正确的选择。同样,editUndoAction 的 activated() 信号与 TextEdit::undo() 连接,并与各自的 ljeditor 函数骨架断开连接。我们对 editPasteAction 和 TextEdit::paste() 插槽、editCopyAction 和 TextEdit::copy() 插槽以及 editCutAction 和 TextEdit::cut() 插槽重复此步骤。
其余预定义操作(helpAboutAction、fileExitAction、fileSaveAction、fileSaveAsAction、fileOpenAction 和 fileNewAction)保持与预定义的 ljeditor 插槽骨架连接,我们稍后必须用代码填充这些骨架。
仍然缺少一个操作:用户激活以关闭当前编辑器窗口的操作(与退出整个应用程序的 fileExitAction 相反)。
工作流程是熟悉的:我们从“操作编辑器 (Action Editor)”的“创建新操作 (Create new Action)”菜单中选择“新建操作 (New Action)”。新操作在“属性编辑器 (Property Editor)”中命名为 “fileCloseAction”,并配备 “关闭 (Close)” 作为文本和 Ctrl-Z 作为键盘快捷键。
但是,我们缺少一个插槽来连接其 activated() 信号。我们通过打开 Designer “编辑 (Edit)” 菜单中的 “插槽 (Slots...)” 菜单项来解决这个问题。图 7 显示了现在打开的对话框。
我们添加一个插槽,其函数名称为 fileClose(),返回类型为 void,访问类型为 public。“编辑插槽 (Edit Slots)” 对话框不做任何魔法,它只是在 ljeditor 类的类定义中创建了一个函数骨架。
现在我们可以在“操作编辑器 (Action Editor)”提供的“编辑连接 (Edit Connections)”对话框中将 fileCloseAction 的 activated() 信号连接到 ljeditor::fileClose()。由于此操作应仅通过菜单提供,我们只需将其拖放到 ljeditor 表单的 “文件 (File)” 菜单中即可。
在 Qt 中关闭小部件很容易:每个 Qt 小部件都从所有 Qt 小部件的母类 QWidget 继承了一个 close() 函数。这实际上没有多少代码,所以如果我们能用这一行代码填充 fileClose() 插槽就好了。
右键单击 ljeditor 表单会打开一个上下文菜单。选择其 “源代码 (Source...)” 条目即可完成此技巧。代码编辑器窗口出现,允许我们填写单行代码(见图 8)
void ljeditor::fileClose() { close(); }
那么为什么不也填充 fileExit() 插槽呢?要退出整个应用程序,将调用应用程序对象的 closeAllWindows() 函数
void ljeditor::fileExit() { qApp->closeAllWindows(); }
由于 Designer 通常不处理 QApplication 对象本身,因此默认情况下不包含 qapplication.h(它为真实应用程序对象提供 qApp 代理),并且从 ui 描述生成的代码将无法编译。
幸运的是,“对象浏览器 (Object Explorer)” 允许我们包含额外的头文件。默认情况下,它显示 “小部件 (Widgets)” 选项卡(见图 4,左下方),但我们现在需要 “源代码 (Source)” 选项卡。右键单击空的 “包含 (Includes)”(在“实现 (Implementation)” 中)文件夹允许我们添加头文件(“新建 (New)”)。不要忘记全局包含的 <> 括号,例如 <qapplication.h>(图 9)。
我们可以用生命填充的另一个插槽而不用担心出现太多编译错误的是 helpAbout()。当用户打开 ljeditor “帮助 (Help)” 菜单中的 “关于 (About...)” 条目时会调用它,并且只是弹出一个消息框,其中包含标题 “关于 ljedit (About ljedit)” 和一些关于程序的信息。通过用 tr() 包围所有文本字符串,我们确保程序可以轻松地本地化,我们将在第二部分中完成这项任务。例如
void ljeditor::helpAbout() { QMessageBox::about( this, tr( "About ljedit" ), tr( "A tiny text editor.\n" "(C) 2002 Patricia Jung for Linux Journal\n" "Using Qt 3.0.4 and Qt Designer." ) ); }
为了能够使用 QMessageBox::about(),我们必须以与 <qapplication.h> 相同的方式包含 <qmessagebox.h>。我们下次在子类中添加剩余的功能。因此,我们使用 Designer 剩下的所有事情就是清理新的 GUI。
在我们彻底离开 Designer 之前,我们检查是否没有重复使用任何已分配的键盘快捷键。这是通过从 “编辑 (Edit)” 菜单中选择 “检查加速键 (Check Accelerators)” 来完成的。
此外,我们清理了我们无论如何都不打算实现的或我们改为使用 TextEdit 插槽的所有 ljeditor 插槽。通过从 “编辑 (Edit)” 菜单中选择 “插槽 (Slots)”(一种方法),我们打开对话框,该对话框允许我们标记 editUndo() 并通过鼠标单击 “删除插槽 (Delete Slots)” 来删除它(图 7)。同样的命运也适用于 editRedo()、editCut()、editCopy()、editPaste()、editFind()、helpIndex()、helpContents() 和 filePrint()。
此外,我们检查 GUI 预览中是否存在不合适的分隔符。由于我们决定不使用 editFindAction,因此可以在 ljeditor 的 “编辑 (Edit)” 菜单的末尾找到单个分隔符:曾经有一个 “查找 (Find)” 条目在下面。要删除分隔符,请在表单中右键单击它,然后选择 “删除项目 (Delete Item)”。这同样适用于 “文件 (File)” 菜单中 “退出 (Exit)” 条目上方的两个分隔符之一。
所有 GUI 元素都已就位——是时候让 Qt 调整小部件比例了。我们再次选择整个表单,然后从主菜单中的 “布局 (Layout)” 中选择 “垂直布局 (Lay Out Vertically)”。如果用户现在调整应用程序窗口大小,TextEdit 小部件将跟随。如果我们希望将小部件元素以不同的方式放置到表单上,我们需要先打破布局。
最后但并非最不重要的一点,我们希望将此 GUI 标记为我们自己的。为此,我们在通过从 “编辑 (Edit)” 菜单中选择 “表单设置 (Form Settings)” 弹出的对话框中填写 “作者姓名 (Author name)” 和描述(见图 10)。在这里,可以决定我们是否要将图标存储在我们项目目录的子目录中,或者我们是否将它们直接包含在用户界面描述中。
最后从 “文件 (File)” 菜单中选择 “全部保存 (Save all)”,以及包含用户界面描述的 XML 文件(最好以相关类命名,例如 ljeditor.ui)以及 ljeditor.ui.h,即存储我们键入代码编辑器的文件,成为项目的一部分。