Qt仿写Windows记事本程序

Qt仿写Windows记事本程序

在简单学习了Qt的基本框架后,我尝试编写一个简单的记事本程序,来熟悉Qt常用控件、Qt应用程序的开发流程和Qt信号和槽机制的使用方法。本文将从Qt开发环境搭建、记事本功能设计、记事本程序界面设计、记事本程序功能实现四个方面介绍我是如何实现这个记事本程序的。

Qt开发环境搭建

Qt开发环境搭建的方法可以参考我的另一篇博客:Windows上Qt开发环境搭建

记事本功能设计

功能需求

记事本的功能参照Windows系统自带的记事本程序,主要功能有:
  **1.**文件菜单:新建、打开、保存、另存为、退出;
  **2.**编辑菜单:查找、替换、转到;
  **3.**查看菜单:缩放、状态栏。

功能分析

记事本应用程序仅支持仅仅开启一个主窗口(不支持Tab页),该窗口需要记录当前文件名(完整路径)。同时,因为需要标识文件的编辑和保存状态,程序需要维护一个状态标记(当前文件的修改是否保存)。根据文件的状态标记,程序可以进行相应操作(如保存文件、给窗口名添加*表示未保存等)。另外,程序为了支持光标位置、缩放等状态栏信息的显示,需要额外记录相关信息。
  将菜单中主要功能的逻辑分析如下:

新建文件

新建文件一定会改变程序当前维护的信息,因此执行新建任务时需要对当前文件状态进行检查,处理未保存的文件修改。新建文件时,初始化当前文件名为空(因为还没有一个完整路径的文件与之对应),标记为未保存,同时更新窗口名为新建文件.txt*

打开文件

打开文件如果成功那么也会改变当前程序维护的信息,因此执行打开任务时也需要对当前文件状态进行检查,处理未保存的文件修改。打开文件时,如果用户未选取文件,则不做任何操作;如果用户选取了文件,打开成功则初始化当前文件名为打开的文件名,标记为已经保存,同时更新窗口名为文件名;打开失败则弹窗提醒,不做其他任何操作。

保存文件

保存文件的操作,不论当前是否有文件打开都应该响应。如果当前没有文件打开,则调用另存为;如果当前文件名不为空则执行保存操作,如果文件标记已保存则不做任何操作,如果文件标记是未保存,则保存文件并更新窗口名(去掉*标记)。

另存为

另存未的操作需要用户选中存储的路径和文件名。如果用户未提供具体的路径和文件名,则不做任何操作;如果用户提供了另存文件的路径和文件名,则执行保存操作,更新文件标记为已保存,窗口更名为另存的文件名。

退出

退出操作仅需要检查当前文件状态,如果文件标记是未保存,则弹窗提醒是否保存,如果用户选择保存则执行保存操作,如果用户选择不保存则不做任何操作。

编辑与查看

编辑与查看功能更多是和Qt的组件进行交互,逻辑功能相对比较简单,这里不再列出。

记事本程序界面设计

记事本程序需要三个主要界面:主界面、查找替换界面、转到界面。分别负责文本的显示和交互、查找和替换的交互、转到指定行的交互。

主界面

主界面比较简单,主要包含一个菜单栏、文本编辑框和状态栏。主界面在程序启动时显示,在程序退出时消失,如下图所示:

查找替换界面

查找替换界面是一个Dialog窗口,包含一个查找框、一个替换框、两个查找按钮和两个替换按钮。查找替换界面在主界面菜单栏中点击查找和替换菜单时显示,在点击Dialog窗口的叉号后关闭,如下图所示:
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

转到界面

转到界面也是一个Dialog窗口,包含一个转到行号的输入框、转到按钮和取消按钮。转到界面在主界面菜单栏中点击转到菜单时显示,在点击转到按钮、点击取消按钮或者点击Dialog窗口的叉号后关闭,如下图所示:

记事本程序功能实现

程序的三个主要界面上的功能都需要有对应的Qt类实现,以完成与UI界面的交互和逻辑的处理,以下为三个界面的功能实现。

