记事本应用程序提供了基本的文本编辑功能,支持文件的新建、打开、保存和另存为操作,同时具备修改提示和关闭窗口时的保存确认功能。使用 UTF - 8 编码确保了对多语言文本的支持。
1. 项目整体结构
main.cpp
:程序的入口点,负责初始化 Qt 应用程序并创建Notepad
窗口实例,最后进入应用程序的事件循环。mainwindow.h
:定义了Notepad
类,继承自QMainWindow
,包含了窗口的成员变量和成员函数的声明。mainwindow.cpp
:实现了Notepad
类的成员函数,完成了记事本的各种功能。
2. 主要功能模块
2.1 窗口初始化
- 在
Notepad
类的构造函数中,创建了一个QTextEdit
控件作为中央部件,用于文本编辑。 - 调用
createActions()
和createMenus()
函数创建菜单栏和菜单项对应的动作。 - 设置窗口标题、大小和初始修改状态。
2.2 菜单栏和动作
- 文件菜单 (
文件(&F)
)- 新建 (&N) (
Ctrl+N
):调用newFile()
函数,在保存当前修改后清空文本编辑区,重置当前文件名和修改状态。 - 打开 (&O)… (
Ctrl+O
):调用openFile()
函数,在保存当前修改后弹出文件选择对话框,选择文件并加载到文本编辑区。 - 保存 (&S) (
Ctrl+S
):调用saveFile()
函数,如果当前文件名为空,则调用saveAsFile()
函数进行另存为操作;否则直接保存当前文件。 - 另存为 (&A)… (
Ctrl+Shift+S
):调用saveAsFile()
函数,弹出文件保存对话框,选择保存路径和文件名并保存文件。 - 退出 (&X):调用
QWidget::close()
函数关闭窗口。
- 新建 (&N) (
- 帮助菜单 (
帮助(&H)
)- 关于 (&A)…:调用
about()
函数,弹出关于对话框,显示应用程序的版本信息和开发环境。
- 关于 (&A)…:调用
2.3 文件操作
- 保存文件 (
save(const QString &fileName)
):使用QSaveFile
打开文件,以 UTF - 8 编码将文本编辑区的内容写入文件。如果保存成功,更新当前文件名和修改状态。 - 加载文件 (
loadFile(const QString &fileName)
):使用QFile
打开文件,自动检测文件编码并读取内容到文本编辑区。如果打开失败,弹出错误对话框。
2.4 修改提示
maybeSave()
:在新建文件、打开文件或关闭窗口时,检查文本编辑区的内容是否被修改。如果已修改,弹出警告对话框询问用户是否保存更改,根据用户的选择进行相应操作。
2.5 关闭窗口处理
closeEvent(QCloseEvent \*event)
:在窗口关闭时,调用maybeSave()
函数询问用户是否保存更改。如果保存成功或用户选择不保存,则接受关闭事件;否则忽略关闭事件。
main.c
// main.cpp
#include "mainwindow.h"
#include <QApplication>int main(int argc ,char *argv[])
{QApplication app(argc, argv);app.setWindowIcon(QIcon(":/icons/notepad.png"));Notepad notepad;notepad.show();return app.exec();
}
mianwidow.c
// notepad.cpp
#include "mainwindow.h"
#include <QTextStream>
#include <QSaveFile> // 添加缺失的头文件
#include <QStringConverter> // 添加缺失的头文件#if _MSC_VER >= 1600
#pragma execution_character_set("utf-8")
#endifNotepad::Notepad(QWidget *parent): QMainWindow(parent),textEdit(new QTextEdit),fileMenu(nullptr),newAct(nullptr),openAct(nullptr),saveAct(nullptr),saveAsAct(nullptr),exitAct(nullptr),helpMenu(nullptr),aboutAct(nullptr)
{textEdit = new QTextEdit;setCentralWidget(textEdit);createActions();createMenus();setWindowTitle(QStringLiteral("Qt记事本[*]"));resize(800, 600);setWindowModified(false);
}void Notepad::createActions()
{newAct = new QAction(QStringLiteral("新建(&N)"), this);newAct->setShortcut(QKeySequence::New);connect(newAct, &QAction::triggered, this, &Notepad::newFile);openAct = new QAction(QStringLiteral("打开(&O)..."), this);openAct->setShortcut(QKeySequence::Open);connect(openAct, &QAction::triggered, this, &Notepad::openFile);saveAct = new QAction(QStringLiteral("保存(&S)"), this);saveAct->setShortcut(QKeySequence::Save);connect(saveAct, &QAction::triggered, this, &Notepad::saveFile);saveAsAct = new QAction(QStringLiteral("另存为(&A)..."), this);saveAsAct->setShortcut(QKeySequence::SaveAs);connect(saveAsAct, &QAction::triggered, this, &Notepad::saveAsFile);exitAct = new QAction(QStringLiteral("退出(&X)"), this);connect(exitAct, &QAction::triggered, this, &QWidget::close);aboutAct = new QAction(QStringLiteral("关于(&A)..."), this);connect(aboutAct, &QAction::triggered, this, &Notepad::about);
}void Notepad::createMenus()
{fileMenu = menuBar()->addMenu(QStringLiteral("文件(&F)"));fileMenu->addAction(newAct);fileMenu->addAction(openAct);fileMenu->addAction(saveAct);fileMenu->addAction(saveAsAct);fileMenu->addSeparator();fileMenu->addAction(exitAct);helpMenu = menuBar()->addMenu(QStringLiteral("帮助(&H)"));helpMenu->addAction(aboutAct);
}void Notepad::newFile()
{if (maybeSave()) {textEdit->clear();currentFile.clear();setWindowModified(false);}
}void Notepad::openFile()
{if (maybeSave()) {QString fileName = QFileDialog::getOpenFileName(this);if (!fileName.isEmpty()) {loadFile(fileName);setWindowModified(false);}}
}bool Notepad::saveFile()
{return currentFile.isEmpty() ? saveAsFile() : save(currentFile);
}bool Notepad::saveAsFile() // 修正返回类型为bool
{QString fileName = QFileDialog::getSaveFileName(this,QStringLiteral("另存为"), currentFile);return fileName.isEmpty() ? false : save(fileName);
}
void Notepad::about()
{QMessageBox::about(this, QStringLiteral("关于 Qt记事本"),QStringLiteral("<h2>Qt记事本 2.0</h2>""<p>基于Qt 6.5开发</p>""<p>支持UTF-8编码文件读写</p>"));
}bool Notepad::maybeSave()
{if (!textEdit->document()->isModified())return true;QMessageBox::StandardButton ret = QMessageBox::warning(this,QStringLiteral("文档修改"),QStringLiteral("文档内容已修改,是否保存更改?"),QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);if (ret == QMessageBox::Save)return saveFile();else if (ret == QMessageBox::Cancel)return false;return true;
}bool Notepad::save(const QString &fileName)
{QSaveFile file(fileName);if (file.open(QFile::WriteOnly | QFile::Text)) {QTextStream out(&file);out.setEncoding(QStringConverter::Utf8);out << textEdit->toPlainText();if (file.commit()) {currentFile = fileName;setWindowModified(false);return true;}}QMessageBox::critical(this, QStringLiteral("保存失败"),QStringLiteral("无法保存文件:\n%1\n%2").arg(fileName, file.errorString()));return false;
}void Notepad::loadFile(const QString &fileName)
{QFile file(fileName);if (file.open(QFile::ReadOnly | QFile::Text)) {QTextStream in(&file);in.setAutoDetectUnicode(true);textEdit->setText(in.readAll());currentFile = fileName;} else {QMessageBox::critical(this, QStringLiteral("打开失败"),QStringLiteral("无法打开文件:\n%1\n%2").arg(fileName, file.errorString()));}
}void Notepad::closeEvent(QCloseEvent *event)
{maybeSave() ? event->accept() : event->ignore();
}Notepad::~Notepad() {}
一、核心功能实现分析
-
文件操作框架
-
采用
QSaveFile
实现原子写入(先写临时文件再替换原文件),有效防止写入中断导致文件损坏;
-
loadFile()
使用
QTextStream
自动检测编码,但建议增加手动编码选择功能(参考Notepad++的多编码支持);
-
缺少最近文件列表功能,可参考Notepad++的Session机制
实现历史文件恢复。
-
-
菜单系统架构
- 采用经典MVC模式,通过
QAction
实现命令与UI解耦; - 快捷键绑定符合平台规范,但缺少自定义快捷键功能(Notepad++支持快捷键自定义)。
- 采用经典MVC模式,通过
-
编码处理
- 显式设置UTF-8编码(
QStringConverter::Utf8
),但未处理BOM标记; - 建议参考Notepad++的编码识别机制,添加自动检测编码功能。
- 显式设置UTF-8编码(
二、潜在问题及改进建议
-
大文件处理
-
当前实现直接读取整个文件到内存,存在内存耗尽风险。建议:
复制
// 分块读取(示例) while(!in.atEnd()) {QString chunk = in.read(4096);textEdit->insertPlainText(chunk); }
-
可参考Notepad++的1MB缓存限制机制
-
-
安全漏洞预防
-
未实现文件变更监控,建议添加
QFileSystemWatcher
检测外部修改; -
需注意Notepad++曾出现的编码转换漏洞
,建议对转换缓冲区进行边界检查。
-
-
用户体验优化
-
增加多文档支持(参考Notepad++的Tab系统)
-
实现撤销/重做栈:
复制
connect(textEdit->document(), &QTextDocument::undoAvailable, undoAct, &QAction::setEnabled);
-
三、关键代码段优化
-
编码处理增强
复制
// 改进后的保存函数 bool Notepad::save(const QString &fileName) {QSaveFile file(fileName);if (file.open(QFile::WriteOnly | QFile::Text)) {QTextStream out(&file);// 添加BOM支持out.setGenerateByteOrderMark(true); out.setEncoding(QStringConverter::Utf8);// 分块写入const QString text = textEdit->toPlainText();for(int i=0; i<text.size(); i+=4096) {out << text.mid(i, 4096);}return file.commit();}//...错误处理 }
-
会话管理实现
// 参考Notepad++的Session结构[4](@ref) struct Session {QStringList recentFiles;QMap<QString, QByteArray> fileStates; // 文件路径+光标位置等 };
四、架构扩展建议
-
插件系统设计
- 创建
PluginInterface
抽象基类 - 实现类似Notepad++的插件加载机制
- 创建
-
语法高亮支持
// 参考Scintilla库集成[2](@ref) class SyntaxHighlighter : public QSyntaxHighlighter {// 实现词法分析规则 };
mianwindow.h
// notepad.h
#ifndef NOTEPAD_H
#define NOTEPAD_H#include <QMainWindow>
#include <QTextEdit>
#include <QFileDialog>
#include <QMessageBox>
#include <QCloseEvent>
#include <QTextStream>
#include <QMenuBar> // 添加缺失的头文件
#include <QMenu> // 添加缺失的头文件
#include <QAction> // 添加缺失的头文件class Notepad : public QMainWindow
{Q_OBJECTpublic:explicit Notepad(QWidget *parent = nullptr);~Notepad();protected:void closeEvent(QCloseEvent *event) override;private slots:void newFile();void openFile();bool saveFile(); // 修正返回类型为boolbool saveAsFile(); // 修正返回类型为boolvoid about();private:void createActions();void createMenus();bool maybeSave();bool save(const QString &fileName);void loadFile(const QString &fileName);QTextEdit *textEdit;QString currentFile;QMenu *fileMenu;QAction *newAct;QAction *openAct;QAction *saveAct;QAction *saveAsAct;QAction *exitAct;QMenu *helpMenu;QAction *aboutAct;
};#endif // NOTEPAD_H
一、代码结构分析
-
继承关系
cpp
复制
class Notepad : public QMainWindow
- 符合Qt主窗口程序标准结构
- 支持菜单栏、工具栏等标准组件
-
成员变量设计
cpp
复制
QTextEdit *textEdit; // 核心编辑组件 QString currentFile; // 当前文件路径 QMenu *fileMenu, *helpMenu; // 主菜单项 QAction *newAct, *openAct... // 功能动作
- 采用Qt对象树内存管理机制
- 指针初始化为
nullptr
(构造函数初始化列表可见)
二、接口设计评估
-
信号槽系统
cpp
复制
private slots:void newFile();bool saveFile(); // 注意:Qt槽函数通常返回void
-
存在设计矛盾:
saveFile()
返回bool但作为槽函数
建议修改
:
cpp
复制
private:bool performSave(); // 非槽函数 private slots:void saveFile(); // 调用performSave()
-
-
事件处理
cpp
复制
protected:void closeEvent(QCloseEvent *event) override;
- 正确覆盖基类虚函数
- 配合
maybeSave()
实现关闭确认
三、代码健壮性分析
-
头文件包含
cpp
复制
#include <QMenuBar> // 冗余包含
-
QMainWindow已包含QMenuBar
优化建议
:
cpp
复制
// 前置声明替代部分包含 class QMenu; class QAction;
-
-
防御性编程
cpp
复制
void createActions(); // 未处理可能的创建失败
建议增强
:
cpp
复制
bool createActions(); // 返回创建状态
四、扩展性改进建议
-
支持多编码
cpp
复制
// 添加编码相关成员 QTextCodec *currentCodec = QTextCodec::codecForName("UTF-8");
-
会话管理
cpp
复制
// 添加最近文件列表 QStringList recentFiles; static const int MAX_RECENT_FILES = 5;
五、代码规范优化
-
命名一致性
cpp
复制
void about(); // 与"关于Qt"的常规命名不一致
- 建议改为
aboutQtNotepad()
- 建议改为
-
const正确性
cpp
复制
bool save(const QString &fileName);
-
可添加const修饰:
cpp
复制
bool isModified() const;
-