> # ♻️ 资源
> **大小:** 21.8MB
> **文档链接:**[**https://www.yuque.com/sxbn/ks/100010268**](https://www.yuque.com/sxbn/ks/100010268)
> **➡️ 资源下载:**[**https://download.csdn.net/download/s1t16/87354521**](https://download.csdn.net/download/s1t16/87354521)
> **注:更多内容可关注微信公众号【神仙别闹】,如当前文章或代码侵犯了您的权益,请私信作者删除!**
> ![qrcode_for_gh_d52056803b9a_344.jpg](https://cdn.nlark.com/yuque/0/2023/jpeg/2469055/1692147256036-49ec7e0c-5434-4963-b805-47e7295c9cbc.jpeg#averageHue=%23a3a3a3&clientId=u8fb96484-770e-4&from=paste&height=140&id=u237e511a&originHeight=344&originWidth=344&originalType=binary&ratio=1.25&rotation=0&showTitle=false&size=8270&status=done&style=none&taskId=ud96bf5f7-fe85-4848-b9c2-82251181297&title=&width=140.1999969482422)
# 文档编辑器
## 主要内容
此次课程设计较规范地实现了一个多文档编辑器
![b7bf6b7dc2cc2437facd877b75f57847.png](https://cdn.nlark.com/yuque/0/2024/png/2469055/1708304958925-42313ab5-6dad-4d42-acd8-0e7a096bbdb3.png#averageHue=%23dadada&clientId=u445cdc45-4180-4&from=paste&height=511&id=ue2589550&originHeight=639&originWidth=802&originalType=binary&ratio=1.25&rotation=0&showTitle=false&size=54589&status=done&style=none&taskId=u060422d6-6425-41df-9d50-41c7d7ccd97&title=&width=641.6)
类似于记事本,可以实时输入文本。支持 txt 文件的读取、保存、撤销等工作等。
# 一、设计思路
界面设计 先进行主窗口菜单栏的设计。
![735fa62c3aa05f8d71fe50d86e5d85b3.png](https://cdn.nlark.com/yuque/0/2024/png/2469055/1708304978748-34a129e8-2663-4466-90e4-43e0e1ff5c02.png#averageHue=%23afafaf&clientId=u445cdc45-4180-4&from=paste&height=511&id=u835c5668&originHeight=639&originWidth=802&originalType=binary&ratio=1.25&rotation=0&showTitle=false&size=27565&status=done&style=none&taskId=u74cb8d3b-ab2d-4429-9c1f-af8ab8b53e5&title=&width=641.6)
设计完菜单栏,向 Action Editor 添加子菜单。
![0f0bacd0be24ee61f42aad1d6250bf9e.png](https://cdn.nlark.com/yuque/0/2024/png/2469055/1708304993130-da992566-2ca5-43db-8abd-9ae30f6126fa.png#averageHue=%23fafaf9&clientId=u445cdc45-4180-4&from=paste&height=492&id=u7164dc04&originHeight=615&originWidth=992&originalType=binary&ratio=1.25&rotation=0&showTitle=false&size=92556&status=done&style=none&taskId=u1bc87df9-5602-4789-946c-2ec91f94d51&title=&width=793.6)
## 1.1 子窗口设计
子窗口中心部件使用 QTextEdit 类,故实现一个继承于 QTextEdit 的类,类函数由思维导图提供
如下:
![38050afc7d48de98d13f514b51c96faf.png](https://cdn.nlark.com/yuque/0/2024/png/2469055/1708305011541-67a17933-ce65-4025-84b8-40f80f974a0c.png#averageHue=%23f9f9f9&clientId=u445cdc45-4180-4&from=paste&height=921&id=u2d30cbdf&originHeight=1151&originWidth=880&originalType=binary&ratio=1.25&rotation=0&showTitle=false&size=153826&status=done&style=none&taskId=u3c480316-42a3-48e3-8db2-7c2de79f737&title=&width=704)
## 1.2 实现菜单的功能
### 1.2.1 更新菜单的状态
通过窗口的状态确定,哪些功能键可以使用,哪些不能。
```c
#include <QMainWindow>
//增加类MdiChild的前置声明
class MdiChild;
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void on_actionNew_triggered();
void updateMenus(); //更新菜单
private:
Ui::MainWindow *ui;
QAction *actionSeparator; //分隔符
MdiChild *activeMdiChild(); //活动窗口
};
```
### 1.2.2 实现新建文件的操作
```c
MdiChild * MainWindow::createMdiChild() //创建子窗口部件
{
MdiChild *child = new MdiChild; //创建MdiChild部件
ui->mdiArea->addSubWindow(child); //向多文档区域添加子窗口,child为中心部件
connect(child,SIGNAL(copyAvailable(bool)),ui->actionCut,
SLOT(setEnabled(bool)));
// 根据QTextEdit类的是否可以复制信号设置剪切复制动作是否可用
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)));
return child;
}
```
### 1.2.3 实现文件打开操作
```c
void MainWindow::on_actionOpen_triggered() // 打开文件菜单
{
QString filePath =
QFileDialog::getOpenFileName(this); // 获取文件路径
if (!filePath.isEmpty())
{
// 如果路径不为空,则查看该文件是否已经打开
QMdiSubWindow *existing =
findMdiChild(filePath);
if (existing)
{
// 如果已经存在,则将对应的子窗口设置为活动窗口
ui->mdiArea->setActiveSubWindow(existing);
return;
}
MdiChild *child = createMdiChild(); // 如果没有打开,则新建子窗口
if (child->loadFile(filePath))
{
ui->statusBar->showMessage(tr("打开文件成功"), 2000);
child->show();
}
else
{
child->close();
}
}
}
```
### 1.2.4 添加子窗口列表
```c
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)
{
// 如果窗口数小于9,则设置编号为快捷键
text = tr("&%1 %2").arg(i + 1).arg(child->getFileNameFromPath());
}
else
{
text = tr("%1 %2").arg(i + 1).arg(child->getFileNameFromPath());
}
QAction *action = ui->menuW->addAction(text); // 添加动作到菜单
action->setCheckable(true); // 设置动作可以选择
// 设置当前活动窗口动作为选中状态
action ->setChecked(child ==activeMdiChild());
// 关联动作的触发信号到信号映射器的map()槽函数上,这个函数会发射mapped()信号
connect(action, SIGNAL(triggered()),
windowMapper, SLOT(map()));
// 将动作与相应的窗口部件进行映射,在发射mapped()信号时就会以这个窗口部件为参数
windowMapper->setMapping(action,
windows.at(i));
}
}
```
### 1.2.5 其它功能
因为在前面已经把核心的功能都实现了,而且像剪切、复制、撤销等常用功能,QTextEdit 类已经提供了,所以这里只需要调用相应的函数即可。
# 二、遇到的问题与解决方案
## 2.1 文件名问题
实现文档编辑器,需要在未保存的文件名上加上(*), 这里使用了一个 document()的库函数
根据文档的 isModified()函数的返回值,判断我们编辑器内容是否被更改了。如果被更改了,参数为 true,则 setWindowModified()就会在设置了[ _ ]号的地方显示“_”号 setWindowModified(document()->isModified()); setWindowModified()为库函数
## 2.2 添加子窗口列表
我们想每添加一个子窗口就可以在窗口菜单中罗列出它的文件名,而且可以在这个列表中选择一个子窗口,将它设置为活动窗口。这个看似很好实现,只要为窗口菜单添加菜单动作,然后关联这个动作的触发信号到设置活动窗口槽上就可以了。但是,如果有很多个子窗口怎么办,难道要一个一个进行关联吗,那怎么获知是哪个动作?