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