ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
## 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 所示。 ![](https://box.kancloud.cn/2016-01-22_56a1a15547981.png) 图 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()等方法对窗口实例进行设置。 看着这张图再对照笔者上面的讲解,应该就很清楚了吧。 ![](https://box.kancloud.cn/2016-01-22_56a1a15556674.png) 图 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(); } ``` 整个程序的实现过程可以用图描述。 ![](https://box.kancloud.cn/2016-01-22_56a1a15568750.png) 图 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 所示。 ![](https://box.kancloud.cn/2016-01-22_56a1a1557add5.png) 图 8-30 使用 QMdiArea 建立的 MDI 应用程序界面 到这里,关于如何使用 Qt4 创建 MDI 应用程序就讲解完了。采用 QMdiArea 的方法是笔 者重点讲解的,一是因为它是 Qt4.5 版以后官方所推荐采用的方法,用于替代 QWorkspace;二是因为它也是实现 MDI 的方法中使用起来最为复杂和令人困惑的一个。即 使是让 Qt“老鸟”来解释清楚什么是“主窗口的中心窗口”、“子窗口的内部窗口”、 “子窗口的中心区域”等等这些名词以及用法,也不是一件容易的事。 相对而言,使用 QWorkspace 创建 MDI 是比较容易的,但 Qt 以后将不再对这个类继续更新和支持,而“多实例实现多文档”的方法在 Mac OS X 上应用很广泛,它实质上就是使 用了多个顶层窗口,也是比较容易掌握的。 熟练使用 Qt4 建立 MDI 应用程序所涉及的内容远远不止本书所介绍的这些,比如子窗 口如何响应键盘与鼠标事件,如何同步所有主窗口的 “最近打开文件列表”等等问题都需要 费些力气解决。对于初学者而言,这是一个比较复杂的话题。大家在阅读到此处时,建议仍 然采用“知道、会用即可”的原则,不必深究它们背后的机理。随着学习进程的逐步深入, 一些开始接触时觉得困难的内容,就会在你心中逐渐明晰了。