Qt學習之路_11(簡易多文檔編輯器)


  

  前言:

  本文將介紹怎樣用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中文件目錄,菜單工具欄等操作有了一定的了解。

 

 

  參考資料:

     http://www.yafeilinux.com/

 

 

  附錄:

  實驗工程code下載

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM