在前面两节中,实现了界面的设计(菜单项)和子窗口类的实现,下一步就是实现菜单项的功能,就是把菜单项和子窗口类的成员函数关联起来。
#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 是不可用的。还有如下图所示,在窗口菜单中显示打开子窗口的文件列表。
(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);
}
作为Qt 的第一个项目——简单的多文档编辑器 算是完成了。
知识点包括:
再接再厉吧!