統的應用程序設計中有多文檔界面(Multi-document Interface,MDI)應用程序,Qt 為設計 MDI 應用程序提供了支持。
本節的實例 samp6_4 是一個 MDI 應用程序,程序運行效果如圖 1 所示。

圖 1 MDI 應用程序實例 samp6_4 的運行時界面
MDI 應用程序就是在主窗口里創建多個同類型的 MDI 子窗口,這些 MDI 子窗口在主窗口里顯示,並共享主窗口上的工具欄和菜單等操作功能,主窗口上的操作都針對當前活動的 MDI 子窗口進行。
設計 MDI 應用程序需要在主窗口工作區放置一個 QMdiArea 作為子窗體的容器。實例 samp6_4 主窗口的工作區使用一個 QMdiArea 組件,實例的子窗口類是 QFormDoc,是一個使用 QPlainTextEdit 進行簡單文本顯示和編輯的窗體。
創建的 QFormDoc 窗體對象作為一個子窗口加入到 mdiArea 組件中。QMdiArea 組件類似於實例 samp6_3 中主窗口上的 tabWidget 組件,只是 QMdiArea 提供更加完備的功能。更改 MDI 的顯示模式,可以得到與實例 samp6_3 相似的以多頁組件管理的 MDI 界面效果。
文檔窗口類QFormDoc的設計
以可視化方式創建一個基於 QWidget 的類 QFormDoc,設計可視化界面時,只放置一個 QPlainTextEdit 組件,並以水平布局填充滿整個窗口。這里不再用可視化的方式設計 Action,因為 QFormDoc 窗口不需要創建自己的工具欄,而是使用主窗口上的工具欄按鈕對 QFormDoc 窗體上的 QPlainTextEdit 組件進行操作。
為 QFormDoc 添加一些用於文件打開和編輯操作的接口函數,QFormDoc 類的完整定義如下:
- class QFormDoc : public QWidget
- {
- Q_OBJECT
- private:
- QString mCurrentFile; //當前文件
- bool mFileOpened=false; //文件已打開
- public:
- explicit QFormDoc(QWidget *parent = 0);
- ~QFormDoc();
- void loadFromFile(QString& aFileName); //打開文件
- QString currentFileName();//返回當前文件名
- bool isFileOpened();//文件已經打開
- void setEditFont();//設置字體
- void textCut(); //cut
- void textCopy(); //copy
- void textPaste(); //paste
- private:
- Ui::QFormDoc *ui;
- };
這些接口函數是為了在主窗口里調用,實現對 MDI 子窗口的操作。實現代碼如下:
- QFormDoc::QFormDoc(QWidget *parent) :
- QWidget(parent),
- ui(new Ui::QFormDoc)
- {
- ui->setupUi(this);
- this->setWindowTitle("New Doc"); //窗口標題
- this->setAttribute(Qt::WA_DeleteOnClose); //關閉時自動刪除
- }
- QFormDoc::~QFormDoc()
- {
- //QMessageBox::information(this,"信息","文檔窗口被釋放");
- delete ui;
- }
- void QFormDoc::loadFromFile(QString &aFileName)
- {//打開文件
- QFile aFile(aFileName); //以文件方式讀出
- if (aFile.open(QIODevice::ReadOnly | QIODevice::Text)) //以只讀文本方式打開文件
- {
- QTextStream aStream(&aFile); //用文本流讀取文件
- ui->plainTextEdit->clear();//清空
- ui->plainTextEdit->setPlainText(aStream.readAll()); //讀取文本文件
- aFile.close();//關閉文件
- mCurrentFile=aFileName;//保存當前文件名
- QFileInfo fileInfo(aFileName); //文件信息
- QString str=fileInfo.fileName(); //去除路徑后的文件名
- this->setWindowTitle(str);
- mFileOpened=true;
- }
- }
- QString QFormDoc::currentFileName()
- {
- return mCurrentFile;
- }
- bool QFormDoc::isFileOpened()
- { //文件是否已打開
- return mFileOpened;
- }
- void QFormDoc::setEditFont()
- {
- QFont font;
- font=ui->plainTextEdit->font();
- bool ok;
- font=QFontDialog::getFont(&ok,font);
- ui->plainTextEdit->setFont(font);
- }
- void QFormDoc::textCut()
- {
- ui->plainTextEdit->cut();
- }
- void QFormDoc::textCopy()
- {
- ui->plainTextEdit->copy();
- }
- void QFormDoc::textPaste()
- {
- ui->plainTextEdit->paste();
- }
注意作為 MDI 子窗口,不管其是否設置為關閉時刪除,在主窗口里關閉一個 MDI 子窗口時,都會刪除子窗口對象。
MDI 主窗口設計與子窗口的使用
主窗口界面設計
要在主窗口實現 MDI 功能,只需在主窗口的工作區放置一個 QMdiArea 組件。圖 2 是設計好的主窗口界面。

