Qt 项目实战 | 多界面编辑器
- Qt 项目实战 | 多界面编辑器
- 界面设计
- 创建子窗口类
官方博客:https://www.yafeilinux.com/
Qt开源社区:https://www.qter.org/
参考书:《Qt 及 Qt Quick 开发实战精解》
Qt 项目实战 | 多界面编辑器
开发环境:Qt Creator 4.6.2 Based on Qt 5.9.6
界面设计
这里主要是对主窗口和工具栏的设计。
新建 Qt Gui 应用,项目名称 myMdi,类名默认 MainWindow,基类默认为 QMainWindow都不做改动。
添加资源文件 myImage.qrc:
双击 mainwindow.ui 进入设计模式,添加菜单:
菜单栏和工具栏:
文件子菜单,注意有2个分隔符:
编辑子菜单,注意有1个分隔符:
窗口子菜单,注意有2个分隔符:
帮助子菜单:
设计完菜单栏和工具栏后,向主窗口中心区域拖入一个 MDI Area 部件,并单击主窗口界面,按下 Ctrl + G,使其处于栅格布局。
确保 MDI Area 部件的 objectName 是 mdiArea,而文件菜单、编辑菜单、窗口菜单、帮助菜单的 objectName 分别是 menuF、menuE、menuW、menuH。
创建子窗口类
为了实现多文档操作,需要向 QMdiArea 中添加子窗口,我们需要子类化子窗口的中心部件。
新建C++类文件,类名为 MdiChild,基类为 QTextEdit,类型信息选择“继承自 QWidget”:
在 mdichild.h 添加代码:
#ifndef MDICHILD_H
#define MDICHILD_H#include <QTextEdit>
#include <QWidget>class MdiChild : public QTextEdit
{Q_OBJECT
private:QString curFile; //当前文件路径bool isUntitled; //作为当前文件是否被保存到硬盘的标志bool maybeSave(); //是否需要保存void setCurrentFile(const Qstring& fileName); //设置当前文件
protected:void closeEvent(QCloseEvent* event); //关闭事件
public:explicit MdiChild(QWidget* parent = 0);void newFile(); //新建文件bool loadFile(const Qstring& fileName); //加载文件bool save(); //保存操作bool saveAs(); //另存为操作bool saveFile(const QString& fileName); //保存文件QString userFriendlyCurrentFile(); //提取文件名QString currentFile() { return curFile; } //返回当前文件路径
private slots:void documentWasModified(); //文档被更改时,窗口显示更改状态标志
};#endif // MDICHILD_H
在 mdichild.cpp 添加代码:
#include "mdichild.h"#include <QApplication>
#include <QCloseEvent>
#include <QFile>
#include <QFileDialog>
#include <QFileInfo>
#include <QMessageBox>
#include <QPushButton>
#include <QTextStream>// 是否需要保存
bool MdiChild::maybeSave()
{// 如果文档被更改过if (document()->isModified()){QMessageBox box;box.setWindowTitle(tr("多文档编辑器"));box.setText(tr("是否保存对“%1”的更改?").arg(userFriendlyCurrentFile()));box.setIcon(QMessageBox::Warning);// 添加按钮,QMessageBox::YesRole可以表明这个按钮的行为QPushButton* yesBtn = box.addButton(tr("是(&Y)"), QMessageBox::YesRole);box.addButton(tr("否(&N)"), QMessageBox::NoRole);QPushButton* cancelBtn = box.addButton(tr("取消"), QMessageBox::RejectRole);// 弹出对话框,让用户选择是否保存修改,或者取消关闭操作box.exec();if (box.clickedButton() == yesBtn){// 如果用户选择是,则返回保存操作的结果return save();}else if (box.clickedButton() == cancelBtn){// 如果选择取消,则返回falsereturn false;}}return true; // 如果文档没有更改过,则直接返回true
}// 设置当前文件
void MdiChild::setCurrentFile(const QString& fileName)
{// canonicalFilePath()可以除去路径中的符号链接,“.”和“..”等符号curFile = QFileInfo(fileName).canonicalFilePath();// 文件已经被保存过了isUntitled = false;// 文档没有被更改过document()->setModified(false);// 窗口不显示被更改标志setWindowModified(false);// 设置窗口标题,userFriendlyCurrentFile() 函数返回文件名setWindowTitle(userFriendlyCurrentFile() + "[*]");
}// 关闭操作,在关闭事件中执行
void MdiChild::closeEvent(QCloseEvent* event)
{if (maybeSave()){// 如果 maybeSave() 函数返回 true,则关闭窗口event->accept();}else{// 否则忽略该事件event->ignore();}
}MdiChild::MdiChild(QWidget* parent) : QTextEdit(parent)
{// 设置在子窗口关闭时销毁这个类的对象setAttribute(Qt::WA_DeleteOnClose);// 初始 isUntitled 为 trueisUntitled = true;
}// 新建文件操作
void MdiChild::newFile()
{// 设置窗口编号,因为编号一直被保存,所以需要使用静态变量static int sequenceNumber = 1;// 新建的文档没有被保存过isUntitled = true;// 将当前文件命名为未命名文档加编号,编号先使用再加 1curFile = tr("未命名文档%1.txt").arg(sequenceNumber++);// 设置窗口标题,使用[*]可以在文档被更改后在文件名称后显示“*”号setWindowTitle(curFile + "[*]" + tr(" - 多文档编辑器"));// 当文档被更改时发射 contentsChanged() 信号,执行 documentWasModified() 槽函数connect(document(), SIGNAL(contentsChanged()), this, SLOT(documentWasModified()));
}// 加载文件
bool MdiChild::loadFile(const QString& fileName)
{// 新建 QFile 对象QFile file(fileName);// 只读方式打开文件,出错则提示,并返回 falseif (!file.open(QFile::ReadOnly | QFile::Text)){QMessageBox::warning(this, tr("多文档编辑器"),tr("无法读取文件 %1:\n%2.").arg(fileName).arg(file.errorString()));return false;}// 新建文本流对象QTextStream in(&file);// 设置鼠标状态为等待状态QApplication::setOverrideCursor(Qt::WaitCursor);// 读取文件的全部文本内容,并添加到编辑器中setPlainText(in.readAll());// 恢复鼠标状态QApplication::restoreOverrideCursor();// 设置当前文件setCurrentFile(fileName);connect(document(), SIGNAL(contentsChanged()), this, SLOT(documentWasModified()));return true;
}// 保存操作
bool MdiChild::save()
{if (isUntitled){// 如果文件未被保存过,则执行另存为操作return saveAs();}else{// 否则直接保存文件return saveFile(curFile);}
}// 另存为操作
bool MdiChild::saveAs()
{// 使用文件对话框获取文件路径QString fileName = QFileDialog::getSaveFileName(this, tr("另存为"), curFile);if (fileName.isEmpty()){// 如果文件路径为空,则返回 falsereturn false;}// 否则保存文件return saveFile(fileName);
}// 保存文件
bool MdiChild::saveFile(const QString& fileName)
{QFile file(fileName);if (!file.open(QFile::WriteOnly | QFile::Text)){QMessageBox::warning(this, tr("多文档编辑器"),tr("无法写入文件 %1:\n%2.").arg(fileName).arg(file.errorString()));return false;}QTextStream out(&file);// 设置应用程序强制光标为等待旋转光标(设置鼠标状态为等待状态)QApplication::setOverrideCursor(Qt::WaitCursor);// 以纯文本文件写入out << toPlainText();// 恢复光标(恢复鼠标状态)QApplication::restoreOverrideCursor();setCurrentFile(fileName);return true;
}// 提取文件名
QString MdiChild::userFriendlyCurrentFile()
{// 从文件路径中提取文件名return QFileInfo(curFile).fileName();
}// 文档被更改时,窗口显示更改状态标志
void MdiChild::documentWasModified()
{// 根据文档的isModified()函数的返回值,判断编辑器内容是否被更改了// 如果被更改了,就要在设置了[*]号的地方显示“*”号,这里会在窗口标题中显示setWindowModified(document->isModified());
}
下面对这个类进行简单的测试。
在 mainwindow.cpp 中添加以下代码:
#include "mdichild.h"
因为程序中使用了中文,要在 main.cpp 文件中添加头文件和代码:
#include <QTextCodec>
// 解决 Qt 中文乱码问题
QTextCodec::setCodecForLocale(QTextCodec::codecForLocale());
// QTextCodec::setCodecForLocale(QTextCodec::codecForName("utf-8"));
转到设计模式,在 Action Editor 中“新建文件”动作上右击,转到它的触发信号 triggered() 的槽,并更改如下:
void MainWindow::on_actionNew_triggered()
{//创建 MdiChildMdiChild* child = new MdiChild;//多文档区域添加子窗口ui->mdiArea->addSubWindow(child);//新建文件child->newFile();//显示子窗口child->show();
}
测试结果:
打开多个界面也没有问题。