多文档编辑器--(3)菜单项的功能

在前面两节中,实现了界面的设计(菜单项)和子窗口类的实现,下一步就是实现菜单项的功能,就是把菜单项和子窗口类的成员函数关联起来。

1. 项目文件

多文档编辑器--(3)菜单项的功能_第1张图片

2. mainwindow.h 头文件

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include "mdichild.h"

#include 
#include 
#include 

class QSignalMapper;

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

private slots:
    MdiChild* createMdiChild();              		// 创建子窗口
    void setActiveSubWindow(QWidget* window);       // 设置活动子窗口
    void updateMenus();                     		// 更新菜单
    void updateWindowMenu();						// 更新“窗口”菜单
    
    void on_actionNew_triggered();					// New 槽函数
    void on_actionOpen_triggered();					// open 槽函数
    void on_actionSave_triggered();					// save 槽函数
    void on_actionUndo_triggered();					// undo 槽函数
    void on_actionClose_triggered();				// close 槽函数
    void on_actionAbout_triggered();				// about 槽函数
    void on_actionAboutQt_triggered();				// AboutQt 槽函数
    void on_actionExit_triggered();					// exit 槽函数
    void on_actionSaveAs_triggered();				// saveAs 槽函数

private:
    Ui::MainWindow *ui;

    QAction* actionSeparator;             			// 间隔器, 窗口菜单下的
    MdiChild* activeMdiChild();           			// 返回一个活动窗口的指针,没有窗口时,返回0
    QMdiSubWindow* findMdiChild(const QString& fileName);       // 查找子窗口

    QSignalMapper* windowMapper;

    void readSettings();                			// 读取窗口设置
    void writeSettings();               			// 写入窗口设置

protected:
    void closeEvent(QCloseEvent* event);                  // 关闭事件
};
#endif // MAINWINDOW_H

可以看出,mainwindow中成员函数大多都是槽函数,槽函数中除了菜单项的槽函数外,还有一些槽函数来响应子窗口的变化,例如,当主窗口中没子窗口时,save, saveAs,close等菜单项是不可用的。没选中一些文字时,cut copy 是不可用的。还有如下图所示,在窗口菜单中显示打开子窗口的文件列表。
多文档编辑器--(3)菜单项的功能_第2张图片

3.mainwindow.cpp 源文件

(1)构造函数
在构造函数中先把私有成员变量 actionSeparator 设置为间隔器,这个间隔器就是在“窗口”菜单中的
“Prev(V)”下面的间隔器。

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    
    actionSeparator = new QAction(this);                // 创建action
    actionSeparator->setSeparator(true);                // 把这一 action 设置为 分隔器
    
    updateMenus();                                      // 更新菜单,该关的菜单项给关闭了
    connect(ui->mdiArea, &QMdiArea::subWindowActivated,  this, &MainWindow::updateMenus);   // 子窗口被激活了,就更新菜单项

    windowMapper = new QSignalMapper(this);
    void (QSignalMapper::*func)(QWidget*) = &QSignalMapper::mapped;
    connect(windowMapper, func, this, &MainWindow::setActiveSubWindow);
    
    updateWindowMenu();                                 // 更新 窗口 菜单下的菜单项
    connect(ui->menuW, &QMenu::aboutToShow, this, &MainWindow::updateWindowMenu);// 显示 窗口 菜单之前就更新菜单项
    readSettings();
}

接下来更新菜单,就是根据子窗口的状态,来显示或者不显示一些菜单项。还把更新菜单设置为槽函数,当有子窗口被激活时,就更新菜单。

还要再更新窗口菜单。还把更新窗口菜单设置为槽函数,当点击窗口菜单时,就更新窗口菜单项。
这些函数将在下面讲解。

最后的readSettings 是设置主窗口的位置和大小的。

(2)更新菜单 updateMenus

根据 有无子窗口 和 子窗口中是否选择了文字,来关闭和开启菜单项。

void MainWindow::updateMenus()
{
    bool hasMdiChild = (activeMdiChild() != 0);     // 判断是否有子窗口,若有,hasMdiChild变量为 1, 若无,则为 0
    ui->actionSave->setEnabled(hasMdiChild);
    ui->actionSaveAs->setEnabled(hasMdiChild);
    ui->actionPaste->setEnabled(hasMdiChild);
    ui->actionClose->setEnabled(hasMdiChild);
    ui->actionCloseAll->setEnabled(hasMdiChild);
    ui->actionTile->setEnabled(hasMdiChild);
    ui->actionCascade->setEnabled(hasMdiChild);
    ui->actionNext->setEnabled(hasMdiChild);
    ui->actionPrevious->setEnabled(hasMdiChild);

    actionSeparator->setVisible(hasMdiChild);

    bool hasSelection = (activeMdiChild() && activeMdiChild()->textCursor().hasSelection());
    ui->actionCut->setEnabled(hasSelection);
    ui->actionCopy->setEnabled(hasSelection);

    ui->actionUndo->setEnabled(activeMdiChild() && activeMdiChild()->document()->isUndoAvailable());
    ui->actionRedo->setEnabled(activeMdiChild() && activeMdiChild()->document()->isRedoAvailable());
}

**(3)新建操作 **
新建文件包含两个动作,一个是新建子窗口,另一个是子窗口的标题设置和显示子窗口。

