QT记事本

记事本应用程序提供了基本的文本编辑功能,支持文件的新建、打开、保存和另存为操作,同时具备修改提示和关闭窗口时的保存确认功能。使用 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()函数关闭窗口。
  • 帮助菜单 (帮助(&H))
    • 关于 (&A)... :调用about()函数,弹出关于对话框,显示应用程序的版本信息和开发环境。
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() {}

一、核心功能实现分析

  1. 文件操作框架

    • 采用

      复制代码
      QSaveFile

      实现原子写入(先写临时文件再替换原文件),有效防止写入中断导致文件损坏;

    复制代码
      loadFile()

    使用

    复制代码
      QTextStream

    自动检测编码,但建议增加手动编码选择功能(参考Notepad++的多编码支持);

    • 缺少最近文件列表功能,可参考Notepad++的Session机制

      实现历史文件恢复。

  2. 菜单系统架构

    • 采用经典MVC模式,通过QAction实现命令与UI解耦;
    • 快捷键绑定符合平台规范,但缺少自定义快捷键功能(Notepad++支持快捷键自定义)。
  3. 编码处理

    • 显式设置UTF-8编码(QStringConverter::Utf8),但未处理BOM标记;
    • 建议参考Notepad++的编码识别机制,添加自动检测编码功能。

二、潜在问题及改进建议

  1. 大文件处理

    • 当前实现直接读取整个文件到内存,存在内存耗尽风险。建议:

      复制

      cpp 复制代码
      // 分块读取(示例)
      while(!in.atEnd()) {
          QString chunk = in.read(4096);
          textEdit->insertPlainText(chunk);
      }
    • 可参考Notepad++的1MB缓存限制机制

  2. 安全漏洞预防

    • 未实现文件变更监控,建议添加QFileSystemWatcher检测外部修改;

    • 需注意Notepad++曾出现的编码转换漏洞

      ,建议对转换缓冲区进行边界检查。

  3. 用户体验优化

    • 增加多文档支持(参考Notepad++的Tab系统)

    • 实现撤销/重做栈:

      复制

      cpp 复制代码
      connect(textEdit->document(), &QTextDocument::undoAvailable, 
              undoAct, &QAction::setEnabled);

三、关键代码段优化

  1. 编码处理增强

    复制

    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();
        }
        //...错误处理
    }
  2. 会话管理实现

    cpp 复制代码
    // 参考Notepad++的Session结构[4](@ref)
    struct Session {
        QStringList recentFiles;
        QMap<QString, QByteArray> fileStates; // 文件路径+光标位置等
    };

四、架构扩展建议

  1. 插件系统设计

    • 创建PluginInterface抽象基类
    • 实现类似Notepad++的插件加载机制
  2. 语法高亮支持

    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

一、代码结构分析

  1. 继承关系

    cpp

    复制

    cpp 复制代码
    class Notepad : public QMainWindow
    • 符合Qt主窗口程序标准结构
    • 支持菜单栏、工具栏等标准组件
  2. 成员变量设计

    cpp

    复制

    cpp 复制代码
    QTextEdit *textEdit;       // 核心编辑组件
    QString currentFile;       // 当前文件路径
    QMenu *fileMenu, *helpMenu; // 主菜单项
    QAction *newAct, *openAct... // 功能动作
    • 采用Qt对象树内存管理机制
    • 指针初始化为nullptr(构造函数初始化列表可见)

二、接口设计评估

  1. 信号槽系统

    cpp

    复制

    cpp 复制代码
    private slots:
        void newFile();
        bool saveFile();  // 注意:Qt槽函数通常返回void
    • 存在设计矛盾:saveFile()返回bool但作为槽函数

    建议修改

    cpp

    复制

    cpp 复制代码
    private:
        bool performSave(); // 非槽函数
    private slots:
        void saveFile();    // 调用performSave()
  2. 事件处理

    cpp

    复制

    cpp 复制代码
    protected:
        void closeEvent(QCloseEvent *event) override;
    • 正确覆盖基类虚函数
    • 配合maybeSave()实现关闭确认

三、代码健壮性分析

  1. 头文件包含

    cpp

    复制

    cpp 复制代码
    #include <QMenuBar>  // 冗余包含
    • QMainWindow已包含QMenuBar

    优化建议

    cpp

    复制

    cpp 复制代码
    // 前置声明替代部分包含
    class QMenu;
    class QAction;
  2. 防御性编程

    cpp

    复制

    cpp 复制代码
    void createActions(); // 未处理可能的创建失败

    建议增强

    cpp

    复制

    cpp 复制代码
    bool createActions(); // 返回创建状态

四、扩展性改进建议

  1. 支持多编码

    cpp

    复制

    cpp 复制代码
    // 添加编码相关成员
    QTextCodec *currentCodec = QTextCodec::codecForName("UTF-8");
  2. 会话管理

    cpp

    复制

    cpp 复制代码
    // 添加最近文件列表
    QStringList recentFiles;
    static const int MAX_RECENT_FILES = 5;

五、代码规范优化

  1. 命名一致性

    cpp

    复制

    cpp 复制代码
    void about();  // 与"关于Qt"的常规命名不一致
    • 建议改为aboutQtNotepad()
  2. const正确性

    cpp

    复制

    cpp 复制代码
    bool save(const QString &fileName); 
    • 可添加const修饰:

      cpp

      复制

      cpp 复制代码
      bool isModified() const;
相关推荐
钮钴禄·爱因斯晨几秒前
Java 面向对象编程中 static 的深度剖析与实践
java·开发语言
SNAKEpc1213812 分钟前
在MFC中使用Qt(二):实现Qt文件的自动编译流程
c++·qt·mfc
self-discipline6341 小时前
【计网速通】计算机网络核心知识点和高频考点——数据链路层(一)
网络·计算机网络
榆榆欸1 小时前
5.实现 Channel 类,Reactor 模式初步形成
linux·网络·c++·tcp/ip
m0_741574751 小时前
IP综合实验
网络
Dream Algorithm1 小时前
SD-WAN组网方案
网络·信息与通信
obboda1 小时前
VRRP虚拟路由器冗余协议
网络·智能路由器
2301_旺仔1 小时前
VRRP交换机三层架构综合实验
网络·vrrp·交换机互为备份·stp生成树
fundroid3 小时前
Rust 为什么不适合开发 GUI
开发语言·后端·rust
三体世界3 小时前
C++ List的模拟实现
java·c语言·开发语言·数据结构·c++·windows·list