主界面类

主界面需要维护当前的文件信息(文件名和保存状态)、维护状态栏信息以及响应UI上的各种操作,其源文件如下:
  MyNotepad.h:

#pragma once
#include <QLabel>
#include <QMainWindow>
#include <QMap>
#include <vector>

#include "ui_MyNotepad.h"
#include "SearchAndReplace.h"
#include "Goto.h"
class MyNotepad : public QMainWindow {
    Q_OBJECT

public:
    using SearchDir = SearchAndReplace::SearchDir;
    using ReplaceType = SearchAndReplace::ReplaceType;

    MyNotepad(QWidget* parent = nullptr);
    ~MyNotepad();

private:
    // 初始光标位置,从1开始计数
    struct CursorPos {
        int line = 1;
        int column = 1;
    };

    // 状态栏信息,包括占位组件,版本号、光标位置、缩放比例
    struct StatusLabelInfo {
        enum SIZE { LABEL_NUM = 4 };                              // 状态栏组件数量
        enum LABELINDEX { PLACEHOLDER, VERSION, CURSOR, SCALE };  // 状态栏索引顺序
        const std::vector<int> label_width = {
            0,
            100,
            100,
            100,
        };                      // 状态栏组件宽度
        CursorPos cursor_pos;   // 光标位置
        int scale_ratio = 100;  // 缩放比例
    };

    Ui_MyNotepad* ui;
    QString currentFile;  // 表示窗口当前已经关联的包含路径的文件
    bool isSaved;         // 表示当前文件是否已经保存

    // 给底部添加的状态栏对象,一共4个
    // 第一个占位,第二个显示版本号,第三个显示光标位置,第四显示缩放百分比
    QLabel* statusLabel[StatusLabelInfo::LABEL_NUM] = {nullptr};
    StatusLabelInfo statusInfo;           // 记录状态栏相关信息
    SearchAndReplace* pSearchAndReplace;  // 查找和替换窗口类指针
    Goto* pGoto;                          // 跳转窗口类指针

private slots:
    // 窗口菜单中的槽函数
    void on_NewAction_triggered();
    void on_OpenAction_triggered();
    void on_SaveAction_triggered();
    void on_SaveAsAction_triggered();
    void on_QuitAction_triggered();
    void on_StatusBarAction_triggered(bool checked);
    void on_ReplaceAction_triggered();
    void on_FindAction_triggered();
    void on_ZoomInAction_triggered();
    void on_ZoomOutAction_triggered();
    void on_ZoomResetAction_triggered();
    void on_GotoAction_triggered();

    // 文本编辑控件的槽函数
    void on_textEdit_textChanged();
    void on_textEdit_cursorPositionChanged();

    // 接收到查找信号时的槽函数,第一个参数表示查找str,第二个参数表示查找方向
    void on_ReceiveSearchInfo(QString str, SearchDir dir);
    // 接收到替换信号时候的槽函数,第一个参数为查找str,第二个参数为替换str,第三个为替换类型Once or All
    void on_ReceiveReplaceInfo(QString sStr, QString rStr, ReplaceType type);
    // 接收到Goto转到信号时的槽函数,参数为行数
    void on_ReceiveGotoInfo(int line);

private:
    // 所有的connect函数
    void connectAll();
    // 根据状态更新窗口标题
    void updateWindowsTitle();
    // 初始化状态栏
    void initStatusLabel();
    // 反初始化状态栏
    void unInitStatusLable();
    // 更新状态栏中的光标位置
    void updateStatusLabelCursor();
    // 更新状态栏中的缩放比例
    void updateStatusLabelScale();
    // 更新状态栏中的占位组件,主要是根据窗口大小更新其宽度
    void updateStatusLabelPlaceHolder();
    
    // 新建、打开、退出时需要对当前状态进行检查,如果有未保存的更改提示用户更改
    void checkSaved();

protected:
    // 相应相关事件
    void resizeEvent(QResizeEvent* event);
    void keyPressEvent(QKeyEvent* event);
    void closeEvent(QCloseEvent* event);

