## 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 应用程序所涉及的内容远远不止本书所介绍的这些,比如子窗 口如何响应键盘与鼠标事件,如何同步所有主窗口的 “最近打开文件列表”等等问题都需要 费些力气解决。对于初学者而言,这是一个比较复杂的话题。大家在阅读到此处时,建议仍 然采用“知道、会用即可”的原则,不必深究它们背后的机理。随着学习进程的逐步深入, 一些开始接触时觉得困难的内容,就会在你心中逐渐明晰了。
- 第 1 章 走近 Qt
- 1.1 Qt 简介
- 1.2 Qt 纪事概览
- 1.3 Qt 套件的组成(以 Qt4.5 为准)
- 1.4 Qt 的授权
- 1.5 Qt 的产品
- 1.6 Qt 的服务与支持
- 1.7 Qt 的最新进展
- 1.8为什么选择 Qt
- 1.9 问题与解答
- 1.10 总结与提高
- 第 2 章 Qt 的安装与配置
- 2.1 获取 Qt
- 2.2 协议说明
- 2.3 安装 Qt
- 2.4 配置 Qt4 环境
- 2.5 问题与解答
- 2.6 总结与提高
- 第 3 章 Qt 编程基础
- 3.1 标准 C++精讲
- 3.2 Windows 编程基础
- 3.3 Linux 编程基础
- 3.4 Mac 编程基础
- 3.5 问题与解答
- 3.6 总结与提高
- 第 4 章 Qt 4 集成开发环境
- 4.1 常见的 Qt IDE
- 4.2 Qt Creator
- 4.3 Eclipse
- 4.5 问题与解答
- 4.6 总结与提高
- 第 5 章 使用 Qt 基本 GUI 工具
- 5.1 使用 Qt Designer 进行 GUI 设计
- 5.2 使用 Qt Assistant 获取在线文档与帮助
- 5.3 使用 Qt Demo 学习 Qt 应用程序开发
- 5.4 问题与解答
- 5.5 总结与提高
- 第 6 章 Qt 4 程序开发方法和流程
- 6.1 开发方法
- 6.2 Hello Qt
- 6.3 几个重要的知识点
- 6.4 问题与解答
- 6.5 总结与提高
- 第 7 章 对话框
- 7.1 QDialog 类
- 7.2 子类化 QDialog
- 7.3 快速设计对话框
- 7.4 常见内建(built in)对话框的使用
- 7.5 模态对话框与非模态对话框
- 7.6 问题与解答
- 7.7 总结与提高
- 第 8 章 主窗口
- 8.1 主窗口框架
- 8.2 创建主窗口的方法和流程
- 8.3 代码创建主窗口
- 8.4 使用 Qt Designer 创建主窗口
- 8.5 中心窗口部件专题
- 8.6 Qt4 资源系统专题
- 8.7 锚接窗口
- 8.8 多文档
- 8.9 问题与解答
- 8.10 总结与提高
- 第 9 章 Qt 样式表与应用程序观感
- 9.1 应用程序的观感
- 9.2 QStyle 类的使用
- 9.3 样式表概述
- 9.4 使用样式表
- 9.5 问题与解答
- 9.6 总结与提高
- 第 10 章 在程序中使用.ui 文件
- 10.1 uic 的使用
- 10.2 Ui_YourFormName.h 文件的组成
- 10.3 编译时加入处理.ui 文件的方法
- 10.4 运行时加入处理.ui 文件的方法
- 10.5 信号与槽的自动连接
- 10.6 问题与解答
- 10.7 总结与提高 本章主要讲解了以下内容:
- 第 11 章 布局管理
- 11.1 基本概念和方法
- 11.2在 Qt Designer 中使用布局
- 11.3 基本布局实践
- 11.4 堆栈布局
- 11.5 分裂器布局
- 11.6 自定义布局管理器
- 11.7 布局管理经验总结
- 11.8 问题与解答
- 11.9 总结与提高
- 第 12 章 使用 Qt Creator
- 12.1 Qt Creator 概览
- 12.2 Qt Creator 的组成
- 12.3 快捷键和常用技巧
- 12.4 Qt Creator 构建系统的设置
- 12.5 处理项目间依赖关系( Dependencies )
- 12.6 Qt 多版本共存时的管理
- 12.7 使用定位器在代码间快速导航
- 12.8 如何创建一个项目
- 12.9 实例讲解
- 12.10 使用 Qt Creator 调试程序
- 12.11 问题与解答
- 12.12 总结与提高
- 第 13 章 Qt 核心机制与原理
- 13.1 Qt 对标准 C++的扩展
- 13.2 信号与槽
- 13.3 元对象系统
- 13.4 Qt 的架构
- 13.5 Qt 的事件模型
- 13.6 构建 Qt 应用程序
- 13.7 总结与提高
- 附录 A qmake 使用指南
- A.1 qmake 简介
- A.2 使用 qmake
- 附录 B make 命令
- B.1 命令解释
- B.2 使用 make 自动构建
- 附录 C Qt 资源
- C.1Qt 官方资源
- C.2 Qt 开发社区