1. 前言
最近学习了C++语法,心血来潮就想着搞一个项目出来,C++是之前学的,Qt才刚出炉,于是就整合了Qt框架编写了一个WIndows版的简易记事本,现在是个很小的文本编辑器,还有很大的扩展空间和改进。
本项目用到的语法不是很多但也比较全面了,比如Qt的信号与槽机制,ui界面布局等等。
实现的记事本项目运行后效果是这样的:
作者希望大家作为学习Qt框架的一个参考,共同努力,共同进步,不足之处,评论区请留言。
2. 部署Qt开发环境
2.1 下载Qt安装包
首先进入Qt官方网站:http://download.qt.io/archive/qt/,点击DownLoad下载Windows版本的Qt最新安装包,如果想下载以前的版本,也可以选择历史版本,本项目使用的是Qt5.12.10版本。
等待下载完成后,双击exe文件进入Qt安装程序,如果连网的话默认是在线安装,需要输入基本信息,如果断网的话可以进行离线安装,安装步骤请自行搜索,本文不予详解。
2.2 Qt Creator介绍
Qt Creator是一款跨平台的、官方推荐的集成开发环境(IDE),专为Qt开发者设计。从官网下载的Qt安装包中就包含了Qt Creator,不用再单独下载。
Qt Creator可用于开发Qt桌面应用程序和Qt控制台程序,还集成了跨平台构建工具:qmake和CMake。
Qt Creator还自带了一个界面设计器:Qt Designer,用于设计和构建图形用户界面(GUI)。
2.3 创建qmake项目
Qt框架和Qt Creator安装好了后,打开Qt Creator就可以进行创建项目了。
首先,选择顶部导航栏中的文件–新建文件或项目,进入如下界面:
左侧选择Application(Qt)模板,右侧选择Qt Widgets Application创建界面程序,输入项目的基本信息,名称自己定义,创建路径选择本地的存放目录::
接下来是选择构建工具为qmake,当然也可以选择CMake:
这里是创建基本窗口类,这里有三种基本窗口类:QMainWindow、QWidget、QDialog,我们这里是选择QMainWindow类,再自定义一个类来继承QMainWindow类:
这里的设置用于国际化信息,选择中文模板就可以了:
接下来是选择编译套件,建议MinGW和MSVC两个都选择上:
最后一步是选择版本控制系统为Git,用于将代码同步到Github。
3. 开始设计界面
3.1 主界面设计
主窗口界面设计在Forms/mainwindow.ui文件中,左边一栏是界面组件,中间的设计效果,右侧是具体的设计对象和类,使用时拖动左边的组件到中间视图中就可以看到效果了。
本记事本项目的文本输入部分使用了QPlainTextEdit类而没有使用QTextEdit,是因为QPlainTextEdit类更高效、性能更优,可以理解为它是简化版的QTextEdit,但QPlainTextEdit的功能比QTextEdit少,不过对于一个简单的记事本来说已经足够了。
3.2 查找与替换界面
查找和替换界面使用Forms/findAndReplaceDialog.ui文件来表示:
4. 实现记事本功能
4.1 文件操作功能
文件操作功能主要包括新建、打开、保存、另存为、打印、退出功能:
/**
* @brief MainWindow::openFileAction 打开文件槽
*/
void MainWindow::openFileAction(){
if(!isSaved()) return;
QString recentPath = settings.value("recent/filePath").toString();
QString filePath = QFileDialog::getOpenFileName(this,QString::fromLocal8Bit("打开"),recentPath,QString::fromLocal8Bit("文本文档(*.txt);;所有文件(*.*)"));
openFile(filePath);
}
/**打开文件API*/
void MainWindow::openFile(QString filePath){
filePath_ = filePath;
if(filePath.isEmpty()){
savedFileData = "";//新建文件数据为空
fileName_ = QString::fromLocal8Bit("无标题");
}else{
QFile file(filePath);
if(!file.exists()) return;
fileName_ = QFileInfo(filePath).baseName();
if(!file.open(QIODevice::ReadOnly|QIODevice::Text)){
QMessageBox::critical(this, "错误", "打开文件失败");
return;
}
savedFileData = QString::fromLocal8Bit(file.readAll());//获取打开的文件数据
file.close();
}
ui->plainTextEdit->setPlainText(savedFileData);
resetWindowTitle();//重置窗口标题
}
/**
* @brief MainWindow::newFileAction 新建
*/
void MainWindow::newFileAction(){
if(!isSaved()) return;
openFile("");
}
/**
* @brief MainWindow::saveFileAction 保存文件槽
*/
bool MainWindow::saveFileAction(){
if(ui->plainTextEdit->toPlainText().isEmpty())
return false;
//如果路径名为空,就另存为
if(filePath_.isEmpty()){
QString recentPath = settings.value("recent/filePath").toString();//返回最近打开的路径
QString filePath = QFileDialog::getSaveFileName(this,QString::fromLocal8Bit("另存为"),recentPath,QString::fromLocal8Bit("文本文档(*.txt);;所有文件(*.*)"));
settings.setValue("recent/filePath", filePath);
filePath_ = filePath;
fileName_ = QFileInfo(filePath).baseName();
}
QFile file(filePath_);
if(!file.open(QIODevice::WriteOnly)) return false;
QTextStream stream(&file);
stream.setCodec("GBK");
savedFileData = ui->plainTextEdit->toPlainText();//保存的文件数据
stream << savedFileData;
stream.flush();
file.close();
resetWindowTitle();
return true;
}
/**
* @brief MainWindow::saveAsAction 另存为
*/
void MainWindow::saveAsAction(){
QString temp = filePath_;
filePath_ = "";
if (!saveFileAction()) // 直接调用保存的
filePath_ = temp;
}
/**
* @brief MainWindow::exitAction 退出
*/
void MainWindow::exitAction(){
if(!isSaved()) return;
this->close();
}
4.2 文本编辑功能
文本编辑功能主要包含撤销、剪切、复制、粘贴、删除等操作。
/**
* @brief MainWindow::undoAction 撤销
*/
void MainWindow::undoAble(bool flag){
ui->actionUndo->setEnabled(flag);
}
/**
* @brief MainWindow::selectedUpdate 选择的内容更新
*/
void MainWindow::selectedUpdate(){
bool selected = ui->plainTextEdit->textCursor().hasSelection();
ui->actionCut->setEnabled(selected);
ui->actionCopy->setEnabled(selected);
ui->actionDelete->setEnabled(selected);
}
/**
* @brief MainWindow::undoAction 撤销
*/
void MainWindow::undoAction(){
ui->plainTextEdit->undo();
}
/**
* @brief MainWindow::cutAction 剪切
*/
void MainWindow::cutAction(){
ui->plainTextEdit->cut();
}
/**
* @brief MainWindow::copyAction 复制
*/
void MainWindow::copyAction(){
ui->plainTextEdit->copy();
}
/**
* @brief MainWindow::pasteAction 粘贴
*/
void MainWindow::pasteAction(){
ui->plainTextEdit->paste();
}
/**
* @brief MainWindow::deleteAction 删除
*/
void MainWindow::deleteAction(){
QTextCursor textCursor = ui->plainTextEdit->textCursor();
int pos = textCursor.position();
if (pos >= ui->plainTextEdit->toPlainText().length())
return ;
textCursor.setPosition(pos + 1, QTextCursor::MoveMode::KeepAnchor);
textCursor.removeSelectedText();
}
4.3 查找与替换功能
查找与替换文本有单独的界面,即查找与替换对话框,用于查找想要搜索的文本或替换:
/**
* @brief FindAndReplaceDialog::openFind 打开查找对话框
* @param isReplace
*/
void FindAndReplaceDialog::openFindOrReplaceDialog(bool isReplace){
//控制选项的显示,如果是查找则不显示下列该项
ui->label_2->setVisible(isReplace);
ui->replaceEdit->setVisible(isReplace);
ui->replaceBtn->setVisible(isReplace);
ui->replaceAllBtn->setVisible(isReplace);
ui->groupBox->setVisible(!isReplace);
QDialog::show();
ui->findEdit->setFocus();
ui->findEdit->selectAll();
this->adjustSize();
if(!isReplace)
setWindowTitle("查找");
else
setWindowTitle("替换");
}
/**
* @brief MainWindow::newFindDialog 新建查找对话框
*/
void MainWindow::newFindDialog(){
findAndReplaceDialog = new FindAndReplaceDialog(settings,this);
connect(findAndReplaceDialog, &FindAndReplaceDialog::signalShow, this, [=]{
ui->actionFindNext->setEnabled(true);
ui->actionFindPrev->setEnabled(true);
});
connect(findAndReplaceDialog, &FindAndReplaceDialog::signalClose, this, [=]{
ui->actionFindNext->setEnabled(false);
ui->actionFindPrev->setEnabled(false);
});
connect(findAndReplaceDialog, &FindAndReplaceDialog::signalFindNext, this, &MainWindow::findNextAction);
connect(findAndReplaceDialog, &FindAndReplaceDialog::signalFindPrev, this, &MainWindow::findPrevAction);
connect(findAndReplaceDialog, &FindAndReplaceDialog::signalReplceNext, this, [=]{
const QString& findText = findAndReplaceDialog->getFindTextData();
const QString& replaceText = findAndReplaceDialog->getReplaceTextData();
if (findText.isEmpty()) return ;
//设置选中的文本
const QString& selectedText = ui->plainTextEdit->textCursor().selectedText();
if((findAndReplaceDialog->isCaseSensitive() && selectedText != findText) || selectedText.toLower() != findText.toLower()){
//如果选中的词不是findText,查找下一个
findNextAction();
}else {
//已选中则替换选中的
QTextCursor tc = ui->plainTextEdit->textCursor();
tc.insertText(replaceText);
ui->plainTextEdit->setTextCursor(tc);
findNextAction();
}
});
connect(findAndReplaceDialog, &FindAndReplaceDialog::signalReplaceAll, this, [=]{
const QString& findText = findAndReplaceDialog->getFindTextData();
const QString& replaceText = findAndReplaceDialog->getReplaceTextData();
if (findText.isEmpty()) return;
QString content = ui->plainTextEdit->toPlainText();
QTextCursor textCursor = ui->plainTextEdit->textCursor();
textCursor.setPosition(0);
textCursor.setPosition(content.length(), QTextCursor::KeepAnchor);
content.replace(findText, replaceText);
textCursor.insertText(content);
});
}
/**
* @brief MainWindow::findNextAction 查找下一个槽
*/
void MainWindow::findNextAction(){
const QString& text = findAndReplaceDialog->getFindTextData();
if(text.isEmpty()) return;
QTextDocument::FindFlags findFlags;
if(findAndReplaceDialog->isCaseSensitive()) findFlags |= QTextDocument::FindCaseSensitively;
bool result = ui->plainTextEdit->find(text,findFlags);
if(!result && findAndReplaceDialog->isLoop() && ui->plainTextEdit->toPlainText().contains(text)){
QTextCursor tc = ui->plainTextEdit->textCursor();//从头开始查找
tc.setPosition(0);
ui->plainTextEdit->setTextCursor(tc);
findNextAction();
}
}
/**
* @brief MainWindow::findPrevAction 查找上一个
*/
void MainWindow::findPrevAction(){
const QString& text = findAndReplaceDialog->getFindTextData();
if(text.isEmpty()) return;
QTextDocument::FindFlags findFlags = QTextDocument::FindBackward;
if(findAndReplaceDialog->isCaseSensitive()) findFlags |= QTextDocument::FindCaseSensitively;
bool result = ui->plainTextEdit->find(text,findFlags);
if(!result && findAndReplaceDialog->isLoop() && ui->plainTextEdit->toPlainText().contains(text)){
QTextCursor tc = ui->plainTextEdit->textCursor();//从末尾开始查找
tc.setPosition(ui->plainTextEdit->toPlainText().length());//设置成末尾位置
ui->plainTextEdit->setTextCursor(tc);
findPrevAction();
}
}
/**
* @brief MainWindow::findAction 查找
*/
void MainWindow::findAction(){
if(!findAndReplaceDialog) newFindDialog();
findAndReplaceDialog->openFindOrReplaceDialog(false);
}
/**
* @brief MainWindow::findAction 替换
*/
void MainWindow::replaceAction(){
if(!findAndReplaceDialog) newFindDialog();
findAndReplaceDialog->openFindOrReplaceDialog(true);
}
除了基本的查找与替换外,还可以选择区分大小写、是否循环等条件
/**
* @brief FindAndReplaceDialog::caseSensitiveSlots 区分大小写
*/
void FindAndReplaceDialog::caseSensitiveSlots(){
settings.setValue("find/caseSensitive", ui->caseSensitiveCheck->isChecked());
}
/**
* @brief FindAndReplaceDialog::loopSlots 是否循环
*/
void FindAndReplaceDialog::loopSlots(){
settings.setValue("find/loop", ui->loopCheck->isChecked());
}
/**
* @brief FindAndReplaceDialog::upSlots 向上
*/
void FindAndReplaceDialog::upSlots(){
settings.setValue("find/down", false);
}
/**
* @brief FindAndReplaceDialog::downSlots 向下
*/
void FindAndReplaceDialog::downSlots(){
settings.setValue("find/down", true);
}
4.4 字体设置功能
Qt框架中有专门的字体设置类,导入QFontDialog类就可以实现该功能了。
/**
* @brief MainWindow::fontAction 字体格式
*/
void MainWindow::fontAction(){
bool flag;
QFont font = QFontDialog::getFont(&flag,ui->plainTextEdit->font(),this,QString::fromLocal8Bit("字体"));
if(!flag) return;
ui->plainTextEdit->setFont(font);
settings.setValue("font",font.toString());
}
4.5 打印功能
Qt框架中提供了QPrintDialog类和QPrinter类用于实现打印功能。
/**
* @brief MainWindow::printAction 打印,编译报错VS中缺少相应的库文件,在pro中添加QT+= printsupport选项即可
*/
void MainWindow::printAction(){
QPrinter printer;
QString printer_name = printer.printerName();
if(printer_name.size() == 0) return;
QPrintDialog printDialog(&printer,this);
if(ui->plainTextEdit->textCursor().hasSelection()){
printDialog.addEnabledOption(QAbstractPrintDialog::PrintSelection);
}
if(printDialog.exec() == QDialog::Accepted){
ui->plainTextEdit->print(&printer);
}
}
4.6 关于-帮助
打开帮助即可显示帮助文档和版本、作者等信息。
/**
* @brief MainWindow::helpAction 帮助
*/
void MainWindow::helpAction(){
QDesktopServices::openUrl(QUrl("https://www.peiqiblog.com/article/4800/"));
}
/**
* @brief MainWindow::aboutAction 关于
*/
void MainWindow::aboutAction(){
QMessageBox::about(this, QString::fromLocal8Bit("关于"), tr("qnotepad\n" "version1.0.0\n""simple notepad"));
}
5. 常见问题解决
5.1 编码问题
默认情况下,创建了新的Qt项目后,如果使用中文则界面中会显示乱码,这种情况下可以使用QString类的fromLocal8Bit方法实现编码配置:
/**
* @brief MainWindow::aboutAction 关于
*/
void MainWindow::aboutAction(){
QMessageBox::about(this, QString::fromLocal8Bit("关于"), tr("qnotepad\n" "version1.0.0\n""simple notepad"));
}
5.2 打印窗口不显示
调用QPrintDialog类实现打印窗口时不显示,只需要在pro配置文件中加入QT+= printsupport即可。
6. 测试运行
编写完代码后,右击项目运行就可以运行本记事本了。
主窗口:
查找窗口:
替换窗口:
打印窗口:
字体窗口:
关于窗口:
C++结合Qt框架实现的简易记事本已开源至Github
记事本项目代码链接:https://github.com/peiqi0818/qnotepad
若有不足之处,请评论区的朋友们指正。