8.8 多文档
在每一个主窗口中只提供一个文档的应用程序被称为单文档界面( SDI)应用程序。基 于 SDI 的应用程序只提供了一个单一主窗口,并且在同一时间只能处理一个文档。如果想 让它在同一时间具有处理多个文档的能力,就需要同时启动多个应用程序实例。但是这对于 用户来讲是很不方便的。针对这种情况,我们可以使用基于多文档界面( MDI)的应用程 序,它也只有一个主窗口,但可以产生和管理多个文档窗口。基于多文档的应用程序也是很 常见的,比如 FireFox 浏览器就是典型的例子,它允许用户在同一时间内打开多个浏览器 窗口。
Qt 可以在它所支持的所有平台上创建 SDI 和 MDI 应用程序。 根据笔者的经验,在通常的工程项目中,使用 SDI 的应用程序占到了大多数的比例,但我们仍然需要对 MDI 有所了解。
8.8.1 创建多文档
在 Qt4 中创建 MDI 应用程序主要有以下方法:
1. 多实例实现主窗口的多文档
在一个应用程序中实例化多个主窗口,即当打开或新建文档的时候,文本编辑器应用 程序新建一个主窗口,这个主窗口单独加载和编辑文档。这种情况下,多个主窗口属于同一 个应用程序,当关闭所有的主窗口的时候,文本编辑器应用程序也就结束了运行。这种方法 称为多实例实现主窗口的多文档。
下面将修改应用程序,以使它可以处理多个文档。首先,需要对 File 菜单做一些简单 改动:
- 利用 File→New 创建一个空文档窗口,而不是再次使用已经存在的主窗口。
- 利用 File→Close 关闭当前主窗口。
- 利用 File→Exit 关闭所有窗口。
在 File 菜单的最初版本中,并没有 Close 选项,这只是因为当时它还和 Exit 一样具 有相同的功能。新的 File 菜单如图 8-27 所示。
新的 main()函数为:
int main(int argc,char *argv[])
{
QApplication app(argc,argv); MainWindow *mainWin = new MainWindow; mainWin->show();
return app.exec();
}
具有多窗口功能后,现在就需要使用菜单中的 new 来创建 MainWindow。考虑到节省内 存,可以再工作完成之后使用 delete 操作来删除主窗口。
这是新的 MainWindow::newFile()槽:
void MainWindow::newFile()
{
MainWindow *mainWin = new MainWindow; mainWin->show();
}
我们只创建了一个新的 MainWindow 实例。这看起来有些奇怪,因为没有保留指向这个 新窗口的任何指针,但实际上这并不是什么问题,因为 Qt 会对所有的窗口进行跟踪。
以下是用于 Close 和 Exit 的动作:
void MainWindow::createActions()
{
...
closeAct = new QAction(tr("Cl&ose"), this); closeAct->setShortcut(tr("Ctrl+F4"));
closeAct->setStatusTip(tr("Close the active window"));
connect(closeAct, SIGNAL(triggered()),mdiArea, SLOT(closeActiveSubWindow()));
exitAct = new QAction(tr("E&xit"), this); exitAct->setShortcut(tr("Ctrl+Q"));
exitAct->setStatusTip(tr("Exit the application"));
connect(exitAct, SIGNAL(triggered()), qApp, SLOT(closeAllWindows()));
}
到此,我们就实现了从 SDI 到 MDI 的“转变”,组合好你的工程文件,编译、链接、 运行程序即可。
小贴士:在 Mac OS X 系统中,通常采用这种方法实现 MDI 应用程序。
2.使用 QWorkSpace
使用 QWorkSpace 作为主窗口的中心部件,在 QWorkspace 中打开多个子窗口,每一个 子窗口可以单独对文档进行加载和编辑。
QWorkspace 类继承自 QWidget 类,利用它可以很方便的实现多文档的应用。使用它的 方法有如下步骤:
第 1 步,在主窗口的头文件(如 mainwindow.h)中加入 QWorkspace 类的头文件,代码 如下:
#include <QWorkspace>
也可以采用类的前置声明,代码如下:
class QWorkspace;
第 2 步,在主窗口头文件中声明一个 QWorkspace 类对象,代码如下:
QWorkspace *workspace;
第 3 步,在构造函数中实例化该对象,并把该对象设置为主窗口的中心窗口部件,代 码如下:
MainWindow::MainWindow()
{
workspace = new QWorkspace; setCentralWidget(workspace);
...
}
第 4 步,创建多个子窗口,并创建它们各自的中心窗口部件 。
QMainWindow *window1 = new QMainWindow;
window1->setWindowTitle(tr("window 1"));
QTextEdit *textEdit1 = new QTextEdit;
textEdit1->setText(tr("Window 1"));
window1->setCentralWidget(textEdit1);
QMainWindow *window2 = new QMainWindow;
window2->setWindowTitle(tr("window 2"));
QTextEdit * textEdit2 = new QTextEdit;
textEdit2->setText(tr("Window 2"));
window2->setCentralWidget(textEdit2);
QMainWindow *window3 = new QMainWindow;
window3->setWindowTitle(tr("window 3"));
QTextEdit * textEdit3 = new QTextEdit;
textEdit3->setText(tr("Window 3"));
window3->setCentralWidget(textEdit3);
第 5 步,在 workSpace 对象中加入这几个子窗口,对它们进行管理,代码如下:
workSpace->addWindow(window1);
workSpace->addWindow(window2);
workSpace->addWindow(window3);
第 6 步,组织好你的工程文件,编译、链接、运行程序即可。 完整的示例程序在本章目录下的 workspace 文件夹下面。
小贴士:在 Qt4.5 版推出以后,不推荐采用上述方法来创建 MDI 应用程序。来自 Qt 的 官方说法是,QWorkspace 是被废弃的类,它的存在就是为了使采用以前版本的 Qt 开发的程 序能够正常运行。所以,如果你使用的是 Qt4.5 及以后的版本,我们强烈建议你使用 QMdiArea 来创建 MDI 应用程序。
3.使用 QMdiArea
这种方法的核心主要是掌握两个类的用法: QMdiArea 和 QMdiSubWindow,前者主要用 于创建程序主窗口的中心窗口部件,后者用于创建主窗口的各个子窗口。具体的做法是把 QMdiArea 类的实例作为主窗口的中心部件,把 QMdiSubWindow 类的实例作为子窗口,并由 QMdiArea 实现对多个子窗口的管理。
QMdiArea 类继承自 QAbstractScrollArea,它是 Qt 4.3 以后新增加的类。在创建 MDI 应用程序时,QMdiArea 类的实例通常被用作主窗口的中心窗口部件,但也可以被放置于一 个布局中。实际上,QMdiArea 是 MDI 应用程序的窗口管理器。它建立、绘制、管理在它之 上的子窗口,并可采用层叠或者平铺的方式排列它们。
QMdiSubWindow 继承自 QWidget,它的作用是为 QMdiArea 创建子窗口。它代表了在 QMdiArea 中创建的顶层窗口。它主要包含一个标题栏、一个内部窗口( Internal Widget)、一个窗口框架和一个大小控制手柄。 QMdiSubWindow 有自己的布局(Layout), 在其中包含窗口标题栏以及内部窗口的中心窗口区域。一个典型的 QMdiSubWindow 实例如 图 8-27 所示。
图 8-27 QMdiSubWindow 子窗口
听了上面的介绍,大家是不是有点“晕”呢?别着急,请看图 8-28,它示意了采用这 种方法创建 MDI 应用程序的窗口布局以及创建的方法。图中的主窗口是子类化 QMainWindow 类创建的;主窗口的中心窗口部件使用 QMdiArea 的实例创建;子窗口是子类化 QWidget 实 现的,而子窗口的内部窗口部件是使用 QMdiSubWindow 的实例创建的。
它们之间的关系总结如下:QMdiArea 是所有子窗口的容器和管理器,QMdiArea 中的子 窗口都是 QMdiSubWindow 类的实例。我们通过 addSubWindow()方法把它们加入到 MDI 应用 程序中。使用时,通常先建立一个 QWidget 或其子类的实例,然后把它作为参数调用 addSubWindow()函数,addSubWindow()函数将把它作为子窗口的内部窗口,并填充中心窗口 区域。由于 QMdiSubWindow 是 QWidget 的子类,所以你可以像使用以前我们介绍过的常见 顶层窗口那样使用它,如可以调用基类 QWidget 的 show(), hide(), showMaximized(), 以及 setWindowTitle()等方法对窗口实例进行设置。
看着这张图再对照笔者上面的讲解,应该就很清楚了吧。
图 8-28 使用 QMdiArea 类创建 MDI 时的窗口框架
小贴士:为 QMdiSubWindow 创建内部窗口有两种方法,一种是调用 addSubWindow(widget),其中 widget 参数将作为内部窗口部件;另一种是先创建一个继承 自 QWidget 的窗口实例,然后调用 setWidget ( QWidget * widget )方法,把 widget 作为子 窗口的内部窗口部件即可,这个内部窗口部件将被显示在子窗口的中心区域。注意, QMdiArea 会对其内部的子窗口进行管理,你不必使用代码显式的管理它们。
QMdiSubWindow 还有许多专门对应 MDI 应用类型而设置的方法和属性,大家可以在 Qt Assistant 中获得详尽的介绍。
好了,我们详细解说一下如何采用 QMdiArea 和 QMdiSubWindow 类来创建 MDI 应用程 序,这个例子在本章目录 mdi 文件夹下面。
第 1 步,包含用到的类
在主窗口的头文件(如 mainwindow.h)中包含程序中使用到的类,有两种方法,一是可 以加入头文件;二是当情况比较简单时,也可以采用类的前置声明。
加入头文件:
#include <QMdiArea>
#include <QMdiSubWindow>
...
或者采用类前置声明:
class QMdiArea;
class QMdiSubWindow;
...
第 2 步,声明一个 QMdiArea 类对象
在主窗口的头文件中声明一个 QMdiArea 类对象,在后面还需要声明一个你的子窗口类 的对象,代码如下:
QMdiArea *mdiArea;
...
MdiChild *child;//声明子窗口类的对象
...
第 3 步,设置中心窗口部件
在主窗口类的实现文件中(如 mainwindow.cpp,通常在其构造函数中)实例化该对 象,并把它设置为主窗口的中心窗口部件,代码如下:
MainWindow::MainWindow()
{
mdiArea = new QMdiArea; setCentralWidget( mdiArea );
...
}
第 4 步,创建子窗口
新建一个子窗口类,它可派生自 QWidget 或其子类,比如 QTextEdit。这个类的实例将 作为子窗口的内部窗口部件。这个子窗口类的创建与我们前面讲到的子类化对话框和子类化 QWidget 的方法相同,只是它没有菜单栏、工具栏和状态栏。
另外记得在主窗口的头文件中加入该子窗口类的声明。
第 5 步,实例化子窗口类,并使用 QMdiArea 对它进行管理,代码如下:
child = new MdiChild;
QMdiSubWindow *subWindow = mdiArea->addSubWindow(child); subWindow->show();
...
小贴士:QMdiArea::addSubWindow()函数创建一个新的 QMdiSubWindow,把作为参数传递的 该窗口部件放进子窗口中,并且返回该子窗口。最后一行代码调用 show()方法,使该子窗口可见。
第 6 步,创建并显示子窗口
这通常是在用户点击 File->NewFile 时完成的,代码如下:
void MainWindow::newFile()
{
child->newFile(); child->show();
}
整个程序的实现过程可以用图描述。
图 8-29 程序的实现过程
小贴士:在 MDI 应用程序中,主窗口类并不需要对文档进行具体处理,这些工作是在子窗 口类中完成的,相当于在 SDI 应用程序中实现的文档处理功能。
第 7 步,创建 main.cpp 文件 这步没有什么好说的,代码如下:
#include <QApplication>
#include "mainwindow.h"
int main(int argc, char *argv[])
{
Q_INIT_RESOURCE(mdi);//使用 Qt 资源系统
QApplication app(argc, argv); MainWindow mainWin; mainWin.show();
return app.exec();
}
做完这些后,组织好你的工程内的文件(头文件、实现文件、资源文件、资源集文件 等),编译、链接、运行程序即可。一个典型的 MDI 应用程序界面如图 8-30 所示。
图 8-30 使用 QMdiArea 建立的 MDI 应用程序界面
到这里,关于如何使用 Qt4 创建 MDI 应用程序就讲解完了。采用 QMdiArea 的方法是笔 者重点讲解的,一是因为它是 Qt4.5 版以后官方所推荐采用的方法,用于替代 QWorkspace;二是因为它也是实现 MDI 的方法中使用起来最为复杂和令人困惑的一个。即 使是让 Qt“老鸟”来解释清楚什么是“主窗口的中心窗口”、“子窗口的内部窗口”、 “子窗口的中心区域”等等这些名词以及用法,也不是一件容易的事。
相对而言,使用 QWorkspace 创建 MDI 是比较容易的,但 Qt 以后将不再对这个类继续更新和支持,而“多实例实现多文档”的方法在 Mac OS X 上应用很广泛,它实质上就是使 用了多个顶层窗口,也是比较容易掌握的。
熟练使用 Qt4 建立 MDI 应用程序所涉及的内容远远不止本书所介绍的这些,比如子窗 口如何响应键盘与鼠标事件,如何同步所有主窗口的 “最近打开文件列表”等等问题都需要 费些力气解决。对于初学者而言,这是一个比较复杂的话题。大家在阅读到此处时,建议仍 然采用“知道、会用即可”的原则,不必深究它们背后的机理。随着学习进程的逐步深入, 一些开始接触时觉得困难的内容,就会在你心中逐渐明晰了。