    // 将内容输出到文件,updCurrFile用于标志是否要更新当前文件名(区分保存和另存)
    void outputToFile(QString fileName, bool updCurrFile = false);
};

MyNotepad.cpp:

#include "MyNotepad.h"

#include <QFileDialog>
#include <QIcon>
#include <QKeyEvent>
#include <QMessageBox>
#include <QStandardPaths>
#include <QString>
#include <QTextBlock>

namespace {}

MyNotepad::MyNotepad(QWidget* parent) : QMainWindow(parent), ui(new Ui_MyNotepad) {
    // 初始化MyNotepad类的成员变量
    ui->setupUi(this);
    pSearchAndReplace = new SearchAndReplace(this);
    pGoto = new Goto(this);
    currentFile = "";
    isSaved = true;
    // 初始化窗口名
    this->setWindowTitle("MyNotepad");
    // 初始化窗口的ico
    QIcon icon("mynotepad.ico");
    this->setWindowIcon(icon);
    // 初始化状态栏
    initStatusLabel();
    // 连接自定义的信号和槽函数
    connectAll();
}

MyNotepad::~MyNotepad() {
    delete ui;
    delete pSearchAndReplace;
    delete pGoto;
    unInitStatusLable();
}

void MyNotepad::connectAll() {
    // 链接信号与槽函数,其他槽函数自动链接
    connect(this->pSearchAndReplace, &SearchAndReplace::searchInfo, this, &MyNotepad::on_ReceiveSearchInfo);
    connect(this->pSearchAndReplace, &SearchAndReplace::replaceInfo, this, &MyNotepad::on_ReceiveReplaceInfo);
    connect(this->pGoto, &Goto::gotoLine, this, &MyNotepad::on_ReceiveGotoInfo);
}

void MyNotepad::initStatusLabel() {
    // 获取窗口的宽度
    int windows_width = this->width();
    // 创建四个状态栏控件,对有内容的控件指定宽度和对齐方式
    for (int i = 0; i < StatusLabelInfo::LABEL_NUM; i++) {
        statusLabel[i] = new QLabel();
        if (i) {
            statusLabel[i]->setFixedWidth(statusInfo.label_width[i]);
            statusLabel[i]->setAlignment(Qt::AlignLeft);
        }
        ui->statusbar->addWidget(statusLabel[i]);
    }
    statusLabel[StatusLabelInfo::VERSION]->setText("Version 1.0");
    // 更新光标位置,缩放比例,占位组件宽度
    updateStatusLabelCursor();
    updateStatusLabelScale();
    updateStatusLabelPlaceHolder();
}

void MyNotepad::unInitStatusLable() {
    for (int i = 0; i < StatusLabelInfo::LABEL_NUM; i++) {
        delete statusLabel[i];
    }
}

void MyNotepad::updateStatusLabelCursor() {
    auto cursor_pos = statusInfo.cursor_pos;
    statusLabel[StatusLabelInfo::CURSOR]->setText(
        QString("第%1行,第%2列").arg(cursor_pos.line).arg(cursor_pos.column));
}
void MyNotepad::updateStatusLabelScale() {
    statusLabel[StatusLabelInfo::SCALE]->setText(QString("%1%").arg(statusInfo.scale_ratio));
}
void MyNotepad::updateStatusLabelPlaceHolder() {
    auto begin = statusInfo.label_width.begin();
    auto end = statusInfo.label_width.end();
    int place_holder_width = this->width() - (std::accumulate(begin, end, 0));
    statusLabel[StatusLabelInfo::PLACEHOLDER]->setFixedWidth(place_holder_width);
}

void MyNotepad::updateWindowsTitle() {
    QString title = currentFile;
    // 确定窗口名
    if (currentFile.isEmpty()) {
        title = "新建文件.txt";
    } else {
        title = QFileInfo(currentFile).fileName();
    }
    // 确定保存标记
    if (!isSaved) {
        title += "*";
    }
    this->setWindowTitle(title);
}

void MyNotepad::on_NewAction_triggered() {
    qDebug() << "NewAction";
    checkSaved();
    ui->textEdit->clear();
    currentFile = "";
    isSaved = false;
    updateWindowsTitle();
}

void MyNotepad::on_OpenAction_triggered() {
    qDebug() << "OpenAction";
    checkSaved();
    // 获取桌面文件夹路径
    QString desktopPath = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
    // 打开文件对话框
    QString fileName = QFileDialog::getOpenFileName(this, "打开文件", desktopPath, "文本文件(*.txt)");
    qDebug() << fileName;
    if (fileName.isEmpty()) {
        return;
    } else {
        // 如果选取了文件,则打开文件
        QFile file(fileName);
        if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
            QMessageBox::information(this, "提示", "打开失败,文件可能被占用");
            return;
        }
        // 如果读取成功,则将文件内容显示到文本框中,并关联文件路径
        QTextStream in(&file);

        ui->textEdit->blockSignals(true);
        ui->textEdit->clear();
        ui->textEdit->setText(in.readAll());
        ui->textEdit->blockSignals(false);

        file.close();
        currentFile = fileName;
        // 窗口名更新为文件名
        isSaved = true;
        updateWindowsTitle();
    }
}

void MyNotepad::on_SaveAction_triggered() {
    qDebug() << "SaveAction";
    if (currentFile.isEmpty()) {  // 如果文件名为空,说明是新建文件,调用另存为
        on_SaveAsAction_triggered();
        return;
    } else {
        // 如果文件名不为空,说明是已经打开的文件,直接保存
        if (isSaved) {
            return;
        }
        outputToFile(currentFile);
    }
}

void MyNotepad::on_SaveAsAction_triggered() {
    qDebug() << "SaveAsAction";
    QString desktopPath = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
    QString fileName = QFileDialog::getSaveFileName(this, "另存为", desktopPath, "文本文件(*.txt)");
    if (fileName.isEmpty()) {
        return;
    } else {
        outputToFile(fileName, true);
    }
}

void MyNotepad::on_QuitAction_triggered() {
    qDebug() << "QuitAction";
    checkSaved();
    this->close();
}

void MyNotepad::on_textEdit_textChanged() {
    qDebug() << "textEdit_textChanged";
    isSaved = false;
    updateWindowsTitle();
}

void MyNotepad::on_textEdit_cursorPositionChanged() {
    qDebug() << "textEdit_cursorPositionChanged";
    QTextCursor currentCursor = ui->textEdit->textCursor();
    statusInfo.cursor_pos.line = currentCursor.blockNumber() + 1;
    statusInfo.cursor_pos.column = currentCursor.columnNumber() + 1;
    updateStatusLabelCursor();
}

void MyNotepad::on_StatusBarAction_triggered(bool checked) {
    qDebug() << "StatusBarAction_triggered" << checked;
    checked ? ui->statusbar->show() : ui->statusbar->hide();
}

void MyNotepad::on_ReplaceAction_triggered() { pSearchAndReplace->show(); }

void MyNotepad::on_FindAction_triggered() { pSearchAndReplace->show(); }

void MyNotepad::on_ReceiveSearchInfo(QString str, SearchDir dir) {
    qDebug() << "on_ReceiveSearchInfo" << str << (dir == SearchDir::LAST ? "LAST" : "NEXT");
    QTextCursor currentCursor = ui->textEdit->textCursor();
    // 当有选择状态时,指针在选中区域的尾部,因此需要偏移量
    int offset = currentCursor.hasSelection() ? str.length() : 0;
    int index = currentCursor.position();
    QString text = ui->textEdit->toPlainText();
    qDebug() << "text" << text << "index" << index;
    if (dir == SearchDir::LAST) {   // 向前查找
        index -= offset;
        if (index <= 0) {
            index = -1;
        } else {
            index = text.lastIndexOf(str, index - 1);
        }
    } else {                        // 向后查找
        index = text.indexOf(str, index);
    }
    if (index == -1) {              // 如果查找失败
        QMessageBox::information(this, "提示", "未找到");
    } else {
        // 如果成功,则修改光标位置并增加选中效果
        currentCursor.setPosition(index);
        currentCursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, str.length());
        ui->textEdit->setTextCursor(currentCursor);
    }
}

void MyNotepad::on_ReceiveReplaceInfo(QString sStr, QString rStr, ReplaceType type) {
    qDebug() << "on_ReceiveReplaceInfo" << sStr << " -> " << rStr << (type == ReplaceType::ONCE ? "ONCE" : "ALL");
    QTextCursor currentCursor = ui->textEdit->textCursor();
    int index = currentCursor.hasSelection() ? currentCursor.selectionStart() : currentCursor.position();
    QString text = ui->textEdit->toPlainText();
    // 从idx开始替换字符
    auto replaceStr = [&](int idx) {
        if (idx == -1)
            return;
        for (int i = 0; i < rStr.size(); i++) {
            text[idx + i] = rStr[i];
        }
    };
    if (type == ReplaceType::ONCE) {        // 单次替换
        index = text.indexOf(sStr, index);
        replaceStr(index);
    } else {                                // 全部替换
        index = 0;
        while (index != -1) {
            index = text.indexOf(sStr, index);
            replaceStr(index);
        }
    }
    ui->textEdit->setText(text);
}

void MyNotepad::on_ReceiveGotoInfo(int line) {
    qDebug() << "on_ReceiveGotoInfo" << line;
    QTextCursor currentCursor = ui->textEdit->textCursor();
    const auto& doc = ui->textEdit->document();
    int totalLIne = doc->lineCount();
    // 超出文本的行数则移动到最后一行
    line = std::min(line, totalLIne);
    int position = doc->findBlockByNumber(line - 1).position();
    currentCursor.setPosition(position);
    ui->textEdit->setTextCursor(currentCursor);
    // 跳转到指定行以后,关闭跳转窗口并将焦点这只在textEdit中
    pGoto->close();
    ui->textEdit->setFocus();
}

void MyNotepad::on_ZoomInAction_triggered() {
    // 限制到300%的最大缩放
    if (statusInfo.scale_ratio >= 300) {
        return;
    }
    statusInfo.scale_ratio += 10;
    ui->textEdit->zoomIn(1);
    updateStatusLabelScale();
}

void MyNotepad::on_ZoomOutAction_triggered() {
    // 限制到10%的最小缩放
    if (statusInfo.scale_ratio <= 10) {
        return;
    }
    statusInfo.scale_ratio -= 10;
    ui->textEdit->zoomOut(1);
    updateStatusLabelScale();
}

void MyNotepad::on_ZoomResetAction_triggered() {
    int zoomLevel = (statusInfo.scale_ratio - 100) / 10;
    // 根据当前的缩放反向缩放至默认缩放比例
    if (zoomLevel < 0) {
        ui->textEdit->zoomIn(-zoomLevel);
    } else {
        ui->textEdit->zoomOut(zoomLevel);
    }
    // 更新状态栏效果
    statusInfo.scale_ratio = 100;
    updateStatusLabelScale();
}

void MyNotepad::on_GotoAction_triggered() {
    qDebug() << "GotoAction_triggered";
    pGoto->show();
}

// 通过事件的方式检测键盘的触发
void MyNotepad::keyPressEvent(QKeyEvent* event) {
    qDebug() << "keyPressEvent" << event->key();
    if (event->modifiers() == Qt::ControlModifier) {
        switch (event->key()) {
            case Qt::Key_N:
                on_NewAction_triggered();
                break;
            case Qt::Key_O:
                on_OpenAction_triggered();
                break;
            case Qt::Key_S:
                on_SaveAction_triggered();
                break;
            case Qt::Key_F:
                on_FindAction_triggered();
                break;
            case Qt::Key_H:
                on_ReplaceAction_triggered();
                break;
            case Qt::Key_Equal:  // 直接按ctrl +
                on_ZoomInAction_triggered();
                break;
            case Qt::Key_Minus:
                on_ZoomOutAction_triggered();
                break;
            case Qt::Key_0:
                on_ZoomResetAction_triggered();
                break;
            case Qt::Key_G:
                on_GotoAction_triggered();
                break;
            default:
                break;
        }
    } else if (event->modifiers() == (Qt::ControlModifier | Qt::ShiftModifier)) {
        switch (event->key()) {
            case Qt::Key_Plus:  // 按下ctrl shift +
                on_ZoomInAction_triggered();
                break;
            case Qt::Key_S:
                on_SaveAsAction_triggered();
                break;
            default:
                break;
        }
    }
}

void MyNotepad::resizeEvent(QResizeEvent* event) { updateStatusLabelPlaceHolder(); }

void MyNotepad::closeEvent(QCloseEvent* event) {
    qDebug() << "closeEvent";
    checkSaved();
}

void MyNotepad::outputToFile(QString fileName, bool updCurrFile){
    QFile file(fileName);
    if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
        QMessageBox::information(this, "提示", "保存失败,文件可能被占用");
        return;
    }
    QTextStream out(&file);
    out << ui->textEdit->toPlainText();
    file.close();
    if (updCurrFile){
        currentFile = fileName;
    }
    isSaved = true;
    updateWindowsTitle();
}

void MyNotepad::checkSaved() {
    qDebug() << "checkSaved";
    if (!isSaved) {                 // 如果未保存,则进行保存
        QMessageBox::StandardButton reply;
        reply = QMessageBox::warning(this, "未保存的更改", "当前文件未保存,是否保存?",
                                     QMessageBox::Yes | QMessageBox::No);
        if (reply == QMessageBox::Yes) {
            on_SaveAction_triggered();
        }
    }
}

查找与替换类

查找替换类负责的功能比较简单,仅需要和UI交互以获取用户的操作,向主窗口发射信号传递UI交互的参数,由主函数实现操作的逻辑。其中,查找和替换发射的信号有两种,一种为查找,另一种为替换。查找的信号会携带查找内容和查找方向,替换信号会携带替换的源内容、目标内容和替换方式(一次替换或替换全部)。该类的源文件如下:
  SearchAndReplace.h:

#pragma once
#include <QDialog>

#include "ui_SearchAndReplace.h"

class SearchAndReplace : public QDialog {
    Q_OBJECT
public:
    enum class SearchDir { LAST, NEXT };    // 查找方向,上一个 或 下一个
    enum class ReplaceType { ONCE, ALL };   // 替换方式, 一次替换 或 替换全部
    SearchAndReplace(QWidget* parent = nullptr);
    ~SearchAndReplace();

private:
    Ui_SearchAndReplace* ui;

private slots:
    // 查找替换界面中按钮的槽函数
    void on_LastPushButton_clicked();
    void on_NextPushButton_clicked();
    void on_ReplaceButton_clicked();
    void on_ReplaceAllButton_clicked();

signals:
    /********************************************************************************
     * @brief 查找替换界面中查找按钮发出的信号
     * @param  str 查找的字符串
     * @param  dir 查找的方向,上一个 or 下一个
     ********************************************************************************/
    void searchInfo(QString str, SearchDir dir);

    /********************************************************************************
     * @brief 查找替换界面中替换按钮发出的信号*
     * @param  sStr 要替换的源字符串
     * @param  rStr 要替换的目的字符串
     * @param  type 替换方式,一次替换 or 全部替换
     ********************************************************************************/
    void replaceInfo(QString sStr, QString rStr, ReplaceType type);

protected:
    /********************************************************************************
     * @brief 检测到查找Dialog显示的时候,清空两个LineEdit
     * @param  event
     ********************************************************************************/
    void showEvent(QShowEvent* event);
};

SearchAndReplace.cpp:

#include "SearchAndReplace.h"

#include <QDebug>

SearchAndReplace::SearchAndReplace(QWidget* parent) : QDialog(parent), ui(new Ui_SearchAndReplace) {
    ui->setupUi(this);
}

SearchAndReplace::~SearchAndReplace() { delete ui; }

void SearchAndReplace::on_LastPushButton_clicked() {
    qDebug() << "on_LastPushButton_clicked!";
    emit searchInfo(ui->SearchLineEdit->text(), SearchDir::LAST);
}

void SearchAndReplace::on_NextPushButton_clicked() {
    qDebug() << "on_NextPushButton_clicked!";
    emit searchInfo(ui->SearchLineEdit->text(), SearchDir::NEXT);
}

void SearchAndReplace::on_ReplaceButton_clicked() {
    qDebug() << "on_ReplaceButton_clicked!";
    emit replaceInfo(ui->SearchLineEdit->text(), ui->ReplaceLineEdit->text(), ReplaceType::ONCE);
}

void SearchAndReplace::on_ReplaceAllButton_clicked() {
    qDebug() << "on_ReplaceAllButton_clicked!";
    emit replaceInfo(ui->SearchLineEdit->text(), ui->ReplaceLineEdit->text(), ReplaceType::ALL);
}

void SearchAndReplace::showEvent(QShowEvent* event) {
    qDebug() << "showEvent!";
    ui->SearchLineEdit->clear();
    ui->ReplaceLineEdit->clear();
}

转到类

转到类负责的功能也比较简单,通过转到的界面获取转到的行号并向主界面发送信号,主界面收到信号后,通过行号定位到相应的行,然后将光标定位到该行。该类的源文件如下:
  Goto.h:

#pragma once
#include <QDialog>

#include "ui_Goto.h"

class Goto : public QDialog {
    Q_OBJECT
public:
    Goto(QWidget* parent = nullptr);
    ~Goto();

private:
    Ui_Goto* ui;

private slots:
    // 转到界面中按钮的槽函数——转到和取消
    void on_GotoPushButton_clicked();
    void on_CancelPushButton_clicked();

signals:
    void gotoLine(int line);

protected:
    /********************************************************************************
     * @brief 检测到转到显示的时候,初始化相关参数
     * @param  event
     ********************************************************************************/
    void showEvent(QShowEvent* event);
};

Goto.h:

#include "Goto.h"
#include <QDebug>

Goto::Goto(QWidget *parent) : QDialog(parent), ui(new Ui::Goto) {
    ui->setupUi(this);
}

Goto::~Goto() {
    delete ui;
}

void Goto::on_GotoPushButton_clicked() {
    qDebug() << "GotoPushButton clicked";
    QString lineStr = ui->GotoLineEdit->text();
    bool toIntOk = false;
    int line = lineStr.toInt(&toIntOk);
    // 对输出进行校验,如果时大于0的整数,发送信号;否则,清空lineEdit重新输入
    if(toIntOk && line > 0){
        emit gotoLine(line);
    } else {
        ui->GotoLineEdit->clear();
        ui->GotoLineEdit->setFocus();
    }
}

void Goto::on_CancelPushButton_clicked(){
    qDebug() << "CancelPushButton clicked";
    close();
}

void Goto::showEvent(QShowEvent *event) {
    qDebug() << "showEvent";
    // 清空lineEdit,并设置Focus
    ui->GotoLineEdit->clear();
    ui->GotoLineEdit->setFocus();
}

主函数

主函数中创建MyNotepad类对象,并显示即可。
  main.cpp:

#include "MyNotepad.h"

#include <QApplication>
#pragma comment(lib, "user32.lib")

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MyNotepad w;
    w.show();
    return a.exec();
}

完整的工程代码

完整的工程代码可以在该仓库地址:https://gitee.com/SAquarius/mynotepad.git获取。

总结

通过使用Qt仿写一个Windows记事本程序,我熟悉了Qt中常用的几种组件的使用方法、Qt界面设计方法,能够相对熟练的使用Qt的信号和槽机制。另外,在代码编写的过程中,尝试先实现功能,然后再对代码进行重构,时刻思考如何降低代码的重复率。不过,自己实现的记事本功能相对比较简单,代码量不大,甚至当前应用程序还存再一些逻辑上不合理的情况或者Bug。但是,仿写记事本程序真的很适合作为学习Qt的一个入门项目。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值