圖 2 設計時的主窗口
在 UI 設計器里創建 Action,並應用 Action 設計主工具欄。在主窗口的工作區放置一個 QMdiArea 組件,然后在主窗口的構造函數里設置 mdiArea 填充滿工作區。
- QWMainWindow::QWMainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::QWMainWindow)
- {
- ui->setupUi(this);
- this->setCentralWidget(ui->mdiArea);
- this->setWindowState(Qt::WindowMaximized);
- ui->mainToolBar->setToolButtonStyle(Qt::ToolButtonTextUnderlcon);
- }
MDI 子窗口的創建與加入
下面是主窗口上“新建文檔”按鈕的響應代碼:
- void QWMainWindow::on_actDoc_New_triggered()
- { //新建文檔
- QFormDoc *formDoc = new QFormDoc(this);
- ui->mdiArea->addSubWindow (formDoc) ; //文檔窗口添力口到 MDI formDoc->show();
- }
代碼功能是新建一個 QFormDoc 類的窗口 formDoc,構造函數中傳入了主窗口指針,所以主窗口是 formDoc 的父窗口,然后使用 QMdiArea 的 addSubWindow() 函數將 formDoc 加入到 mdiArea。
下面是主窗口上“打開文檔”按鈕的響應代碼:
- void QWMainWindow::on_actDoc_Open_triggered()
- {//打開文件
- //必須先獲取當前MDI子窗口,再使用打開文件對話框,否則無法獲得活動的子窗口
- bool needNew=false;// 是否需要新建子窗口
- QFormDoc *formDoc;
- if (ui->mdiArea->subWindowList().count()>0) //如果有打開的主窗口,獲取活動窗口
- {
- formDoc=(QFormDoc*)ui->mdiArea->activeSubWindow()->widget();
- needNew=formDoc->isFileOpened();//文件已經打開,需要新建窗口
- }
- else
- needNew=true;
- QString curPath=QDir::currentPath();
- QString aFileName=QFileDialog::getOpenFileName(this,tr("打開一個文件"),curPath,
- "C程序文件(*.h *cpp);;文本文件(*.txt);;所有文件(*.*)");
- if (aFileName.isEmpty())
- return; //如果未選擇文件,退出
- if (needNew) //需要新建子窗口
- {
- formDoc = new QFormDoc(this);//指定父窗口,必須在父窗口為Widget窗口提供一個顯示區域
- ui->mdiArea->addSubWindow(formDoc);
- }
- formDoc->loadFromFile(aFileName); //打開文件
- formDoc->show();
- ui->actCut->setEnabled(true);
- ui->actCopy->setEnabled(true);
- ui->actPaste->setEnabled(true);
- ui->actFont->setEnabled(true);
- }
通過 QMdiArea::subWindowList() 可以獲得子窗口對象列表,從而可以判斷子窗口的個數。如果沒有一個MDI子窗口,就創建一個新的窗口並打開文件。
如果有 MDI 子窗口,則總有一個活動窗口,通過 QMdiArea::activeSubWindow() 可以獲得此活動的子窗口,通過子窗口的 isFileOpened() 函數判斷是否打開了文件,如果沒有打開過文件,就在這個活動窗口里打開文件,否則新建窗口打開文件。
注意一定要先獲取 MDI 子窗口,再使用 QFileDialog 選擇需要打開的文件。如果順序更換了,則無法獲得正確的 MDI 活動子窗口。
QMdiArea常用功能函數
QMdiArea 提供了一些成員函數,可以進行一些操作,工具欄上的“關閉全部”、“MDI模式”、“級聯展開”、“平鋪展開”等按鈕都是調用 QMdiArea 類的成員函數實現的。
下面是這幾個按鈕功能的實現代碼:
- void QWMainWindow::on_actCascade_triggered()
- { //窗口級聯展開
- ui->mdiArea->cascadeSubWindows();
- }
- void QWMainWindow::on_actTile_triggered()
- {//平鋪展開
- ui->mdiArea->tileSubWindows();
- }
- void QWMainWindow::on_actCloseALL_triggered()
- {//關閉全部子窗口
- ui->mdiArea->closeAllSubWindows();
- }
- void QWMainWindow::on_actViewMode_triggered(bool checked)
- {//MDI 顯示模式
- if (checked) //Tab多頁顯示模式
- {
- ui->mdiArea->setViewMode(QMdiArea::TabbedView); //Tab多頁顯示模式
- ui->mdiArea->setTabsClosable(true); //頁面可關閉
- ui->actCascade->setEnabled(false);
- ui->actTile->setEnabled(false);
- }
- else ////子窗口模式
- {
- ui->mdiArea->setViewMode(QMdiArea::SubWindowView); //子窗口模式
- ui->actCascade->setEnabled(true); //
- ui->actTile->setEnabled(true); //
- }
- }
其中,設置 MDI 視圖模式用 setViewMode() 函數,有兩種模式可以選擇:
- QMdiArea::Sub Window View 是傳統的子窗口模式,顯不效果如圖 1 所示。
- QMdiArea::TabbedView 是多頁的顯示模式,顯示效果如圖 3 所示。

