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

#include

#include

#include

#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 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

#include

#include

#include

#include

#include

#include

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

#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

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

#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

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

#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的一个入门项目。