记事本应用程序提供了基本的文本编辑功能,支持文件的新建、打开、保存和另存为操作,同时具备修改提示和关闭窗口时的保存确认功能。使用 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
dart
// 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
dart
// notepad.cpp
#include "mainwindow.h"
#include <QTextStream>
#include <QSaveFile> // 添加缺失的头文件
#include <QStringConverter> // 添加缺失的头文件
#if _MSC_VER >= 1600
#pragma execution_character_set("utf-8")
#endif
Notepad::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编码(
二、潜在问题及改进建议
-
大文件处理
-
当前实现直接读取整个文件到内存,存在内存耗尽风险。建议:
复制
cpp// 分块读取(示例) while(!in.atEnd()) { QString chunk = in.read(4096); textEdit->insertPlainText(chunk); }
-
可参考Notepad++的1MB缓存限制机制
-
-
安全漏洞预防
-
未实现文件变更监控,建议添加
QFileSystemWatcher
检测外部修改; -
需注意Notepad++曾出现的编码转换漏洞
,建议对转换缓冲区进行边界检查。
-
-
用户体验优化
-
增加多文档支持(参考Notepad++的Tab系统)
-
实现撤销/重做栈:
复制
cppconnect(textEdit->document(), &QTextDocument::undoAvailable, undoAct, &QAction::setEnabled);
-
三、关键代码段优化
-
编码处理增强
复制
cpp// 改进后的保存函数 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(); } //...错误处理 }
-
会话管理实现
cpp// 参考Notepad++的Session结构[4](@ref) struct Session { QStringList recentFiles; QMap<QString, QByteArray> fileStates; // 文件路径+光标位置等 };
四、架构扩展建议
-
插件系统设计
- 创建
PluginInterface
抽象基类 - 实现类似Notepad++的插件加载机制
- 创建
-
语法高亮支持
cpp// 参考Scintilla库集成[2](@ref) class SyntaxHighlighter : public QSyntaxHighlighter { // 实现词法分析规则 };
mianwindow.h
dart
// 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_OBJECT
public:
explicit Notepad(QWidget *parent = nullptr);
~Notepad();
protected:
void closeEvent(QCloseEvent *event) override;
private slots:
void newFile();
void openFile();
bool saveFile(); // 修正返回类型为bool
bool saveAsFile(); // 修正返回类型为bool
void 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
复制
cppclass Notepad : public QMainWindow
- 符合Qt主窗口程序标准结构
- 支持菜单栏、工具栏等标准组件
-
成员变量设计
cpp
复制
cppQTextEdit *textEdit; // 核心编辑组件 QString currentFile; // 当前文件路径 QMenu *fileMenu, *helpMenu; // 主菜单项 QAction *newAct, *openAct... // 功能动作
- 采用Qt对象树内存管理机制
- 指针初始化为
nullptr
(构造函数初始化列表可见)
二、接口设计评估
-
信号槽系统
cpp
复制
cppprivate slots: void newFile(); bool saveFile(); // 注意:Qt槽函数通常返回void
-
存在设计矛盾:
saveFile()
返回bool但作为槽函数
建议修改
:
cpp
复制
cppprivate: bool performSave(); // 非槽函数 private slots: void saveFile(); // 调用performSave()
-
-
事件处理
cpp
复制
cppprotected: void closeEvent(QCloseEvent *event) override;
- 正确覆盖基类虚函数
- 配合
maybeSave()
实现关闭确认
三、代码健壮性分析
-
头文件包含
cpp
复制
cpp#include <QMenuBar> // 冗余包含
-
QMainWindow已包含QMenuBar
优化建议
:
cpp
复制
cpp// 前置声明替代部分包含 class QMenu; class QAction;
-
-
防御性编程
cpp
复制
cppvoid createActions(); // 未处理可能的创建失败
建议增强
:
cpp
复制
cppbool createActions(); // 返回创建状态
四、扩展性改进建议
-
支持多编码
cpp
复制
cpp// 添加编码相关成员 QTextCodec *currentCodec = QTextCodec::codecForName("UTF-8");
-
会话管理
cpp
复制
cpp// 添加最近文件列表 QStringList recentFiles; static const int MAX_RECENT_FILES = 5;
五、代码规范优化
-
命名一致性
cpp
复制
cppvoid about(); // 与"关于Qt"的常规命名不一致
- 建议改为
aboutQtNotepad()
- 建议改为
-
const正确性
cpp
复制
cppbool save(const QString &fileName);
-
可添加const修饰:
cpp
复制
cppbool isModified() const;
-