问:为什么不把这两个动作放在一个函数中完成?
答:因为在open 的槽函数中也新建子窗口。

// New 槽函数
void MainWindow::on_actionNew_triggered()
{
    MdiChild* child = createMdiChild();

    child->newFile();
    child->show();
}

// 创建子窗口,并且把子窗口的动作和菜单项的显示相关联
MdiChild *MainWindow::createMdiChild()
{
    MdiChild* child = new MdiChild;
    ui->mdiArea->addSubWindow(child);

    connect(child, &MdiChild::copyAvailable, ui->actionCut, &QAction::setEnabled);
    connect(child, &MdiChild::copyAvailable, ui->actionCopy, &QAction::setEnabled);
    connect(child->document(),&QTextDocument::undoAvailable, ui->actionUndo, &QAction::setEnabled);
    connect(child->document(), &QTextDocument::redoAvailable, ui->actionRedo, &QAction::setEnabled);

    return child;
}

(4)打开操作
打开文件操作也分两步,
一是查看欲打开的文件是不是在子窗口中,若在则不用新建窗口,
若不在则执行第二步:新建子窗口,再把文件载入子窗口。

void MainWindow::on_actionOpen_triggered()
{
    QString fileName = QFileDialog::getOpenFileName(this);
    if (!fileName.isEmpty()){
        QMdiSubWindow* existing = findMdiChild(fileName);
        if (existing){
            ui->mdiArea->setActiveSubWindow(existing);
            return;
        }
        MdiChild* child = createMdiChild();
        if (child->loadFile(fileName)){
            ui->statusBar->showMessage(tr("打开文件成功"), 2000);
            child->show();
        }
        else{
            child->close();
        }
    }
}

// 在子窗口列表中找某一个窗口(根据pathname来判断)
QMdiSubWindow *MainWindow::findMdiChild(const QString &fileName)
{
    QString canonicalFilepath = QFileInfo(fileName).canonicalFilePath();

    foreach (QMdiSubWindow* window, ui->mdiArea->subWindowList()) {
        MdiChild* mdiChild = qobject_cast<MdiChild*>(window->widget());
        if (mdiChild->currentFile() == canonicalFilepath)
            return window;
    }
    return 0;
}

(5)更新窗口菜单

// 更新 窗口 菜单的 菜单项
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){
        MdiChild* child = qobject_cast<MdiChild*>(windows.at(i)->widget());
        QString text;
        if (i < 9){
            text = tr("&%1 %2").arg(i + 1).arg(child->userFriendlyCurrentFile());
        }
        else{
            text = tr("%1 %2").arg(i + 1).arg(child->userFriendlyCurrentFile());
        }
        QAction* action = ui->menuW->addAction(text);
        action->setCheckable(true);
        action->setChecked(child == activeMdiChild());
        void (QSignalMapper::*func)() = &QSignalMapper::map;
        connect(action, &QAction::triggered, windowMapper, func);
        windowMapper->setMapping(action, windows.at(i));
    }
}

(6)设置活动窗口和查看活动窗口
调用了Qt自带的函数,来设置和查看MDI Area中的活动窗口。

// 设置某一个子窗口为活动窗口
void MainWindow::setActiveSubWindow(QWidget *window)
{
    if (!window){
        return;
    }
    ui->mdiArea->setActiveSubWindow(qobject_cast<QMdiSubWindow*>(window));
}

// 返回一个活动窗口的指针,没有窗口时,返回 0
MdiChild *MainWindow::activeMdiChild()
{
    if(QMdiSubWindow* activeSubWindow = ui->mdiArea->activeSubWindow())
        return qobject_cast<MdiChild*>(activeSubWindow->widget());
    return 0;
}

(7)其它一些简单 槽函数
这些槽函数就直接关联mdichild 中的成员函数,比较简单。

void MainWindow::on_actionSave_triggered()
{
    if (activeMdiChild() && activeMdiChild()->save())
        ui->statusBar->showMessage(tr("文件保存成功"), 2000);
}




void MainWindow::on_actionUndo_triggered()
{
    if (activeMdiChild())
        activeMdiChild()->undo();
}

// close 槽函数,关闭活动窗口
void MainWindow::on_actionClose_triggered()
{
    ui->mdiArea->closeActiveSubWindow();
}

void MainWindow::on_actionAbout_triggered()
{
    QMessageBox::about(this, tr("关于"), tr("这是Mr.Jia的编辑器!"));
}

void MainWindow::on_actionAboutQt_triggered()
{
    QMessageBox::aboutQt(this, "关于Qt");
}

void MainWindow::on_actionExit_triggered()
{
    qApp->closeAllWindows();
}

void MainWindow::on_actionSaveAs_triggered()
{
    if (activeMdiChild() && activeMdiChild()->saveAs())
        ui->statusBar->showMessage(tr("文件保存成功"), 2000);
}

4. 最后

作为Qt 的第一个项目——简单的多文档编辑器 算是完成了。

知识点包括:

  • Qt Creator的使用
  • 项目的建立与发布
  • 基础的窗口部件的使用(QMainWindow、QMessageBox等)
  • ui 界面文件的设计(节省了不少时间)
  • 信号与槽函数

再接再厉吧!

你可能感兴趣的:(Qt学习)