前言:
本文將介紹怎樣用Qt做一個簡單的多文檔編輯器,該實驗的過程中主要涉及到Qt窗口的設計,菜單欄(包括右擊菜單),工具欄,狀態欄,常見的文本文件等操作。參考資料為網址上的一個例子:http://www.yafeilinux.com/
本來是在ubuntu下做這個實驗的,可是一開始建立菜單欄等時,里面用的是中文,運行后中文就是不顯示.在網上找了2天的辦法,各種手段都試過了,什么編碼方式啊,什么實用QTextCodec這個類啊都試過了,沒成功。很不爽,暫時還是轉到windows下吧。在ubuntu下只是簡單的設計了該程序的界面,然后把那些代碼弄到windows下后,打開main.pp文件后出現直接在main.cpp中出現 了 could not decode "main.cpp" with "System"-encoding這種錯誤提示,發現在這個源文件中不能輸入中文(比如說注釋的時候想用中文注釋).但是在同一個工程的其它cpp文件中就可以輸入中文.看了下其它的cpp文件的encoding也是System的.誤解,又是編碼問題!!
實驗過程:
下面講的主要是一個個簡單功能的逐步實現過程的某些細節。
界面的設計:
在action編輯器中,Text欄中輸入文字內容,如果有快捷方式,也最好用括號將其注釋起來 ;Object Name中輸入目標名字,最好采用默認的action開頭命名;Tooltip是當鼠標靠近該菜單一會兒的時候會提示的文字,這里一般與Text欄中除括號注釋外的相同;ShotCut一欄中直接用鍵盤按下快捷鍵一遍即可,注意按下Ctrl+Tab時顯示的為Ctrl+Tab,當按下Ctrl+Shift+Tab時顯示的是Ctrl+Shift+Backtab.;
菜單涉及完成后 如下圖所示:
上面的used一欄不可用,當按住action編輯器中的每一欄,拖動到對應菜欄下的typehere了就變成打勾可用了。
MyMdi文檔類的建立:
新建一個類,名字取為MyMdi,基類名為QTextEdit(注意,因為下拉列框中可選的基類有限,所以這里可以自己輸入),類型信息選擇繼承來自QWidget。
因為我們在建立工程的時候,其主界面就是用的MainWindow這個類,這個類主要負責主界面的一些界面的布局(比如菜單欄,工具欄,狀態欄等),顯示,退出和一些人機交互等。那么我們新建的MyMdi這個類就不需要負責它在它的父窗口中的顯示退出等,只需負責自己窗口的布局和界面顯示等。這種思想是說每個界面都單獨分離開來,只負責自己界面的實現和與它的子界面交互。
好像window和widget不同,window為窗口,包括菜單欄,工具欄,狀態欄等,而widget一般不包括這些,只包括其文本欄。
打開文件功能的實現:
1. 單擊工具欄上的打開文件,則會彈出相應的對話框,選中所需要打開的文本文件。
2. 如果該文件已經被打開過,則設置顯示該文件對應的窗口為活動窗口。
3. 如果該文件沒有被打開過,則新建一個窗口,該窗口貼在其父窗口中。且此時把文件打開,打開成功則狀態欄對應顯示成功信息,否則輸出錯誤信息。
4. 過程3中打開文件是以只讀和文本方式打開文件的,打開完后將文本內容顯示到窗口,並設置好文件窗口的標題信息等。
5. 如果文本內容變化后但沒有保存,則窗口的標題有*號在后面。
新建文件功能的實現:
1. 單擊工具欄上的新建文件,則新建立一個窗口對象,其類為MyMdi,本身具備輸入文字的功能,因為是繼承的QTextEdit。
2. 設置好標題欄等信息,且當有文字內容改變又沒有保存的情況下則后面也一樣顯示*號。
保存文件功能的實現:
1. 如果是新建的文件,單擊保存時會自動跳到另存為那邊,即彈出一個另存為對話框,重新選擇保存文件的目錄和文件名。
2. 如果是已經保存過的文件,比如說打開的文件,單擊菜單欄下的保存時,其內部執行的是用文件流將打開的文件寫入到指定的文件名中。
關閉窗口功能的實現:
當單擊窗口右上角的關閉按鈕時,程序會自動執行該窗口的closeEvent()函數,所以如果我們在關閉窗口時需要某些功能,可以重寫這個函數。
復制粘貼剪切撤銷等功能實現:
因為MyMdi這個類是繼承QTextEdit類的,所以這些方法都可以直接調用QTextEdit類里面對應的方法就可以了。
更新菜單欄和工具欄功能的實現:
菜單欄中並不是所有的操作都是可用的,比如說復制,如果沒有活動窗口,或者即使有活動窗口但是沒有選中文本,則該操作不可以,同理,剪切也是一樣。
另外,撤銷和恢復都是要經過系統判斷,當前是否可用執行這些操作,如果可以則這些操作對應的圖標為亮色,可用,否則為灰色不可用。
狀態欄的操作也是一樣,當有光標移動時,狀態欄顯示的行列號值才會跟着變化。
更新窗口子菜單欄功能實現:
當打開多個文檔時,窗口子菜單下面會自動列出這些文檔的名字,且作為一個組單獨用分隔符與上面的子菜單隔開。我們可以在該菜單欄下選擇一個文檔,選完后該文檔會被自動當做活動文檔,且處於選中狀態。前9個文檔可以用1~9這些數字做為快捷鍵。
保存窗口設置功能實現:
如果軟件需要實現這一功能:當下次打開時和上次該軟件關閉時的窗口大小,位置一樣。那么我們就必須在每次關閉軟件時,保留好窗口大小,尺寸等信息,當下次打開該軟件時,重新讀取這些信息並對窗口進行相應的設置。這里需要用到QSettings這個類,該類是永久保存於平台無關的應用程序的一些設置的類。在本程序中,關閉軟件時寫入窗口信息,打開軟件在構造函數中讀取該信息並設置相應的窗口。
自定義右鍵菜單欄功能實現:
默認的右鍵菜單欄為英文的,我們這里需要把它弄成中文的,只需在MyMdi這個類中重寫函數contextMenuEvent(QContextMenuEvent *event)即可。在該函數中,只需新建一個菜單,然后動態為這個菜單加入action,並且為每個action設置快捷鍵,同時也需要根據情況實現對應action是否可用。
初始化窗口的實現:
這一部分包括設置窗口標題,設置工具欄標題,設置水平垂直滾動條,在狀態欄上加一個label,狀態欄上顯示菜單欄上各種action的提示信息,雖然這個初始化窗口是在構造函數中調用的,但是這些設置在整個應用程序中都有效。
實驗結果:
本實驗的功能在上面幾個過程中已有實現,類似於windows下的記事本一樣。下面是其效果一張簡單的截圖:
實驗主要部分代碼即注釋(附錄有工程code下載鏈接):
mymdi.h:
#ifndef MYMDI_H #define MYMDI_H #include <QTextEdit> class MyMdi : public QTextEdit { Q_OBJECT public: explicit MyMdi(QWidget *parent = 0); void NewFile(); bool LoadFile(const QString &file_name); QString CurrentFilePath(); QString get_current_file_name(); void SetCurrentFile(const QString &file_name); bool Save(); bool SaveAs(); bool SaveFile(const QString &file_name);//因為Save()和SaveAs()有很多共同的代碼,所以最好單獨寫個函數供其調用。 signals: public slots: private: QString current_file_path_;//當前文件的文件名 bool is_saved_; //文件是否保存標志 bool has_saved(); void contextMenuEvent(QContextMenuEvent *event); protected: void closeEvent(QCloseEvent *);//重寫關閉事件 private slots: void DocumentWasModified();//當文檔內容被改后所需執行的操作 }; #endif // MYMDI_H
mymdi.cpp:
#include "mymdi.h" #include <QFile> #include <QMessageBox> #include <QTextStream> #include <QApplication> #include <QFileInfo> #include <QFileDialog> #include <QPushButton> #include <QCloseEvent> #include <QMenu> MyMdi::MyMdi(QWidget *parent) : QTextEdit(parent)//因為MyMdi是繼承QTextEdit類的,所以它本身就是一個文本編輯類,可以編輯文字 { setAttribute(Qt::WA_DeleteOnClose);//加入了這句代碼后,則該窗口調用close()函數不僅僅是隱藏窗口而已,同時也被銷毀 is_saved_ = false; } void MyMdi::NewFile() { static int sequence_number = 1; is_saved_ = false; current_file_path_ = tr("未命名文檔%1.txt").arg(sequence_number++); setWindowTitle(current_file_path_ + "[*]");//設置文檔默認標題,“[*]”在默認情況下是什么都不顯示的,只有當調用setWindowModified() //函數的時候,會自動在由“[*]”的地方加上“*”,后面的文字會自動后移 connect(document(), SIGNAL(contentsChanged()), this, SLOT(DocumentWasModified()));//文檔內容發生改變時, //觸發槽函數DocumentWasModified(). } QString MyMdi::CurrentFilePath() { return current_file_path_;//current_file_path_是私有變量,對外隱藏起來了,但是CurrentFilePath()是公有成員函數,顯示出現 } //設置當前文件的一些信息,比如說窗口標題,該文件的路徑名等 void MyMdi::SetCurrentFile(const QString &file_name) { current_file_path_ = QFileInfo(file_name).canonicalFilePath();//得到解釋過后的絕對路徑名 is_saved_ = true;//設置為被保存過,因為該函數是被LoadFile()函數調用的,所以肯定可以被當做是保存過的了 document()->setModified(false);//文檔沒有被改過 setWindowModified(false);//窗口不顯示被更改的標志 setWindowTitle(get_current_file_name() + "[*]");//設置窗口標題 } bool MyMdi::LoadFile(const QString &file_name) { QFile file(file_name);//建立需打開的文件對象 if(!file.open(QFile::ReadOnly | QFile::Text)) { //打開失敗時,輸出錯誤信息 QMessageBox::warning(this, "多文檔編輯器", tr("無法讀取文件 %1:\n%2").arg(file_name).arg(file.errorString())); return false; } QTextStream in(&file);//文本流 QApplication::setOverrideCursor(Qt::WaitCursor);//設置整個應用程序的光標形狀為等待形狀,因為如果文件的內容非常多時可以提醒用戶 setPlainText(in.readAll());//讀取文本流中的所有內容,並顯示在其窗體中 QApplication::restoreOverrideCursor();//恢復開始時的光標狀態 SetCurrentFile(file_name);//設置標題什么的 //注意這里發射信號用的是contentsChanged(),而不是contentsChange(). connect(document(), SIGNAL(contentsChanged()), this, SLOT(DocumentWasModified())); return true; } QString MyMdi::get_current_file_name() { return QFileInfo(current_file_path_).fileName();//從當前文件路徑名中提取其文件名 } void MyMdi::DocumentWasModified() { setWindowModified(document()->isModified());//“*”顯示出來 } bool MyMdi::has_saved() { if(document()->isModified()) { QMessageBox box; box.setWindowTitle(tr("多文檔編輯器")); box.setText(tr("是否保存對%1的更改?").arg(get_current_file_name())); box.setIcon(QMessageBox::Warning);//警告圖標 //下面是消息box上添加3個按鈕,分別為yes,no,cancel QPushButton *yes_button = box.addButton(tr("是"), QMessageBox::YesRole); QPushButton *no_button = box.addButton(tr("否"), QMessageBox::NoRole); QPushButton *cancel_button = box.addButton(tr("取消"), QMessageBox::RejectRole); box.exec();//在這里等待用戶選擇3個按鈕中的一個 if(box.clickedButton() == yes_button) return Save(); else if(box.clickedButton() == no_button) return true;//不用保存,直接關掉 else if(box.clickedButton() == cancel_button) return false;//什么都不做 } return true;//要么已經保存好了,要么根本就沒更改過其內容 } bool MyMdi::Save() { if(is_saved_)//已經保存過至少一次后,則說明文件的文件名等已經弄好了,直接保存內容即可。 return SaveFile(current_file_path_); else return SaveAs();//第一次保存時,需要調用SaveAs } bool MyMdi::SaveAs() { //返回的名字file_name是自己手動輸入的名字,或者直接采用的是默認的名字 QString file_name = QFileDialog::getSaveFileName(this, tr("另存為"), current_file_path_); if(file_name.isEmpty()) return false; return SaveFile(file_name); } bool MyMdi::SaveFile(const QString &file_name) { QFile file(file_name); //即使是寫入文本,也得將文本先打開 if(!file.open(QFile::WriteOnly | QFile::Text)) { QMessageBox::warning(this, "多文檔編輯器", tr("無法寫入文件 %1:\n%2").arg(file_name).arg(file.errorString())); return false; } QTextStream out(&file); QApplication::setOverrideCursor(Qt::WaitCursor); out << toPlainText();//以純文本方式寫入,核心函數 QApplication::restoreOverrideCursor(); //返回之前,也將該文件的標題,路徑名等設置好。 SetCurrentFile(file_name); return true; } void MyMdi::contextMenuEvent(QContextMenuEvent *event) { QMenu *menu = new QMenu; //QKeySequence類是專門封裝快捷鍵的,這里使用的是默認的快捷鍵操作,其快捷鍵位"&"號后面那個字母 QAction *undo = menu->addAction(tr("撤銷(&U)"), this, SLOT(undo()), QKeySequence::Undo);//直接調用槽函數undo() undo->setEnabled(document()->isUndoAvailable());//因為該類是一個widget,所以可以直接使用document()函數 QAction *redo = menu->addAction(tr("恢復(&A)"), this, SLOT(redo()), QKeySequence::Redo); redo->setEnabled(document()->isRedoAvailable()); menu->addSeparator();//增加分隔符 QAction *cut = menu->addAction(tr("剪切(&T)"), this, SLOT(cut()), QKeySequence::Cut); cut->setEnabled(textCursor().hasSelection()); QAction *copy = menu->addAction(tr("復制(&C)"), this, SLOT(copy()), QKeySequence::Copy); copy->setEnabled(textCursor().hasSelection()); menu -> addAction(tr("粘貼&P"), this, SLOT(paste()), QKeySequence::Paste); QAction *clear = menu->addAction(tr("清空"), this, SLOT(clear())); clear->setEnabled(!document()->isEmpty());//文本內容非空時就可以清除 menu->addSeparator();//增加分隔符 QAction *select_all = menu->addAction(tr("全選"), this, SLOT(selectAll()), QKeySequence::SelectAll); select_all->setEnabled(!document()->isEmpty()); menu->exec(event->globalPos());//獲取鼠標位置,並顯示菜單 delete menu;//銷毀這個菜單 } //該函數是頂層窗口被關閉時發出的事件,是關閉窗口自帶的關閉符號X void MyMdi::closeEvent(QCloseEvent *event)//要記得加入 #include <QCloseEvent> { if(has_saved()) event->accept();//保存完畢后直接退出程序 else event->ignore(); }
mainwindow.h:
#ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> //#include "mymdi.h" #include <QAction> class MyMdi; class QMdiSubWindow;//加入一個類相當於加入一個頭文件? class QSignalMapper;//這是個跟信號發射相關的類 namespace Ui { class MainWindow; } class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = 0); ~MainWindow(); private slots: // void set_active_sub_window(QWidget *window); MyMdi *CreateMyMdi(); void set_active_sub_window(QWidget *window); void UpdateMenus(); void ShowTextRowCol(); void UpdateWindowMenu(); void closeEvent(QCloseEvent *event); void on_actionNew_triggered(); void on_actionOpen_triggered(); void on_actionExit_triggered(); void on_actionSave_triggered(); void on_actionSaveAs_triggered(); void on_actionCut_triggered(); void on_actionCopy_triggered(); void on_actionPaste_triggered(); void on_actionUndo_triggered(); void on_actionRedo_triggered(); void on_actionClose_triggered(); void on_actionCloseAll_triggered(); void on_actionTile_triggered(); void on_actionCascade_triggered(); void on_actionNext_triggered(); void on_actionPrevious_triggered(); void on_actionAbout_triggered(); void on_actionAboutQt_triggered(); private: Ui::MainWindow *ui; QAction *actionSeparator; QMdiSubWindow *FindMdiChild(const QString &file_name);//查找子窗口 MyMdi *GetActiveWindow(); QSignalMapper *window_mapper; void read_settings(); void write_settings(); void init_window(); }; #endif // MAINWINDOW_H
mainwindow.cpp:
#include "mainwindow.h" #include "ui_mainwindow.h" #include "mymdi.h" #include <QFileDialog> #include <QMdiSubWindow> #include <QDebug> #include <QSignalMapper> #include <QSettings> #include <QCloseEvent> #include <QLabel> #include <QMessageBox> #include <QMenu> MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); actionSeparator = new QAction(this); actionSeparator->setSeparator(true); UpdateMenus(); //有子窗口被激活,則更新菜單欄 connect(ui->mdiArea, SIGNAL(subWindowActivated(QMdiSubWindow*)), this, SLOT(UpdateMenus())); window_mapper = new QSignalMapper(this);//創建信號發生器 connect(window_mapper, SIGNAL(mapped(QWidget*)), this, SLOT(set_active_sub_window(QWidget*)));//通過信號發生器設置活動窗口 UpdateWindowMenu();//更新窗口子菜單 connect(ui->menuW, SIGNAL(aboutToShow()), this, SLOT(UpdateWindowMenu()));//當窗口子菜單將要出現時,就觸發更新窗口子菜單 read_settings();//因為在退出窗口時,執行了write_settings()函數,即保存了退出窗口時的窗口位置,尺寸等信息。因此下次打開該程序時,其位置尺寸 //等信息會保留 init_window();//初始化窗口 } MainWindow::~MainWindow() { delete ui; } void MainWindow::on_actionNew_triggered() { // MyMdi *new_mdi = new MyMdi(); // ui->mdiArea->addSubWindow(new_mdi); /*為什么不能使用上面的方法呢?因為上面的方法沒有涉及到文檔內容改變時,比如選中了文字,有過撤銷操作等。 即使我們又UpdateMenus()函數,但是關聯它的connect函數的信號為當有新的活動窗口出現時,所以一旦新 的活動窗口出現后,后面該文檔內容的改變就不會觸發菜單欄和工具欄對應action的變化了。 */ MyMdi *new_mdi = CreateMyMdi(); new_mdi->NewFile();//新建文件 new_mdi->show(); } void MainWindow::on_actionOpen_triggered() { QString file_name = QFileDialog::getOpenFileName(this);//手動選擇需要打開的文件,其實返回的file_name是包含路徑名的文件名 if(!file_name.isEmpty()) { QMdiSubWindow *existing_window = FindMdiChild(file_name); if(existing_window) //如果該文件對應窗口已經打開 { set_active_sub_window(existing_window);//設置該窗口為活動窗口,雖然set_active_sub_window是該類的成員函數,但是不能使用 //ui->來調用,冒失ui->調用的都是跟界面相關自動生成的一些量 return ; } MyMdi *open_window = CreateMyMdi();//否則新建子窗口,且加入到多文檔容器中 if(open_window->LoadFile(file_name)) { ui->statusBar->showMessage(tr("打開文件成功"), 2000);//狀態欄顯示打開文件成功,持續2秒 open_window->show(); } else { open_window->close();//打不開該文件時,則銷毀新建的窗口 } } } MyMdi* MainWindow::CreateMyMdi() { MyMdi *child = new MyMdi(); ui->mdiArea->addSubWindow(child); //根據是否可復制來設置剪切復制動作是否可用 connect(child, SIGNAL(copyAvailable(bool)), ui->actionCopy, SLOT(setEnabled(bool))); connect(child, SIGNAL(copyAvailable(bool)), ui->actionCut, SLOT(setEnabled(bool))); //根據文檔時否可用撤銷和恢復來設置相應的撤銷恢復動作是否可用 connect(child->document(), SIGNAL(undoAvailable(bool)), ui->actionUndo, SLOT(setEnabled(bool))); connect(child->document(), SIGNAL(redoAvailable(bool)), ui->actionRedo, SLOT(setEnabled(bool))); connect(child, SIGNAL(cursorPositionChanged()), this, SLOT(ShowTextRowCol())); return child; } QMdiSubWindow* MainWindow::FindMdiChild(const QString &file_name) { QString canonical_file_path = QFileInfo(file_name).canonicalFilePath();//解釋過后的絕對路徑 foreach(QMdiSubWindow *window, ui->mdiArea->subWindowList()) { MyMdi *my_mdi = qobject_cast<MyMdi *>(window->widget());//qobject_cast為進行強制類型轉換 if(my_mdi->CurrentFilePath() == canonical_file_path)//如果已經存在該窗口,則返回。比較的是絕對路徑名+文件名 return window; } return 0;//沒找到,則返回0 } void MainWindow::set_active_sub_window(QWidget *window) { if(!window) return; ui->mdiArea->setActiveSubWindow(qobject_cast<QMdiSubWindow*>(window));//將當前窗口設置為多文檔中的活動窗口 } MyMdi* MainWindow::GetActiveWindow() { // //獲得子窗口后還需要獲得其widget() // MyMdi *active_window = qobject_cast<MyMdi*>(ui->mdiArea->activeSubWindow()->widget()); // if(active_window) // return active_window; // else // return 0;//雖然返回類型是類的指針,但是這里也可以返回0,表示的是空指針。 /*上面的方法在后面會報內存錯誤*/ if(QMdiSubWindow *active_sub_window = ui->mdiArea->activeSubWindow()) return qobject_cast<MyMdi*>(active_sub_window->widget());//為什么還要調用widget()呢? else return 0; } void MainWindow::on_actionExit_triggered() { qApp->closeAllWindows();//qApp為全局指針,關閉所有窗口 } void MainWindow::on_actionSave_triggered() { if(GetActiveWindow() && GetActiveWindow()->Save()) ui->statusBar->showMessage(tr("保存文件成功"), 2000);//狀態欄顯示保存成功字樣2秒 } void MainWindow::on_actionSaveAs_triggered() { if(GetActiveWindow() && GetActiveWindow()->SaveAs()) ui->statusBar->showMessage(tr("保存文件成功"), 2000);//狀態欄顯示保存成功字樣2秒 } void MainWindow::on_actionCut_triggered() { if(GetActiveWindow()) GetActiveWindow()->cut();//直接調用QTextEdit這個類的cut()函數 } void MainWindow::on_actionCopy_triggered() { if(GetActiveWindow()) GetActiveWindow()->copy();//復制 } void MainWindow::on_actionPaste_triggered() { if(GetActiveWindow()) GetActiveWindow()->paste();//粘貼 } void MainWindow::on_actionUndo_triggered() { if(GetActiveWindow()) GetActiveWindow()->undo();//撤銷 } void MainWindow::on_actionRedo_triggered() { if(GetActiveWindow()) GetActiveWindow()->redo();//恢復 } void MainWindow::on_actionClose_triggered() { ui->mdiArea->closeActiveSubWindow();//關閉當前活動窗口 } void MainWindow::on_actionCloseAll_triggered() { ui->mdiArea->closeAllSubWindows();//關閉所有子窗口 } void MainWindow::on_actionTile_triggered() { ui->mdiArea->tileSubWindows();//平鋪窗口 } void MainWindow::on_actionCascade_triggered() { ui->mdiArea->cascadeSubWindows();//重疊窗口 } void MainWindow::on_actionNext_triggered() { ui->mdiArea->activateNextSubWindow();//下一個窗口 } void MainWindow::on_actionPrevious_triggered() { ui->mdiArea->activatePreviousSubWindow();//上一個窗口 } void MainWindow::on_actionAbout_triggered() { QMessageBox::about(this, tr("關於本軟件"), tr("參考www.yafeilinux.com網站做的一個實驗")); } void MainWindow::on_actionAboutQt_triggered() { qApp->aboutQt();//這里的qApp是QApplication對象的全局指針 } void MainWindow::UpdateMenus() { bool has_active_window; //如果有活動窗口,則為1,沒有則為0 if(GetActiveWindow()) has_active_window = true; else has_active_window = false; //設置間隔器是否顯示,貌似沒有效果? // actionSeparator->setVisible(has_active_window); //下面是根據是否存在活動窗口來設置各個動作是否可用 ui->actionSave->setEnabled(has_active_window); ui->actionSaveAs->setEnabled(has_active_window); ui->actionPaste->setEnabled(has_active_window); ui->actionClose->setEnabled(has_active_window); ui->actionCloseAll->setEnabled(has_active_window); ui->actionTile->setEnabled(has_active_window); ui->actionCascade->setEnabled(has_active_window); ui->actionNext->setEnabled(has_active_window); ui->actionPrevious->setEnabled(has_active_window); //只有當有活動窗口,且有文字被選中時,剪切和復制功能才可以使用 bool has_text_selection; // QTextEdit->textCursor().hasSelection()用來判斷是否有文本被選中 has_text_selection = (GetActiveWindow() && GetActiveWindow()->textCursor().hasSelection()); ui->actionCut->setEnabled(has_text_selection); ui->actionCopy->setEnabled(has_text_selection); //有活動窗口,且系統判斷可以執行撤銷操作時才顯示撤銷可用,判斷恢復操作可執行時恢復操作才可用 ui->actionUndo->setEnabled(GetActiveWindow() && GetActiveWindow()->document()->isUndoAvailable()); ui->actionRedo->setEnabled(GetActiveWindow() && GetActiveWindow()->document()->isRedoAvailable()); } //狀態欄上顯示光標的行號和列號 void MainWindow::ShowTextRowCol() { if(GetActiveWindow()) { ui->statusBar->showMessage(tr("%1行 %2列").arg(GetActiveWindow()->textCursor().blockNumber()+1). arg(GetActiveWindow()->textCursor().columnNumber()+1), 2000); } } void MainWindow::UpdateWindowMenu() { ui->menuW->clear();//清空所有菜單欄 /*重新加載已有的菜單*/ ui->menuW->addAction(ui->actionClose); ui->menuW->addAction(ui->actionCloseAll); ui->menuW->addSeparator(); ui->menuW->addAction(ui->actionTile); ui->menuW->addAction(ui->actionCascade); ui->menuW->addSeparator(); ui->menuW->addAction(ui->actionNext); ui->menuW->addAction(ui->actionPrevious); //加載間隔器 ui->menuW->addAction(actionSeparator); QList<QMdiSubWindow *> windows = ui->mdiArea->subWindowList(); actionSeparator->setVisible(!windows.isEmpty()); for(int i = 0; i < windows.size(); i++) { MyMdi *child = qobject_cast<MyMdi*>(windows.at(i)->widget()); QString text; if(i < 1)//這個時候變化數字就是其快捷鍵 text = tr("&% 1%2").arg(i+1).arg(child->get_current_file_name());//內容前面加了“&”表示可以使用快捷鍵,為第一個字母或數字 else text = tr("%1 %2").arg(i+1).arg(child->get_current_file_name()); QAction *action = ui->menuW->addAction(text);//添加新的菜單動作 action->setCheckable(true); action->setChecked(child == GetActiveWindow());//選中當前的活動窗口 connect(action, SIGNAL(triggered()), window_mapper, SLOT(map()));//選中action會觸發槽函數發送mapped()信號 //該函數的作用是設置一個映射,當在運行action的信號函數map()時,該函數會自動發送信號mapped(),並且會以mapped(windows.at(i))來發送 //此時會觸發在構造函數中設置的連接,其槽函數為設置活動窗口 window_mapper->setMapping(action, windows.at(i)); } } void MainWindow::init_window() { setWindowTitle(tr("簡易多文檔編輯器")); ui->mainToolBar->setWindowTitle(tr("工具欄"));//設置工具欄的標題名稱,右擊時才可以看到 //當需要的時候,設置水平垂直滾動條 ui->mdiArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); ui->mdiArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); ui->statusBar->showMessage(tr("歡迎使用多文檔編輯器")); QLabel *label = new QLabel(this); label->setFrameStyle(QFrame::Box | QFrame::Sunken);//設置label的形狀和陰影模式的,這里采用的box形狀和凹陷模式 label->setText(tr("<a href = \"www.cnblogs.com/tornadomeet\">www.cnblogs.com/tornadomeet</a>"));//設置文本內容 label->setTextFormat(Qt::RichText);//設置文本格式為富文本格式,又稱多文本格式,用於跨平台使用的 label->setOpenExternalLinks(true);//運行打開label上的鏈接 ui->statusBar->addPermanentWidget(label);//將label附加到狀態欄上,永久性的 ui->actionNew->setStatusTip(tr("創建一個文件")); ui->actionOpen->setStatusTip(tr("打開一個已經存在的文件")); ui->actionSave->setStatusTip(tr("保存文檔到硬盤")); ui->actionSaveAs->setStatusTip(tr("以新的名稱保存文檔")); ui->actionExit->setStatusTip(tr("退出應用程序")); ui->actionUndo->setStatusTip(tr("撤銷先前的操作")); ui->actionRedo->setStatusTip(tr("恢復先前的操作")); ui->actionCut->setStatusTip(tr("剪切選中的內容到剪貼板")); ui->actionCopy->setStatusTip(tr("復制選中的內容到剪貼板")); ui->actionPaste->setStatusTip(tr("粘貼剪貼板的內容到當前位置")); ui->actionClose->setStatusTip(tr("關閉活動窗口")); ui->actionCloseAll->setStatusTip(tr("關閉所有窗口")); ui->actionTile->setStatusTip(tr("平鋪所有窗口")); ui->actionCascade->setStatusTip(tr("層疊所有窗口")); ui->actionNext->setStatusTip(tr("將焦點移動到下一個窗口")); ui->actionPrevious->setStatusTip(tr("將焦點移動到前一個窗口")); ui->actionAbout->setStatusTip(tr("顯示本軟件的介紹")); ui->actionAboutQt->setStatusTip(tr("顯示Qt的介紹")); } void MainWindow::write_settings() { QSettings settings("Qt", "MyMdi");//第一個為公司的名字,第二個為軟件的名字 settings.setValue("pos", pos());//寫入該窗口相對於其父窗口的位置信息 settings.setValue("size", size());//寫入窗口大小信息 } void MainWindow::read_settings() { QSettings settings("Qt", "MyMdi"); //settings.value()第二個參數為默認值,即如果key:“pos”不存在,則返回默認值 QPoint pos = settings.value("pos", QPoint(200, 200)).toPoint(); QSize size = settings.value("size", QSize(400, 400)).toSize(); move(pos); //在構造函數中才調用read_settings()函數,因此這里重新移動窗口位置和設置窗口大小 resize(size); } void MainWindow::closeEvent(QCloseEvent *event) { ui->mdiArea->closeAllSubWindows(); if(ui->mdiArea->currentSubWindow())//如果還有窗口沒關閉,則忽略該事件。應該是上面的語句沒有全部關閉成功。 event->ignore(); else { write_settings();//關閉前寫入窗口設置 event->accept();//關閉 } }
main.cpp:
#include <QApplication> #include "mainwindow.h" #include <QTextCodec> int main(int argc, char *argv[]) { QApplication a(argc, argv); QTextCodec::setCodecForTr(QTextCodec::codecForLocale()); MainWindow w; w.show(); return a.exec(); }
總結:
通過本次實驗,對Qt中文件目錄,菜單工具欄等操作有了一定的了解。
參考資料:
附錄: