- 基于Qt实现一个文档编辑器
源代码(点击此处查看下载) - godouo.h
#ifndef MDICHILD_H
#define MDICHILD_H
#include <QTextEdit>
#include <QCloseEvent>
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); // 保存文件
void mergeFormatOnWordOrSelection(const QTextCharFormat &format);
protected:
void closeEvent(QCloseEvent *event); // 关闭事件
private slots:
void documentWasModified(); // 文档被更改时,显示更改状态标志
private:
void setCurrentFile(const QString &fileName); // 设置当前文件
QString curFile; // 保存当前文件路径
bool isUntitled; // 作为当前文件是否被保存到硬盘上的标志
};
#endif // MDICHILD_H
- godouo.cpp
#include "godouo.h"
#include <QFile>
#include <QTextStream>
#include <QMessageBox>
#include <QFileInfo>
#include <QApplication>
#include <QFileDialog>
#include <QCloseEvent>
#include <QPushButton>
#include <QMenu>
MdiChild::MdiChild(QWidget *parent) :
QTextEdit(parent)
{
setAttribute(Qt::WA_DeleteOnClose); // 这样可以在子窗口关闭时销毁这个类的对象
isUntitled = true; // 初始isUntitled为true
}
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()));
}
bool MdiChild::loadFile(const QString &fileName)
{
//打开文件
QFile file(fileName);
if(file.open(QFile::ReadOnly | QFile::Text))
{
QTextStream stream(&file);// 新建文本流对象
stream.setCodec("GBK");
QApplication::setOverrideCursor(Qt::WaitCursor); // 设置鼠标状态为等待状态
setPlainText(file.readAll()); // 读取文件的全部文本内容,并添加到编辑器中
QApplication::restoreOverrideCursor(); // 恢复鼠标状态
setCurrentFile(fileName); // 设置当前文件
connect(document(), SIGNAL(contentsChanged()),
this, SLOT(documentWasModified()));
file.close();
return true;
}
else
{
//打开失败
QMessageBox::critical(this,tr("error"),file.errorString());
return false;
}
}
bool MdiChild::save()
{
if (isUntitled) { // 如果文件未被保存过,则执行另存为操作
return saveAs();
} else {
return saveFile(curFile); // 否则直接保存文件
}
}
bool MdiChild::saveAs()
{
// 获取文件路径,如果为空,则返回false
QString fileName = QFileDialog::getSaveFileName(this, tr("另存为"),curFile);
if (fileName.isEmpty())
return false;
return saveFile(fileName); // 否则保存文件
}
bool MdiChild::saveFile(const QString &fileName)
{
if(fileName.isEmpty())
return false;
QFile file(fileName);
if(file.open(QFile::WriteOnly | QFile::Text)) //以写的方式打开文件 若文件有内容 则追加内容
{
QTextStream stream(&file);
stream.setCodec("GBK");
QApplication::setOverrideCursor(Qt::WaitCursor);
stream<<toPlainText(); //将text的内容转换为QString后,写入文件
QApplication::restoreOverrideCursor();
file.close();
return true;
}
QMessageBox::critical(this,tr("error"),file.errorString());
return false;
}
void MdiChild::closeEvent(QCloseEvent *event)
{
//查看qt助帮文档 QMessageBox
if(document()->isModified()) //textEdit内容已经被修改
{
QMessageBox msgBox;
msgBox.setWindowFlags(Qt::Widget | Qt::FramelessWindowHint | Qt::WindowSystemMenuHint | Qt::WindowStaysOnTopHint);
msgBox.setText(tr("是否保存更改?"));
msgBox.setInformativeText(tr("您是否要保存文件"));
msgBox.setStandardButtons(QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);
msgBox.setDefaultButton(QMessageBox::Save);
int ret = msgBox.exec(); //执行QMessageBox
switch (ret) {
case QMessageBox::Save:
// Save was clicked
this->save();
break;
case QMessageBox::Discard:
// Don't Save was clicked
event->accept();
break;
case QMessageBox::Cancel:
// Cancel was clicked
event->ignore();
break;
default:
// should never be reached
event->ignore();
break;
}
}
else
{
event->accept();
}
}
void MdiChild::documentWasModified()
{
// 根据文档的isModified()函数的返回值,判断我们编辑器内容是否被更改了
// 如果被更改了,就要在设置了[*]号的地方显示“*”号,这里我们会在窗口标题中显示
setWindowModified(document()->isModified());
}
void MdiChild::setCurrentFile(const QString &fileName)
{
// canonicalFilePath()可以除去路径中的符号链接,“.”和“..”等符号
curFile = QFileInfo(fileName).canonicalFilePath();
isUntitled = false; // 文件已经被保存过了
document()->setModified(false); // 文档没有被更改过
setWindowModified(false); // 窗口不显示被更改标志
// 设置窗口标题,QFileInfo()返回文件名
setWindowTitle(QFileInfo(curFile).fileName() + "[*]");
}
void MdiChild::mergeFormatOnWordOrSelection(const QTextCharFormat &format)
{
QTextCursor cursor = this->textCursor();
if (!cursor.hasSelection())
cursor.select(QTextCursor::WordUnderCursor);
cursor.mergeCharFormat(format);
this->mergeCurrentCharFormat(format);
}
- mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "godouo.h"
#include <QFileDialog>
#include <QSettings>
#include <QCloseEvent>
#include <QLabel>
#include <QMdiSubWindow>
#include <QMessageBox>
#include <QFontDialog>
#include <QColorDialog>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
readSettings(); // 初始窗口时读取窗口设置信息
initWindow(); // 初始化窗口
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::on_actionNew_triggered()
{
MdiChild *child = createMdiChild(); // 创建MdiChild
// 新建文件
child->newFile();
// 显示子窗口
child->show();
}
void MainWindow::on_actionOpen_triggered()
{
QString fileName = QFileDialog::getOpenFileName(this,tr("打开文件")); // 获取文件路径
if(fileName.isEmpty())
return;
//打开文件
MdiChild *child = createMdiChild(); // 如果没有打开,则新建子窗口
if (child->loadFile(fileName)) {
ui->statusBar->showMessage(tr("打开文件成功"), 2000);
child->show();
} else {
child->close();
}
}
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_actionExit_triggered()// 退出菜单
{
qApp->closeAllWindows();
}
MdiChild *MainWindow::createMdiChild()
{
MdiChild *child = new MdiChild; // 创建MdiChild部件
ui->mdiArea->addSubWindow(child); // 向多文档区域添加子窗口,child为中心部件
// 根据QTextEdit类的是否可以复制信号设置剪切复制动作是否可用
connect(child,SIGNAL(copyAvailable(bool)),ui->actionCut,
SLOT(setEnabled(bool)));
connect(child,SIGNAL(copyAvailable(bool)),ui->actionCopy,
SLOT(setEnabled(bool)));
// 每当编辑器中的光标位置改变,就重新显示行号和列号
connect(child,SIGNAL(cursorPositionChanged()),this,SLOT(showTextRowAndCol()));
return child;
}
void MainWindow::readSettings()
{
QSettings settings("Neusoft-neuedu/neutech", "GodOuO");
QPoint pos = settings.value("pos", QPoint(200, 200)).toPoint();
QSize size = settings.value("size", QSize(600, 400)).toSize();
move(pos);
resize(size);
}
void MainWindow::writeSettings()
{
QSettings settings("Neusoft-neuedu/neutech", "GodOuO");
settings.setValue("pos", pos()); // 写入位置信息
settings.setValue("size", size()); // 写入大小信息
}
void MainWindow::initWindow()
{
setWindowTitle(tr("GodOuOEdit"));
// 我们在工具栏上单击鼠标右键时,可以关闭工具栏
ui->mainToolBar->setWindowTitle(tr("工具栏"));
// 当多文档区域的内容超出可视区域后,出现滚动条
ui->mdiArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
ui->mdiArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
ui->statusBar->showMessage(tr("Welcome To GodOuOEdit"));
QLabel *label = new QLabel(this);
label->setFrameStyle(QFrame::Box | QFrame::Sunken);
label->setText(tr("人机交互应用开发技术"));
label->setTextFormat(Qt::RichText); // 标签文本为富文本
label->setOpenExternalLinks(true); // 可以打开外部链接
ui->statusBar->addPermanentWidget(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的介绍"));
}
MdiChild *MainWindow::activeMdiChild()
{
// 如果有活动窗口,则将其内的中心部件转换为MdiChild类型
if(QMdiSubWindow *activeSubWindow = ui->mdiArea->activeSubWindow())
return qobject_cast<MdiChild*>(activeSubWindow->widget());
return 0;// 没有活动窗口,直接返回0
}
void MainWindow::closeEvent(QCloseEvent *event)
{
ui->mdiArea->closeAllSubWindows(); // 先执行多文档区域的关闭操作
if (ui->mdiArea->currentSubWindow()) {
event->ignore(); // 如果还有窗口没有关闭,则忽略该事件
} else {
writeSettings(); // 在关闭前写入窗口设置
event->accept();
}
}
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::showTextRowAndCol()
{
// 如果有活动窗口,则显示其中光标所在的位置
if(activeMdiChild()){
// 因为获取的行号和列号都是从0开始的,所以我们这里进行了加1
int rowNum = activeMdiChild()->textCursor().blockNumber()+1;
int colNum = activeMdiChild()->textCursor().columnNumber()+1;
ui->statusBar->showMessage(tr("%1行 %2列")
.arg(rowNum).arg(colNum),2000);
}
}
void MainWindow::on_actionNext_triggered()// 下一个菜单
{
ui->mdiArea->activateNextSubWindow();
}
void MainWindow::on_actionTile_triggered()// 平铺菜单
{
ui->mdiArea->tileSubWindows();
}
void MainWindow::on_actionCascade_triggered()// 层叠菜单
{
ui->mdiArea->cascadeSubWindows();
}
void MainWindow::on_actionPrevious_triggered()// 前一个菜单
{
ui->mdiArea->activatePreviousSubWindow();
}
void MainWindow::on_actionAbout_triggered() // 关于菜单
{
QMessageBox::about(this,tr("关于本软件"),tr("人机交互应用开发技术\n姓名:宋明桥\n学号:181203616"));
}
void MainWindow::on_actionAboutQt_triggered()// 关于Qt菜单
{
qApp->aboutQt(); // 这里的qApp是QApplication对象的全局指针,这行代码相当于QApplication::aboutQt();
}
void MainWindow::on_boldAct_triggered()
{
QFont bold;
bold.setBold(true);
ui->boldAct->setFont(bold);
QTextCharFormat fmt;
fmt.setFontWeight(ui->boldAct->isChecked() ? QFont::Bold : QFont::Normal);
if(activeMdiChild())
activeMdiChild()->mergeFormatOnWordOrSelection(fmt);
}
void MainWindow::on_italicAct_triggered()
{
QTextCharFormat fmt;
fmt.setFontItalic(ui->italicAct->isChecked());
if(activeMdiChild())
activeMdiChild()->mergeFormatOnWordOrSelection(fmt);
}
void MainWindow::on_underlineAct_triggered()
{
QTextCharFormat fmt;
fmt.setFontUnderline(ui->underlineAct->isChecked());
if(activeMdiChild())
activeMdiChild()->mergeFormatOnWordOrSelection(fmt);
}
void MainWindow::on_action_yangshi_triggered()
{
bool ok = false;
QFont font = QFontDialog::getFont(&ok,this);
if(ok)
{
this->activeMdiChild()->setFont(font);
}
}
void MainWindow::on_actioncolor_triggered()
{
if(activeMdiChild())
{
QColor col = QColorDialog::getColor(activeMdiChild()->textColor(), this);
if (!col.isValid())
return;
QTextCharFormat fmt;
fmt.setForeground(col);
activeMdiChild()->mergeFormatOnWordOrSelection(fmt);
}
}
效果图: