6.6 多文件介面(Multiple Document Interface)
轉自:http://zlhwhj.blog.163.com/blog/static/253900342008125114147389/
一個主視窗中央區域內能夠提供多個文件的程式稱之為多文件程式,或者MDI程式。在Qt中,一個MDI程式是由QWorkspace類實現的,把QWorkspace做為中央控制元件,每一個文件視窗做為QWorkspace的子控制元件。
MDI程式的慣例是提供一個window選單,管理視窗的顯示方式和當前開啟的視窗列表。正在活動的視窗由選中記號標示。使用者可以點選window選單中視窗列表中的一個視窗把它啟用。
在這一節中,我們實現一個圖6.16所示的MDI編輯程式,介紹如何建立MDI程式,如何實現window選單。
Figure 6.16. The MDI Editor application
這個應用程式包含兩個類:MainWindow和Editor類。程式中大部分程式碼和第一部分的Spreadsheet程式相似,這裡我們只介紹新增的程式碼。
Figure 6.17. The MDI Editor application's menus
首先看一下MainWindow類:
MainWindow::MainWindow()
{
workspace = new QWorkspace;
setCentralWidget(workspace);
connect(workspace, SIGNAL(windowActivated(QWidget *)),
this, SLOT(updateMenus()));
createActions();
createMenus();
createToolBars();
createStatusBar();
setWindowTitle(tr("MDI Editor"));
setWindowIcon(QPixmap(":/images/icon.png"));
}
在MainWindow的建構函式中,我們建立了一個QWorkSpace控制元件,並把這個控制元件做為一箇中央控制元件。連線QWorkSpace的windowActivated()訊號和updateMenus()函式,對window選單進行更新。
void MainWindow::newFile()
{
Editor *editor = createEditor();
editor->newFile();
editor->show();
}
函式newFile()用來對應File|New選單,呼叫createEditor()私有函式建立一個子控制元件Editor。
Editor *MainWindow::createEditor()
{
Editor *editor = new Editor;
connect(editor, SIGNAL(copyAvailable(bool)),
cutAction, SLOT(setEnabled(bool)));
connect(editor, SIGNAL(copyAvailable(bool)),
copyAction, SLOT(setEnabled(bool)));
workspace->addWindow(editor);
windowMenu->addAction(editor->windowMenuAction());
windowActionGroup->addAction(editor->windowMenuAction());
return editor;
}
函式createEditor()建立一個Editor控制元件,連線兩個訊號和槽,這保證了Edit|Cut選單和Edit|Copy選單的使能狀態依賴於是否有選中的文字,如果有選中的文字,Edit|Cut選單和Edit|Copy選單變為可用狀態。
因為是MDI程式,主視窗中可能有多個Editor控制元件。問題是當前活動的Editor視窗發出的copyAvailable(bool)訊號才能改變選單的狀態。實際上也只有當前活動的視窗能夠發出訊號,所以這個問題也不用考慮。
一旦新加了一個Editor控制元件,我們在Window選單中增加一個QAction啟用這個視窗。這個QAction是Editor類提供的,稍後會介紹。我們也向QActionGroup物件中新增了該Action,這樣QActionGroup保證了視窗選單中一次只有一項是選中的,即只有一個視窗是啟用的。
void MainWindow::open()
{
Editor *editor = createEditor();
if (editor->open()) {
editor->show();
} else {
editor->close();
}
}
函式open()對應選單File|Open。為新文件建立一個Editor,並在Editor上呼叫open()函式。這樣保證了檔案操作的實現是在Editor類中實現而非MainWindow類,因為每個Editor類需要維護自己的獨立狀態。
如果open()失敗,關閉Editor,錯誤的原因由Editor類已經告訴使用者。我們不必顯式的刪除Editor物件,在Editor的建構函式中,設定了Qt::WA_DeleteOn_Close屬性,在關閉的同時Editor會自動刪除自己。
void MainWindow::save()
{
if (activeEditor())
activeEditor()->save();
}
槽save()功能:如果有一活動Editor,則呼叫當前活動的Editor::save()。具體的實現操作也是在Editor中實現。
Editor *MainWindow::activeEditor()
{
return qobject_cast<Editor *>(workspace->activeWindow());
}
函式activeEditor()返回當前活動的Editor型別的子視窗指標,如果沒有活動視窗,則返回一個空指標。
void MainWindow::cut()
{
if (activeEditor())
activeEditor()->cut();
}
槽cut()呼叫當前活動的Editor::cut(),copy(),paste()函式和cut()函式相同,在此略去不談。
void MainWindow::updateMenus()
{
bool hasEditor = (activeEditor() != 0);
bool hasSelection = activeEditor()
&& activeEditor()->textCursor().hasSelection();
saveAction->setEnabled(hasEditor);
saveAsAction->setEnabled(hasEditor);
pasteAction->setEnabled(hasEditor);
cutAction->setEnabled(hasSelection);
copyAction->setEnabled(hasSelection);
closeAction->setEnabled(hasEditor);
closeAllAction->setEnabled(hasEditor);
tileAction->setEnabled(hasEditor);
cascadeAction->setEnabled(hasEditor);
nextAction->setEnabled(hasEditor);
previousAction->setEnabled(hasEditor);
separatorAction->setVisible(hasEditor);
if (activeEditor())
activeEditor()->windowMenuAction()->setChecked(true);
}
當啟用一個視窗或者關閉最後一個視窗時,呼叫updateMenus()槽更新選單,updateMenus()是槽函式,在MainWindow的建構函式呼叫了這個函式,使程式啟動時也能更新選單。
只要有一個活動視窗,大部分選單都是有意義的,如果沒有活動視窗,這些選單都被禁止。最後,呼叫QAction::setChecked()標示活動視窗。由於使用了QActionGroup,以前標示的活動視窗自動取消。
void MainWindow::createMenus()
{
...
windowMenu = menuBar()->addMenu(tr("&Window"));
windowMenu->addAction(closeAction);
windowMenu->addAction(closeAllAction);
windowMenu->addSeparator();
windowMenu->addAction(tileAction);
windowMenu->addAction(cascadeAction);
windowMenu->addSeparator();
windowMenu->addAction(nextAction);
windowMenu->addAction(previousAction);
windowMenu->addAction(separatorAction);
...
}
列出的這部分的createMenu()這段程式碼實現了window選單。這些QAction能夠很容易通過QWorkspace的成員函式實現,如,closeActiveWindow(),closeAllWindow(),tile(),cascade(),只要開啟一個新的子視窗,就在window選單中加一個Action。當使用者關閉一個視窗時,相應的window選單項就會刪除,這個Action會自動從Window選單中刪除。
void MainWindow::closeEvent(QCloseEvent *event)
{
workspace->closeAllWindows();
if (activeEditor()) {
event->ignore();
} else {
event->accept();
}
}
虛擬函式closeEvent()給每一個子視窗傳送關閉事件,關閉子視窗。如果還有一個子視窗,這很可能是因為使用者在“unsaved changes”訊息對話方塊中選擇了cancel按鈕,因此忽略這個關閉事件。如果沒有活動視窗,Qt關閉所有的視窗。如果我們不在MainWindow類中重寫closeEvent(),使用者就沒有機會儲存文件的改變。
以上是MainWindow部分的程式碼。接著看Editor類的實現。Editor類代表的是一個子視窗。它繼承自QTextEdit,基類中提供了文字編輯函式。Qt中的所有控制元件都可以做為一個獨立的視窗,因此也能做為MDI中的一個子視窗。
類定義如下:
class Editor : public QTextEdit
{
Q_OBJECT
public:
Editor(QWidget *parent = 0);
void newFile();
bool open();
bool openFile(const QString &fileName);
bool save();
bool saveAs();
QSize sizeHint() const;
QAction *windowMenuAction() const { return action; }
protected:
void closeEvent(QCloseEvent *event);
private slots:
void documentWasModified();
private:
bool okToContinue();
bool saveFile(const QString &fileName);
void setCurrentFile(const QString &fileName);
bool readFile(const QString &fileName);
bool writeFile(const QString &fileName);
QString strippedName(const QString &fullFileName);
QString curFile;
bool isUntitled;
QString fileFilters;
QAction *action;
};
在Spreadsheet程式中的四個私有函式,也同樣出現在Editor類中:okToContinue(),saveFile(),setCurrentFile(),stripptedName()。
Editor::Editor(QWidget *parent)
: QTextEdit(parent)
{
action = new QAction(this);
action->setCheckable(true);
connect(action, SIGNAL(triggered()), this, SLOT(show()));
connect(action, SIGNAL(triggered()), this, SLOT(setFocus()));
isUntitled = true;
fileFilters = tr("Text files (*.txt)\n"
"All files (*)");
connect(document(), SIGNAL(contentsChanged()),
this, SLOT(documentWasModified()));
setWindowIcon(QPixmap(":/images/document.png"));
setAttribute(Qt::WA_DeleteOnClose);
}
在建構函式中,我們首先建立一個QAction,把它新增到Window選單中表示一個editor,並把這個QAction發出的訊息triggered()和視窗的show(),setFocus()槽連線起來。
這個MDI程式允許使用者建立任意數量的Editor視窗,因此我們必須在新建時給文件一個預設的名字,這樣在儲存時才能把不同的文件區分開。通常的做法是用一個包含一個數字的名字,例如,document1.txt。變數isUntitled區分文件的名字是使用者輸入的還是程式自動生成的。
我們連線文件的contentsChanged()訊號和documentWasModified()槽,這個函式只是呼叫setWindowModified(true)。
最後設定屬性Qt::WA_DeleteOnClose,在關閉Editor視窗時自動刪除它,避免記憶體洩漏。
void Editor::newFile()
{
static int documentNumber = 1;
curFile = tr("document%1.txt").arg(documentNumber);
setWindowTitle(curFile + "[*]");
action->setText(curFile);
isUntitled = true;
++documentNumber;
}
在newFile()函式中給新建的文件一個類似document1.txt的名字。這段程式碼放在了newFile()中而不是在建構函式中,是因為documentNumber是一個靜態的變數,在所有的Editor型別的物件中只有一個例項,我們不想呼叫open()函式來開啟一個已存在的文件時也浪費該數字,增加documentNumber的值。
主視窗標題中的[*]是一個位置標識號。在非Mac OS X平臺上表示文件有需要儲存。這個標識號在第三章也出現過。
bool Editor::open()
{
QString fileName =
QFileDialog::getOpenFileName(this, tr("Open"), ".",
fileFilters);
if (fileName.isEmpty())
return false;
return openFile(fileName);
}
函式open()通過使用openFile()開啟一個已經存在的檔案。
bool Editor::save()
{
if (isUntitled) {
return saveAs();
} else {
return saveFile(curFile);
}
}
如果isUntitled為true,則呼叫函式saveAs()讓使用者給文件輸入一個名字,如果isUntitled為false,呼叫saveFile()函式。
void Editor::closeEvent(QCloseEvent *event)
{
if (okToContinue()) {
event->accept();
} else {
event->ignore();
}
}
closeEvent()函式是重寫實現的,允許使用者儲存文件的改變。使用者是否儲存在okToContinue()函式中實現,彈出對話方塊“Do you want to save your changes?”,如果okToContinue()返回為true,接受這個關閉事件,否則,忽略這個事件,不關閉視窗。
void Editor::setCurrentFile(const QString &fileName)
{
curFile = fileName;
isUntitled = false;
action->setText(strippedName(curFile));
document()->setModified(false);
setWindowTitle(strippedName(curFile) + "[*]");
setWindowModified(false);
}
函式setCurrentFile()在openFile()和saveFile()中被呼叫,用以改變curFile和isUtitled變數的值,設定視窗標題和子視窗對應的QAction的名稱,設定document()->setModified(false)。如果使用者改變了文件中的文字,QTextDocument會發出contentsChanged()訊號,把“modified”值為true。
QSize Editor::sizeHint() const
{
return QSize(72 * fontMetrics().width('x'),
25 * fontMetrics().lineSpacing());
}
用字母“X”的寬度和一行字元的高度做參考,sizeHint()函式返回一個尺寸,QWorkspace用這個尺寸初始化視窗大小。
以下是這個函式的main.cpp檔案:
#include <QApplication>
#include "mainwindow.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QStringList args = app.arguments();
MainWindow mainWin;
if (args.count() > 1) {
for (int i = 1; i < args.count(); ++i)
mainWin.openFile(args[i]);
} else {
mainWin.newFile();
}
mainWin.show();
return app.exec();
}
如果使用者在命令列上指定了一個檔案,則開啟這個檔案,否則新建一個空文件。Qt指定的選項如-style和-font自動由QApplication的建構函式從引數列表中刪除,因此如果在命令列上這樣寫:
mdieditor -style motif readme.txt
QApplication::arguments()返回一個包含兩個字串(mdieditor和readme.text)的QStringList,程式開啟文件readme.txt。
MDI是同時處理多個文件的一種方法。在Mac OS X上,較好的方法是使用多個頂層的視窗,在第三章“多文件”中有介紹。
相關文章
- interface/介面
- 多文件介面(MDI)(轉)
- public interface View介面和public interface ViewResolver介面介紹View
- 如何理解 interface 介面
- Java-介面(interface)Java
- java之介面interfaceJava
- C#介面interfaceC#
- c# interface介面C#
- firewalld: 介面interface操作
- MongoDB(5)- Document 文件相關MongoDB
- java中的interface(介面)Java
- c# interface介面之C#
- Multiple Books多賬薄
- DOM (文件物件模型(Document Object Model))物件模型Object
- JavaScript 元素在document文件中位置JavaScript
- 瞭解下C# 介面(Interface)C#
- go sort.Interface 排序介面Go排序
- Interface(介面分享)第一節
- C#-介面(Interface)詳解C#
- Golang interface介面深入理解Golang
- Elasticsearch——分散式文件系統之documentElasticsearch分散式
- ambari2.2.1 for redhat6.6安裝部署文件Redhat
- interface 介面 -Go 學習記錄Go
- Spring Boot 注入介面 @Autowired interfaceSpring Boot
- Java介面(interface)的概念及使用Java
- ElasticSearch 文件(document)內部機制詳解Elasticsearch
- Golang 學習——interface 介面學習(一)Golang
- Golang 學習——interface 介面學習(二)Golang
- PHP interface(介面)的示例程式碼PHP
- JavaScript設計模式--實現介面InterfaceJavaScript設計模式
- PLC結構化文字(ST)——介面(Interface)
- 【MOS】Parameter FILESIZE - Multiple Export Files (文件 ID 290810.1)Export
- LEADTOOLS的新功能Document Composer可以讓你在多個檔案中建立文件
- C#學習筆記——MDI窗體(多文件介面)C#筆記
- 介面文件生成
- 介面文件 工具
- 使用Golang的interface介面設計原則Golang
- 什麼是Java Marker Interface(標記介面)Java