目录
0. 前言
一、界面布局
二、文件操作功能的实现
2.1 思路整理
2.2 初始化界面功能的实现
2.3 判断当前文本是否可以"修改"的功能实现
2.4 新建文本功能的实现
2.5 保存功能的实现
2.6 另存为功能的实现
2.7 文件保存功能的实现
2.8 加载文件功能的实现
2.9 界面退出、文本打开、关闭、剪切、粘贴、复制、撤销功能的实现
2.10 信号与槽机制
2.11 误触关闭预防功能的实现
2.12 文本查找功能的实现
所有教程是给自己看,学的是思想、逻辑,操作不一定100%完整,见谅。
界面布局属于比较简单的内容,直接贴上结果图。
第一,用户打开界面总会存在一个初始界面。
第二,用户可以新建一个文本,但前提是当前文本内容为“空”或已保存。
第三,用户可以保存当前文本,无论是否文本内容为”空“,都可以保存。
第四,用户可以打开一个已经存在的文本,但前提是当前文本内容为”空“或已保存。
第五,用户可以关闭当前文本界面,不退出操作界面,但前提是当前文本内容为”空“或已保存。
第六,用户可以退出当前操作界面,但前提是当前文本内容为”空“或已保存。
第七,用户可以对当前文本内容进行复制、粘贴、撤销等操作。
第八,用户可以通过输入关键词在当前文本内查找有效信息。
第九,操作界面其他一些附件的设计。
综上所述,可以发现,只要是对文本进行任何”修改“类的工作,都需要确认当前文本的状态,故因此可以设计一个专门的函数处理这件事;此外,各个函数模块单独设计即可。
首先在当前.h文件的class中添加如下函数声明,权限为public:
void InitWindow(); //初始化界面
其次定义两个成员变量,权限为private:
bool isUntitled; //true表示文件没被保存过,false表示有
QString curFile; //当前文件的路径
最后,在当前.cpp文件中定义初始化界面函数:
void QtDemo01::InitWindow() {
isUntitled = true; //初始化文件为未保存状态
curFile = tr("未命名.txt"); //初始化文件名
setWindowTitle(curFile); //设置当前窗口的标题
ui.textEdit->clear();
ui.textEdit->setVisible(true); //使界面可见,可以修改为false看看效果
}
首先,在.h文件的class中添加函数声明,权限为public:
bool maybeSave(); //判断当前文本内容是否为"空"或已保存
最后,在.cpp文件中定义新建文本函数:
//判断当前文本内容是否需要保存
bool QtDemo01::maybeSave() {
if (ui.textEdit->document()->isModified()) { //文件初始状态是空白,若文件被更改了(增加了新内容),则考虑重新保存
QMessageBox box; //生成对话框对象
box.setWindowTitle("警告"); //设置对话框标题
box.setIcon(QMessageBox::Warning); //使用QMessageBox自带的警告图片作为对话框图片
box.setText(curFile + "尚未保存,是否保存?"); //设置对话框要显示的文本
/*
自定义按钮,其中按钮中的角色一般有有如下几种:
1. QMessageBox::AcceptRole ---> 单击按钮使对话框被接受
2. QMessageBox::RejectRole ---> 单击按钮使对话框被拒绝
3. QMessageBox::YesRole ---> 按钮类似于"是"的按钮
4. QMessageBox::NoRole ---> 按钮类似于"不"的按钮
注:按钮的顺序与程序编写顺序无关,与平台有关.
*/
QPushButton* yesBtn = box.addButton("是(&Y)"
,QMessageBox::YesRole); //生成文本内容为“是(&Y)”的Yes按钮是按钮
QPushButton* noBox = box.addButton("否(&N)", QMessageBox::NoRole);
QPushButton* cancelBox = box.addButton("取消", QMessageBox::RejectRole);
box.exec(); //循环等待用户下达指令
if (box.clickedButton() == yesBtn) { //当用户点击了yes按钮,转到保存函数内操作.addButton与clickedButton对应使用
return save();
}
else if (box.clickedButton() == noBox) { //当用户点击no按钮,则不保存文本界面
return true;
}
else if(box.clickedButton() == cancelBox) { //当用户点击cancel按钮,则取消当前操作
return false;
}
}
return true;
}
上述函数的目的是:若当前文本内容为空,则返回true,也就是告诉系统,可以创建一个新文本。若当前文本内容非空,则进入用户确认阶段。首先,利用QMessageBox类创建对话框,该对话框往往是用于提示、警告用户。用户通过QPushButton按钮类实现,按钮发送指令给系统。仅当用户选择 no,当前文本会被清空。若用户选择 yes,则会对当前文本保存 --> 进入Save()函数。
.h文件的class中添加,权限为public:
void newFile(); //新建文件
.cpp文件中添加函数定义:
void QtDemo01::newFile() {
if (maybeSave()) {
InitWindow();
}
}
上述函数的目的:首先判定当前文本内容是否为“空”或已保存,若用户选择 no 或 当前文本为“空”,则刷新界面,相当于新建了一个新文本。
同样地,权限为public:
bool save(); //保存操作
同样地,定义函数:
bool QtDemo01::save() {
if (isUntitled) { //文件没有保存过
return saveAs();
}
else {
return saveFile(curFile);
}
}
上述函数的目的:由于界面初始化作用,若第一个保存文件,则直接进入saveAs()函数;反之,若此时isUntitled == false,则表明当前文本已经被保存过。也就是说,不用重新选择保存路径了,因为正在操作的文本已经有了一个保存路径。没错,这个布尔类型变量isUntitled的目的就是判断是否需要选择保存路径!那么saveAs()与saveFile(const QString& filename)的区别就在于是否需要选择路径的问题。
权限public。
bool saveAs(); //另存为操作
函数定义:
bool QtDemo01::saveAs() {
/*
使用用户选择的文件名保存文件.在此处,表明保存文件名为curFile这个文件.
参数二:保存文件对话框的标题
参数三:用户选择的文件名
参数四(可选):保存类型
*/
QString filename = QFileDialog::getSaveFileName(this, tr("另存为"), curFile);
if (filename.isEmpty())
return false;
return saveFile(filename);
}
如2.5所述,我们选择分两步走。第一步:选择保存路径;第二步:保存文件。所以,关键点在与saveFile(const QString& filename)函数的编写!QFileDialog是文件对话框的类,无论是打开文件、保存文件都是使用这个类。
权限public:
bool saveFile(const QString& filename); //保存文件
函数定义:
//真正的文件保存操作
bool QtDemo01::saveFile(const QString& filename) {
QFile file(filename); //加载待操作文件,filename是待操作文件
/*
open()函数内的参数是文件的打开模式.总共有如下几种:
1. QFile::ReadOnly 只读
2. QFile::WriteOnly 只写
3. QFile::ReadWrite 可读可写
4. QFile::Append 追加模式,写入的数据会被追加到文件末尾
5. QFile::Truncate 重写模式,写入的数据将原有数据全部清除(此模式要与ReadOnly或WriteOnly搭配使用)
6. QFile::Text 读取文件时,将结尾结束符\r\n转化成\n.写入文件时,将结尾结束符转换成本地模式,如\n -> \r\n
*/
if (!file.open(QFile::WriteOnly | QFile::Text)) {
/*
warning对话框
参数二:对话框标题
参数三:对话框内容
参数四:(可选)对话框按钮
*/
QMessageBox::warning(this, tr("多文档编辑器"), tr("无法写入文件 %1: \n %2").arg(filename).arg(file.errorString()));
return false;
}
QTextStream out(&file); //构造一个指向file文件的QTextStream对象,输出数据流
QApplication::setOverrideCursor(Qt::WaitCursor); //鼠标指针变为等待状态
out << ui.textEdit->toPlainText(); //获取文本内容(toPlainTest()函数获取),并且传输给out文本流( << 运算符 )
QApplication::restoreOverrideCursor(); //鼠标指针恢复初始状态
isUntitled = false;
curFile = QFileInfo(filename).canonicalFilePath(); //获得文件标准路径
setWindowTitle(curFile);
return true;
}
如上述代码,QFile包含有关于文件操作的类,包括读\写文件,打开\关闭文件等等,通常与QTextStream一同使用。文件的打开方式也有多种,如上述代码注释所写。还记得QMessageBox吗?是的,它的作用就是 提示 或 警告 用户。QTextStream式处理文件流相关的类,能够对文件进行读取和写入。QApplication这个类比较宽泛。这里使用主要是针对正在保存文件时,希望鼠标达到“转圈圈”的效果;当读取完成(<<运算符表示将ui.textEdit->toPlainText()的内容输入到文件流out中)后,鼠标恢复正常。而之后,因为保存路径已经选择,所以isUntitled改变状态,当前文件操作界面的标题也随之改变。
声明,权限publilc:
bool loadFile(const QString& filename); //打开文件
定义:
bool QtDemo01::loadFile(const QString& filename) {
QFile file(filename);
if (!file.open(QFile::ReadOnly | QFile::Text)) {
QMessageBox::warning(this, tr("多文档编辑器"), tr("无法读取文件: %1 \n %2").arg(filename).arg(file.errorString()));
return false;
}
QTextStream in(&file); //构造一个指向file文件的QTextStream对象,输入数据流
QApplication::setOverrideCursor(Qt::WaitCursor);//鼠标保持旋转状态
ui.textEdit->setPlainText(in.readAll()); //将文件内容读取出来,并写入在ui编辑器中.
QApplication::restoreOverrideCursor();
curFile = QFileInfo(filename).canonicalFilePath();
setWindowTitle(curFile);
return true;
}
趁热打铁,看懂了2.7,那么这里难度就不大了,细节应该不用解释了,只是将输出文件流out改成了输入文件流in。
这里直接放代码,但是该代码具体放置在那个函数里,后面再讲
1、退出界面
ui.textEdit->setVisible(false);
//qApp指向所有应用程序的全局指针
qApp->quit();
2、打开文件
QString FileName = QFileDialog::getOpenFileName(this,tr("请选择需要打开的文件"));
if (!FileName.isEmpty()) {
loadFile(FileName);
ui.textEdit->setVisible(true);
}
3、关闭当前文本
ui.textEdit->setVisible(false);
4、撤销
ui.textEdit->undo();
5、剪切
ui.textEdit->cut();
6、复制
ui.textEdit->copy();
7、粘贴
ui.textEdit->paste();
可以看出,这都是ui.textEdit函数内置的函数。
Qt中特殊的通信方式:信号与槽。这里不具体介绍,信号与槽机制的关键在于槽函数的编写,而槽函数的名称可以自己定义,也可以按照Qt默认的方式定义。若自己定义,则需要自己写connect连接函数;若按照Qt默认的方式定义,则不需要写connect函数。如何选择呢?很简单,若Qt Creator中提供了发送者、信号、接收者,那我们就用默认的方式定义槽函数即可。
在本项目中,所有文件的操作都在Qt Creator中定义了,也就是说,Qt Creator中提供了发送者(各个action)、信号(triggered())、接收者(一般都是this)。
默认槽函数的定义方式如下:
void on_
举个栗子,若我想定义打开文件的槽函数,首先,找到ui界面中相关action的信息,如下图。
对象名是:action_New,信号是triggered()
那么定义的槽函数就是:
void on_action_New_triggered() {
……
}
举一反三,所有的槽函数如下,
还记得2.9所述的功能需要放在哪里吗?放在各个槽函数内即可。
void QtDemo01::on_action_Open_triggered() {
if (maybeSave()) {
QString FileName = QFileDialog::getOpenFileName(this,tr("请选择需要打开的文件"));
if (!FileName.isEmpty()) {
loadFile(FileName);
ui.textEdit->setVisible(true);
}
}
}
void QtDemo01::on_action_Exit_triggered() {
on_action_Close_triggered();
//qApp指向所有应用程序的全局指针
qApp->quit();
}
void QtDemo01::on_action_Close_triggered() {
if (maybeSave()) {
ui.textEdit->setVisible(false);
}
}
void QtDemo01::on_action_Revoke_triggered() {
if (maybeSave()) {
ui.textEdit->undo();
}
}
void QtDemo01::on_action_Cut_triggered() {
if (maybeSave()) {
ui.textEdit->cut();
}
}
void QtDemo01::on_action_Copy_triggered() {
if (maybeSave()) {
ui.textEdit->copy();
}
}
void QtDemo01::on_action_Paste_triggered() {
if (maybeSave()) {
ui.textEdit->paste();
}
}
在正常使用软件期间,若用户误触软件界面的关闭按钮,且当前文本中有尚未保存的内容时,会直接损失数据。为了解决这种情况,需要对关闭事件的函数重新改写。
在.h文件的class类中声明关闭事件的函数。
protected:
void closeEvent(QCloseEvent* event);
在.cpp文件中重写函数:
/*
该函数目的是:
当鼠标点击主界面右上角 X 时,文件没来得及保存.
为了解决这种现象,重写CloseEvent函数,确保在maybeSave()返回true时才会直接关闭,否则询问后再关闭.
*/
void QtDemo01::closeEvent(QCloseEvent* event) {
if (maybeSave()) {
event->accept();
}
else {
event->ignore();
}
}
QCloseEvent类是关闭事件的类,至于事件,本人也是刚刚开始学习,所以此处见解较浅。函数的目的是:先检查当前文本是否为“空”或已保存,再执行关闭程序。
注意,这里的函数名closeEvent,不能修改一个,因为它是重写了,并不是重新定义!。
.h内需要做三件事,第一件事是类的前置声明(置于头文件下)、第二件事是创建两个对象(权限为private)、第三件事是声明两个函数(函数权限为public)。
//类的前置声明
class QLineEdit;
class QDialog;
QLineEdit* findLineEdit;
QDialog* findDlg;
第一个参数是单行输入框的类对象声明。
第二个参数是查找对话框的对象声明。
void on_action_Find_triggered();
void showFindText();
第一个函数是默认槽函数,目的是按下查找action_Find,弹出文件查找框。
第二个函数是文件查找框的各项功能函数。
.cpp文件中首先在构造函数内通过代码的方式写好查找对话框的界面
this->findDlg = new QDialog(this); //创建一个对话框
this->findDlg->setWindowTitle(tr("查找")); //设定该对话框标题是“查找”
this->findLineEdit = new QLineEdit(this->findDlg); //在findDlg对话框中嵌入单行输入框
QPushButton* btn = new QPushButton(tr("查找下一个"), this->findDlg); //在findDlg对话框中嵌入一个按钮,该按钮名称是"查找下一个"
QVBoxLayout* layout = new QVBoxLayout(this->findDlg); //对话框设置为垂直布局(如果在ui中设计多次,这应该很好理解)
layout->addWidget(this->findLineEdit); //在layout中加入单行输入框
layout->addWidget(btn); //在layout中加入按钮Btn
QObject::connect(btn, SIGNAL(clicked()), this, SLOT(showFindText())); //信号与槽机制,当btn按下,发送信号clicked().QtDemo01Class接受信号,发动槽函数showFindText()
//普通标签(可能会被覆盖)
QLabel* statusLabel = new QLabel;
statusLabel->setMinimumSize(150, 20); //设置Label框的最小宽、最小高
statusLabel->setFrameShape(QFrame::Box); //设置Label框的形状
statusLabel->setFrameShadow(QFrame::Sunken); //设置Label框的阴影
statusLabel->setText("欢迎使用文本操作界面"); //设置Label框的文字内容
ui.statusBar->addWidget(statusLabel); //在ui界面的状态栏(状态栏在ui界面的最下端)加入一个Label(加入顺序从左至右)
//永久标签
QDate* nowDate = new QDate;
QLabel* permenentLabel = new QLabel;
permenentLabel->setMinimumSize(100, 20);
permenentLabel->setFrameShape(QFrame::Box);
permenentLabel->setFrameShadow(QFrame::Sunken);
permenentLabel->setText(tr("今天是: %1 ").
arg(nowDate->currentDate().toString("yyyy-MM-dd")));
ui.statusBar->addPermanentWidget(permenentLabel); //永久对话框始终在最右端
接下来定义两个函数
void QtDemo01::on_action_Find_triggered() {
findDlg->setVisible(true);
}
需要查找对话框出现,只需要设置为可见即可。
void QtDemo01::showFindText() {
QString str = findLineEdit->text(); //获取要查找的字符串
/*
textEdit中内置了查找函数.
参数一: 查找的字符串
参数二:查找的方式,常用的有以下几种,若不指定,则默认向前查找
1. QTextDocument::FindBackward ---> 向后查找
2. QTextDocument::FindCaseSensitively ---> 区分大小写
3. QTextDocument::FindWholeWords ---> 全词匹配
*/
if (ui.textEdit->find(str, QTextDocument::FindBackward)) {
QPalette palette = ui.textEdit->palette();
palette.setColor(QPalette::Highlight, palette.color(QPalette::Active, QPalette::Highlight));
ui.textEdit->setPalette(palette);
}
else {
QMessageBox::warning(this, tr("查找"), tr("查找不到您输入的信息 %1 !").arg(str));
}
}
if语句内是查找后,将查找到的内容高亮显示。QPalette是调色板,高亮的本质就是改变字体背景颜色即可。
else语句内是什么?是的,还是QMessageBox类,就是 提示 和 警告 用户。若整篇阅读下来或自己敲下来,对这个类将会非常熟悉。