Qt项目实战之实现多文本编辑器
作者:音视频开发老舅
这篇文章主要为大家详细介绍了如何利用Qt实现简易的多文本编辑器,文中的示例代码讲解详细,具有一定的参考价值,感兴趣的小伙伴可以了解一下
首先先来看实验成果图,大概就是这么个多文档编辑器。
首先需要在设计模式里进行设计器的设置:
然后就是新建类MdiChild的声明和实现
mdichild.h
#ifndef MDICHILD_H #define MDICHILD_H #include<QTextEdit> class MdiChild:public QTextEdit { Q_OBJECT 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;} //返回当前文件路径 protected: void closeEvent(QCloseEvent *event); //关闭事件 void contextMenuEvent(QContextMenuEvent *e); //右键菜单事件 private slots: void documentWasModified(); //文档被更改时,窗口显示更改状态标志 private: bool maybeSave(); //是否需要保存 void setCurrentFile(const QString &fileName); //设置当前文件 QString curFile; //保存当前文件路径 bool isUntitled; //作为当前文件是否被保存到硬盘上的标志 }; #endif // MDICHILD_H
mdichild.cpp
#include "mdichild.h" #include<QFile> #include<QMessageBox> #include<QTextStream> #include<QApplication> #include<QFileInfo> #include<QFileDialog> #include<QCloseEvent> #include<QPushButton> #include<QMenu> MdiChild::MdiChild(QWidget *parent):QTextEdit (parent) { //设置在子窗口关闭时销毁这个类的对象 setAttribute(Qt::WA_DeleteOnClose); //初始isUntitled为true isUntitled = true; } /* * 设置窗口编号 * 保存文件未被保存过“isUntitled = true” * 保存文件路径,给curFile赋初值 * 设置子窗口标题 * 关联文档内容改变信号到显示文档更改状态 */ void MdiChild::newFile() { //设置窗口编号,因为编号一致被保存,所以需要使用静态变量 static int sequenceNumber =1; //新建的文档没有被保存过 isUntitled =true; //将当前文件命名为未命名文档加编号,编号先使用再加1 curFile = tr("未命名文档%1.txt").arg(sequenceNumber++); //设置窗口标题,使用[*]可以再文档被更改后再文件名称后显示“*”号 setWindowTitle(curFile +"[*]"+tr(" - 多文档编辑器")); //文档更改时发射contentsChanged()信号,执行documentWasModified()槽 connect(document(),SIGNAL(contentsChanged()),this,SLOT(documentWasModified())); } /* * 打开指定的文件,并读取文件内容到编辑器 * 设置当前文件setCurrentFile(),该函数可以获取文件路径,完成文件和窗口状态的设置 * 关联文档内容改变信号到显示文档更改状态槽documentWasModified() */ bool MdiChild::loadFile(const QString &fileName) { //新建QFile对象 QFile file(fileName); //只读方式打开文件,出错则提示,并返回false if(!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(documentWasChanged())); return true; } bool MdiChild::save() { //如果文件未被保存过,则执行另存为操作,否则直接保存文件 if(isUntitled) { return saveAs(); } else { return saveFile(curFile); } } bool MdiChild::saveAs() { QString fileName = QFileDialog::getSaveFileName(this,tr("另存为"),curFile); //获取文件路径,如果为空,则返回false,否则保存文件 if(fileName.isEmpty()) return 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); } QString MdiChild::userFriendlyCurrentFile() { //从文件路径中提取文件名 return QFileInfo(curFile).fileName(); } void MdiChild::closeEvent(QCloseEvent *event) { //如果maybeSave()函数返回true,则关闭窗口,否则忽略该事件 if(maybeSave()) { event->accept(); } else { event->ignore(); } } void MdiChild::contextMenuEvent(QContextMenuEvent *e) { //创建菜单,并向其中添加动作 QMenu *menu = new QMenu; QAction *undo =menu->addAction(tr("撤销(&U)"),this,SLOT(undo()),QKeySequence::Undo); undo->setEnabled(document()->isUndoAvailable()); QAction *redo =menu->addAction(tr("恢复(&R)"),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()); QAction *clear = menu->addAction(tr("清空"),this,SLOT(clear())); clear->setEnabled(!document()->isEmpty()); menu->addSeparator(); QAction *select = menu->addAction(tr("全选"),this,SLOT(selectAll()),QKeySequence::SelectAll); select->setEnabled(!document()->isEmpty()); //获取鼠标的位置,然后在这个位置显示菜单 menu->exec(e->globalPos()); //最后销毁这个菜单 delete menu; } void MdiChild::documentWasModified() { //根据文档的isModified()函数的返回值,判断编辑器内容是否被更改了 //如果被更改了,就要在设置[*]号的地方显示“*”号,这里会在窗口标题中显示 setWindowModified(document()->isModified()); } 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(); //如果用户选择是,则返回保存操作的结果;如果选择取消,则返回false if(box.clickedButton() ==yesBtn) return save(); else if(box.clickedButton() ==cancelBtn){ return false; } } //如果文档没有更改过,则直接返回true; return true; } void MdiChild::setCurrentFile(const QString &fileName) { //canonicalFilePath()可以除去路径中的符号链接,"."和".."等符号 curFile = QFileInfo(fileName).canonicalFilePath(); //文件已经被保存过了 isUntitled = false; //文档没有被更改过 document()->setModified(false); //窗口不显示被更改标志 setWindowModified(false); //设置窗口标题,userFriendlyCurrentFile()返回文件名 setWindowTitle(userFriendlyCurrentFile()+"[*]"); }
然后是mainwindow类的声明与实现
mainwindow.h
#ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> class QMdiSubWindow; class MdiChild; class QSignalMapper; namespace Ui { class MainWindow; } class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = nullptr); ~MainWindow(); private slots: void updateMenus(); //更新菜单 MdiChild* createMdiChild(); //创建子窗口 void setActiveSubWindow(QWidget *window); //设置活动子窗口 void updateWindowMenu(); //更新窗口菜单 void showTextRowAndCol(); //显示文本的行号和列号 void on_actionNew_triggered(); void on_actionOpen_triggered(); void on_actionSave_triggered(); void on_actionSaveAs_triggered(); void on_actionUndo_triggered(); void on_actionRedo_triggered(); void on_actionCut_triggered(); void on_actionCopy_triggered(); void on_actionPaste_triggered(); void on_actionClose_triggered(); void on_actionCloseAll_triggered(); void on_actionAbout_triggered(); void on_actionAboutQt_triggered(); void on_actionExit_triggered(); private: QMdiSubWindow *findMdiChild(const QString &fileName);//查找子窗口 void readSettings(); //读取窗口设置 void writeSettings(); //写入窗口设置 void initWindow(); //初始化窗口 protected: void closeEvent(QCloseEvent* event); //关闭事件 private: Ui::MainWindow *ui; QAction *actionSeparator; //间隔器 MdiChild *activeMdiChild(); //活动窗口 QSignalMapper *windowMapper; //信号映射器 }; #endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h" #include "ui_mainwindow.h" #include"mdichild.h" #include<QFileDialog> #include<QMdiSubWindow> #include<QSignalMapper> #include<QMessageBox> #include<QSettings> #include<QCloseEvent> #include<QLabel> MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); /* * 初始化actionSeparator动作,然后执行更新菜单函数,并关联多文档区域的活动子窗口信号到更新菜单槽上 * 每当更换子窗口时,都会更新菜单状态 */ //创建间隔期动作并在其中设置间隔期 actionSeparator = new QAction(this); actionSeparator->setSeparator(true); //更新菜单 updateMenus(); //当有活动窗口时更新菜单 connect(ui->mdiArea,SIGNAL(subWindowActivated(QMdiSubWindow*)),this,SLOT(updateMenus())); //创建信号映射器 windowMapper = new QSignalMapper(this); //映射器重新发射信号,更具信号设置活动窗口 connect(windowMapper,SIGNAL(mapped(QWidget *)),this,SLOT(setActiveSubWindow(QWidget *))); //更新窗口菜单,并且设置当窗口菜单将要显示的时候更新窗口菜单 updateWindowMenu(); connect(ui->menu_W,SIGNAL(aboutToShow()),this,SLOT(updateWindowMenu())); readSettings(); //初始窗口时读取窗口设置信息 //初始化窗口 initWindow(); } MainWindow::~MainWindow() { delete ui; } void MainWindow::on_actionNew_triggered() { //创建MdiChild MdiChild *child = createMdiChild(); //新建文件 child->newFile(); //显示子窗口 child->show(); } /* * 更新菜单函数:根据是否有活动子窗口设置各个菜单动作是否可用 * 这里剪切复制操作和撤销恢复操作的设置还要进行特殊情况的判断 */ void MainWindow::updateMenus() { //根据是否有活动窗口来设置各个动作是否可用 bool hasMdiChild = (activeMdiChild()!= 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); //有活动窗口且有被选择的文本,剪切复制才可用 bool hasSelection =(activeMdiChild() &&activeMdiChild()->textCursor().hasSelection()); ui->actionCut->setEnabled(hasSelection); ui->actionPaste->setEnabled(hasSelection); //有活动窗口且有撤销操作时撤销动作可用 ui->actionUndo->setEnabled(activeMdiChild()&&activeMdiChild()->document()->isUndoAvailable()); //有活动窗口且文档有恢复操作时恢复动作可用 ui->actionRedo->setEnabled(activeMdiChild()&&activeMdiChild()->document()->isRedoAvailable()); } /* * 这个函数中创建了MdiChild部件,并将它作为子窗口的中心部件,然后添加到多文档区域。 * 下面关联了编辑器的信号和我们的菜单动作,让它们可以随着文档的改变而改变状态 * 最后返回了MdiChild对象指针。 * 之所以要添加这样一个函数,是因为在下面的打开操作中还要使用到这个函数中的功能,所以将它们从新建文件菜单的触发信号槽中提取出来,另写了这样一个函数 */ MdiChild *MainWindow::createMdiChild() { //创建MdiChild部件 MdiChild *child = new MdiChild; //向多文档区域添加子窗口,child为中心部件 ui->mdiArea->addSubWindow(child); //根据QTextEdit类的是否可以复制信号设置剪切复制动作是否可用 connect(child,SIGNAL(copyAvailable(bool)),ui->actionCut,SLOT(setEnabled(bool))); connect(child,SIGNAL(copyAvailable(bool)),ui->actionCopy,SLOT(setEnabled(bool))); //根据QTextDocument类的是否可以撤销恢复信号设置撤销恢复动作是否可用 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(showTextRowAndCol())); return child; } /* * 函数功能:将传递过来的窗口部件设置为活动窗口 */ void MainWindow::setActiveSubWindow(QWidget *window) { //如果传递了窗口部件,则将其设置为活动窗口 if(!window) return; ui->mdiArea->setActiveSubWindow(qobject_cast<QMdiSubWindow*>(window)); } 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(); } } } void MainWindow::updateWindowMenu() { //先清空菜单,然后在添加各个菜单动作 ui->menu_W->clear(); ui->menu_W->addAction(ui->actionClose); ui->menu_W->addAction(ui->actionCloseAll); ui->menu_W->addSeparator(); ui->menu_W->addAction(ui->actionTile); ui->menu_W->addAction(ui->actionCascade); ui->menu_W->addSeparator(); ui->menu_W->addAction(ui->actionNext); ui->menu_W->addAction(ui->actionPrevious); ui->menu_W->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; //如果窗口数小于9,则设置编号为快捷键 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->menu_W->addAction(text); action->setCheckable(true); //设置当前活动窗口动作为选中状态 action->setChecked(child ==activeMdiChild()); //关联动作的触发信号到信号映射器的map()槽,这个槽会发射mapped()信号 connect(action,SIGNAL(triggered()),windowMapper,SLOT(map())); //将动作与相应的窗口部件进行映射 //在发射mappedd()信号时就会以这个窗口部件为参数 windowMapper->setMapping(action,windows.at(i)); } } void MainWindow::showTextRowAndCol() { //如果有活动窗口,则显示其中光标所在的位置 if(activeMdiChild()) { //因为获取的行号和列号都是从0开始的,所以我们这里进行了加1 int rowNum = activeMdiChild()->textCursor().blockNumber()+1; int colNum = activeMdiChild()->textCursor().blockNumber()+1; ui->statusBar->showMessage(tr("%1行 %2列").arg(rowNum).arg(colNum),2000); } } QMdiSubWindow *MainWindow::findMdiChild(const QString &fileName) { QString canonicalFilePath = QFileInfo(fileName).canonicalFilePath(); //利用foreach语句遍历子窗口列表,如果其文件路径和要查找的路径相同,则返回该窗口 foreach(QMdiSubWindow* window,ui->mdiArea->subWindowList()) { MdiChild* mdiChild = qobject_cast<MdiChild*>(window->widget()); if(mdiChild->currentFile() ==canonicalFilePath) return window; } return 0; } void MainWindow::readSettings() { QSettings settings("yafeilinux","myMdi"); QPoint pos =settings.value("pos",QPoint(200,200)).toPoint(); QSize size =settings.value("size",QSize(400,200)).toSize(); move(pos); resize(size); } void MainWindow::writeSettings() { QSettings settings("yafeilinux","myMdi"); //写入位置信息和大小 settings.setValue("pos",pos()); settings.setValue("size",size()); } void MainWindow::initWindow() { 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->setText(tr("<a href=\"http://www.yafeilinux.com\">yafeilinux.com</a>")); //标签文字为富文本 label->setTextFormat(Qt::RichText); //可以打开外部链接 label->setOpenExternalLinks(true); ui->statusBar->addPermanentWidget(label); ui->actionNew->setStatusTip(tr("创建一个文件")); ui->actionOpen->setStatusTip(tr("打开一个文件")); } void MainWindow::closeEvent(QCloseEvent *event) { //先执行多文档区域的关闭操作 ui->mdiArea->closeAllSubWindows(); //如果还有窗口没有关闭,则忽略该事件 if(ui->mdiArea->currentSubWindow()) event->ignore(); else { //在关闭前写入窗口设置 writeSettings(); event->accept(); } } /* * 这个函数中使用了QMdiArea类的activeSubWindow()函数来获得多文档区域的活动子窗口, * 然后使用了T qobject_cast()函数来进行类型转换。这个函数时QObject类的函数,它将object对象指针转换为T类型的对象指针 * 这里将活动窗口的中心部件QWidget类型指针转换为MdiChild类型的指针 * 这里的T类型必须是直接或者间接继承自QObject类,并且在其定义中要有Q_OBJECT宏变量 */ MdiChild *MainWindow::activeMdiChild() { //如果有活动窗口,则将其内的中心部件转换为MdiChild类型,没有则直接返回0 if(QMdiSubWindow *activeSubWindow = ui->mdiArea->activeSubWindow()) return qobject_cast<MdiChild*>(activeSubWindow->widget()); return 0; } void MainWindow::on_actionSave_triggered() { if(activeMdiChild()&&activeMdiChild()->save()) ui->statusBar->showMessage(tr("文件保存成功"),2000); } void MainWindow::on_actionSaveAs_triggered() { if(activeMdiChild()&&activeMdiChild()->saveAs()) ui->statusBar->showMessage(tr("文件保存成功"),2000); } void MainWindow::on_actionUndo_triggered() { if(activeMdiChild()) activeMdiChild()->undo(); } void MainWindow::on_actionRedo_triggered() { if(activeMdiChild()) activeMdiChild()->redo(); } void MainWindow::on_actionCut_triggered() { if(activeMdiChild()) activeMdiChild()->cut(); } void MainWindow::on_actionCopy_triggered() { if(activeMdiChild()) activeMdiChild()->copy(); } void MainWindow::on_actionPaste_triggered() { if(activeMdiChild()) activeMdiChild()->paste(); } void MainWindow::on_actionClose_triggered() { ui->mdiArea->closeActiveSubWindow(); } void MainWindow::on_actionCloseAll_triggered() { ui->mdiArea->closeAllSubWindows(); } void MainWindow::on_actionAbout_triggered() { QMessageBox::about(this,"关于",tr("致力于多文档编辑器普及工作")); } void MainWindow::on_actionAboutQt_triggered() { QMessageBox::about(this,"关于Qt",tr("Qt是一个1991年由Qt Company开发的跨平台C++图形用户界面应用程序开发框架。它既可以开发GUI程序,也可用于开发非GUI程序,比如控制台工具和服务器。Qt是面向对象的框架,使用特殊的代码生成扩展(称为元对象编译器(Meta Object Compiler, moc))以及一些宏,Qt很容易扩展,并且允许真正地组件编程。")); } void MainWindow::on_actionExit_triggered() { //等价于QApplication::closeAllWindows(); qApp->closeAllWindows(); }
最后是main函数
#include "mainwindow.h" #include <QApplication> int main(int argc, char *argv[]) { QApplication a(argc, argv); MainWindow w; w.show(); return a.exec(); }
以上就是Qt项目实战之实现多文本编辑器的详细内容,更多关于Qt多文本编辑器的资料请关注脚本之家其它相关文章!