圖 3 多頁模式下 MDI 界面
MDI的信號
QMdiArea 有一個信號 subWindowActivated(QMdiSubWindow *argl),在當前活動窗口切換時發射,利用此信號可以在活動窗口切換時進行一些處理,例如,在狀態欄里顯示活動 MDI 子窗口的文件名,在沒有 MDI 子窗口時,將工具欄上的編輯功能按鈕設置為禁用。
下面是該信號的槽函數代碼:
- void QWMainWindow::on_mdiArea_subWindowActivated(QMdiSubWindow *arg1)
- {//當前活動子窗口切換時
- if (ui->mdiArea->subWindowList().count()==0)
- { //若子窗口個數為零
- ui->actCut->setEnabled(false);
- ui->actCopy->setEnabled(false);
- ui->actPaste->setEnabled(false);
- ui->actFont->setEnabled(false);
- ui->statusBar->clearMessage();
- }
- else
- {
- QFormDoc *formDoc=static_cast<QFormDoc*>(ui->mdiArea->activeSubWindow()->widget());
- ui->statusBar->showMessage(formDoc->currentFileName()); //顯示主窗口的文件名
- }
- }
主窗口工具欄上的“剪切”、“復制”、“粘貼”、“字體設置”等按鈕都是調用當前子窗口的相應函數,關鍵是獲取當前 MDI 子窗體對象。
例如,“剪切”和“字體設置”按鈕的代碼如下:
- void QWMainWindow::on_actCut_triggered()
- { //cut
- QFormDoc* formDoc=(QFormDoc*)ui->mdiArea->activeSubWindow()->widget();
- formDoc->textCut();
- }
- void QWMainWindow::on_actFont_triggered()
- {//設置字體
- QFormDoc* formDoc=(QFormDoc*)ui->mdiArea->activeSubWindow()->widget();
- formDoc->setEditFont();
- }
- 參考網址
- http://m.biancheng.net/view/1875.html