这就是我们的记事本
看完这篇博客可能还有许多不懂,不过别急,我后面还会更新博客来讲Qt。你可以先百度来解决自己的问题。
所有新增的代码都应该自己敲一遍。不懂的,可以先略过。C++要是不懂,先补补C++。
notepad.pro - 项目文件
main.cpp - 主文件
notepad.cpp - Notepad主界面的源码
notepad.h - Notepad主界面的头文件
notepad.ui - Notepad 的UI文件
#include "notepad.h"
#include
int main(int argc, char *argv[])
{
/**
* @brief a QApplication对象
* 管理Qt应用的资源,每个使用了Widget的应用都需要QApplication的对象
*/
QApplication a(argc, argv);
/**
* @brief w Notepad 的对象
* Notepad也是一个Widget
*/
Notepad w;
// 显示Notepad的界面
w.show();
// 进入应用的事件循环,当程序在运行时,事件(键盘点击、鼠标点击等很多事件)会发送到
// 本应用的所有Widget,比如在本应用中,事件就会发送给w。
return a.exec();
}
notepad.ui是定义用户界面的文件,实际上是一个XML文件。它包含了有什么控件、控件的位置、控件之间的所属关系等等。当构建(编译)项目时,编译程序会调用“Qt User Interface Compiler (uic)”来把notepad.ui编译为一个C++头文件ui_notepad.h;ui_notepad.h包含了有什么控件、控件的位置、控件之间的所属关系等等。
双击notepad.ui,会进入UI编辑器界面。截取出我们感兴趣的部分,如下,左右同种颜色的框一一对应
Notepad类继承QMainWindow,一个QMainWindow包含菜单栏(menuBar)、工具栏(mainToolBar)、状态栏(statsuBar)和centralWidget(可用来放入其他控件,中文不知如何翻译)。
添加Text Edit控件。
控件的周围会出现8个深蓝色小方块,把鼠标移动到这些方块上,并拖动,就可以改变控件的大小。界面如下图所示
点击运行,程序的界面如下
设计菜单项。双击UI中的“在这里输入”,在输入框中输入“文件”,敲回车。然后同理,再双击后面的“在这里输入”,输入“编辑”,
得到如下界面
点击“文件”或者“编辑”,会出现一个下拉菜单,点击下拉菜单中中的“在这里输入”,就可以输入子菜单项。“文件”应该包含的子菜单项为“新建”、“打开”、“保存”、“另存为”;“编辑”应当包含的子菜单项为“粘贴”、“重做”、“撤销”。添加子菜单的时候,我发现不能输入中文,目前的解决方案是在别的地方输入中文,然后复制粘贴进去。添加完子菜单之后,界面如下
红色框内就是控件之间的关系。Notepad包含centralWidget、menuBar、mainToolBar和statusBar;centralWidget包含一个textEdit;menuBar包含menu和menu_2;依次类推。双击红色框内控件的名字,可以更改控件的名字。控件名应该代表这个控件的作用,而不应该是action、action_2、action_3这样无意义的控件名。改完控件名之后,如下图所示
红色框内是Action Editor(我翻译为动作编辑器) ,每一行为一个Action,也就是一个子菜单项。双击一行中的任意一个位置,会打开一个Action的属性窗口如下
点击一下红色框内的输入框,按下组合键,输入框里面就会出现你刚才按下的组合键。这个组合键就是这个Action的快捷键。修改所有Action的组合键,修改完成后如下图所示;每完成一步都要按Ctrl+S保存!
此时在编译运行程序,菜单已经已经有了。但是,这时候点击菜单,是没有任何反应的。
下面就是notepad.h的定义,添加了详细的注释。在Qt中,按住Ctrl,然后再点击宏或者类名,就会转到宏或者类的定义;点击变量名的时候,会转到变量的定义;点击函数名的时候,会在函数定义(函数声明)和函数实现之间来回跳转。
#ifndef NOTEPAD_H
#define NOTEPAD_H
#include
namespace Ui {
class Notepad;//这个类是ui_notepad.h中的UI界面类,和下面的Notepad不是同一个类
}
class Notepad : public QMainWindow
{
/**
* 每个UI相关的类(或者其他继承自QObject的类)都需要有下面这一行。
* Q_OBJECT是一个宏,暂且不需要了解是什么。
*/
Q_OBJECT
public:
/**
* @brief Notepad 构造函数。
* 这个Notepad类是应用的功能实现,上面的Ui::Notepad是一个定义UI的类。
* @param parent 父类指针
*/
explicit Notepad(QWidget *parent = 0);
~Notepad();
private:
// 界面指针
Ui::Notepad *ui;
};
#endif // NOTEPAD_H
Qt的信号(signal)和槽(slot)机制简述 。Qt中代替回调函数的机制叫做信号槽,继承自QObject类(Qt库中,很多类都直接或者间接继承自QObject类),都可以有信号和槽。信号和槽都是一个函数,信号只有函数名,没有实现;槽是一个完整的函数,可执行某种功能。信号和槽的参数需要相同;一个信号可以通过connect函数和多个槽相连。当信号被触发时,槽函数会执行,所以,槽函数也可以理解为回调函数;有多个槽函数时,当信号被触发,会按照与信号相连的顺序顺序执行。
在Qt中的类中,定义信号和槽的关键字分别为signals和slots,具体示例如下
#ifndef NOTEPAD_H
#define NOTEPAD_H
#include
namespace Ui {
class Notepad;//这个类是ui_notepad.h中的UI界面类,和下面的Notepad不是同一个类
}
class Notepad : public QMainWindow
{
/**
* 每个UI相关的类(或者其他继承自QObject的类)都需要有下面这一行。
* Q_OBJECT是一个宏,暂且不需要了解是什么。
*/
Q_OBJECT
public:
/**
* @brief Notepad 构造函数。
* 这个Notepad类是应用的功能实现,上面的Ui::Notepad是一个定义UI的类。
* @param parent 父类指针
*/
explicit Notepad(QWidget *parent = 0);
~Notepad();
private:
// 界面指针
Ui::Notepad *ui;
signals: //信号
void opened();
public slots: //公有槽定义区,可关联其他类中信号
void onActionNewTriggered(bool checked); //新建
void onActionOpenTriggered(bool checked);//打开
void onActionSaveTriggered(bool checked);//保存
void onActionSaveasTriggered(bool checked);//另存为
void onActionPasteTriggered(bool checked);//粘贴
void onActionUndoTriggered(bool checked);//撤销
void onActionRedoTriggered(bool checked);//重做
private slots: //私有槽定义区,只能关联本类中的信号
void onEdited();
};
#endif // NOTEPAD_H
需要的注意的是,不管公由槽还是私有槽,都需要实现,否则编译通不过。
按住Ctrl键,鼠标左键点击“explicit Notepad(QWidget *parent = 0)”中的“Notepad”,就会跳转到notepad.cpp中“explicit Notepad(QWidget *parent = 0)”的实现,如下
#include "notepad.h"
#include "ui_notepad.h"
Notepad::Notepad(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::Notepad) //实例化ui
{
ui->setupUi(this);//把ui和本类关联起来,ui中的事件什么的就可以发送到本类
}
Notepad::~Notepad()
{
delete ui;
}
把信号和槽相连。信号和槽相连需要使用connect函数,connect有四个参数,第一个参数是sender的指针,第二个参数是sender的一个信号,第三个参数是receiver的指针,第四个参数是receiver的槽。
#include "notepad.h"
#include "ui_notepad.h"
Notepad::Notepad(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::Notepad) //实例化ui
{
ui->setupUi(this);//把ui和本类关联起来,ui中的事件什么的就可以发送到本类
// 当actionNew的信号triggered被触发时,也就是被点击时,槽onActionNewTriggered会被调用
this->connect(ui->actionNew, //sender
SIGNAL(triggered(bool)),//sender的信号
this,//receiver
SLOT(onActionNewTriggered(bool)));//receiver的槽
// 连接剩下的信号与槽
this->connect(ui->actionOpen,SIGNAL(triggered(bool)),this,SLOT(onActionOpenTriggered(bool)));
connect(ui->actionSave,SIGNAL(triggered(bool)),this,SLOT(onActionSaveTriggered(bool)));
connect(ui->actionSaveas,SIGNAL(triggered(bool)),this,SLOT(onActionSaveasTriggered(bool)));
connect(ui->actionPaste,SIGNAL(triggered(bool)),this,SLOT(onActionPasteTriggered(bool)));
connect(ui->actionRedo,SIGNAL(triggered(bool)),this,SLOT(onActionRedoTriggered(bool)));
connect(ui->actionUndo,SIGNAL(triggered(bool)),this,SLOT(onActionUndoTriggered(bool)));
}
Notepad::~Notepad()
{
delete ui;
}
完成上面的代码之后,菜单项发出的信号就能和Notepad类的槽关联起来了。当对应的菜单项被点击时,对应的槽就会被执行。但是槽函数并没有实现,所此时编译无法通过。
转到notepad.h中,左键单击一个槽函数的名字,比如“onActionNewTriggered”,然后Alt+Enter键,会弹出一个菜单,然后在菜单中选择"在notepad.cpp"中添加定义,此时,编辑器会跳转notepad.cpp中,并添加以下代码
void Notepad::onActionNewTriggered(bool checked)
{
}
在上面的槽中添加一行输出Debug信息的代码
void Notepad::onActionNewTriggered(bool checked)
{
//输出函数名和checked的值,qDebug()需要包含头文件QDebug
qDebug()<<__FUNCTION__<<":"<
此时,按住Ctrl键,左键单击“onActionNewTriggered”,就又会跳转到notepad.h中“onActionNewTriggered”的定义处。重复上面的步骤,把槽都实现,整个notepad.cpp如下
#include "notepad.h"
#include "ui_notepad.h"
#include
Notepad::Notepad(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::Notepad) //实例化ui
{
ui->setupUi(this);//把ui和本类关联起来,ui中的事件什么的就可以发送到本类
// 当actionNew的信号triggered被触发时,也就是被点击时,槽onActionNewTriggered会被调用
this->connect(ui->actionNew, //sender
SIGNAL(triggered(bool)),//sender的信号
this,//receiver
SLOT(onActionNewTriggered(bool)));//receiver的槽
// 连接剩下的信号与槽
this->connect(ui->actionOpen,SIGNAL(triggered(bool)),this,SLOT(onActionOpenTriggered(bool)));
connect(ui->actionSave,SIGNAL(triggered(bool)),this,SLOT(onActionSaveTriggered(bool)));
connect(ui->actionSaveas,SIGNAL(triggered(bool)),this,SLOT(onActionSaveasTriggered(bool)));
connect(ui->actionPaste,SIGNAL(triggered(bool)),this,SLOT(onActionPasteTriggered(bool)));
connect(ui->actionRedo,SIGNAL(triggered(bool)),this,SLOT(onActionRedoTriggered(bool)));
connect(ui->actionUndo,SIGNAL(triggered(bool)),this,SLOT(onActionUndoTriggered(bool)));
}
Notepad::~Notepad()
{
delete ui;
}
void Notepad::onActionNewTriggered(bool checked)
{
//输出函数名和checked的值
qDebug()<<__FUNCTION__<<":"<
此时再运行项目,点击菜单项,可以看到“应用程序输出”下面的框中就有对应的输出
接下来是实现函数的功能。
实现onActionSaveTriggered()。首先在Notepad类里定义一个QString类型的变量savePath,为保存路径
然后实现onActionSaveTriggered函数
void Notepad::onActionSaveTriggered(bool checked)
{
qDebug()<<"保存文件..."<savePath = file_path;
}
// 定义QFile文件
QFile txt(this->savePath);
// 打开文件
bool tag = txt.open(QIODevice::WriteOnly);
if(tag){
// 写入文件
txt.write(ui->textEdit->toPlainText().toStdString().data());
}else{
qDebug()<<"打开文件失败"<saved = true;
}
现在看上面的代码,你可能有很多不懂,不懂的部分,除了百度,还可以参考Qt的文档。比如QFileDialog的用法,就可以百度“QFileDialog 使用”,会有很多博客教你使用;有的博客写的不好,就多看几篇,Qt的资源相对还是比较多。除了百度以外,把光标移动到类名,比如“QFileDialog”上,然后按F1,就可以打开QFileDialog的帮助文档;把光标移动到“getSaveFileName”,再按F1,就可以跳转到getSaveFileName的帮助文档。上述函数每一行都搞清楚搞清楚之后再往下看。
接下来实现onActionNewTriggered()。要新建文件,要检查是否已经保存,若保存了,则直接清空编辑框,若没有保存,提示用户是否保存。若输入框,也就是textEdit里面的文本被编辑,则可以认为文件被编辑过;当textEdit的里面的文本被编辑时,会触发信号"textChanged",因此,可在Notepad中定义一个槽onEdited来接收文本变化的信息,并在Notepad的构造函数中关联
this->saved = true;
connect(ui->textEdit,SIGNAL(textChanged()),this,SLOT(onEdited()));
实现onEdited
void Notepad::onEdited()
{
qDebug()<<"文本被修改..."<
实现onActionNewTriggered
void Notepad::onActionNewTriggered(bool checked)
{
qDebug()<<"新建文件..."<saved){//若已经保存
//清空编辑框
ui->textEdit->clear();
this->saved = true;
}else{//若没有保存
// 询问是否保存
int tag = QMessageBox::question(NULL, "注意", "是否保存文件?",
QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
if(tag == QMessageBox::Yes){//点了“yes”
this->onActionSaveTriggered(false);//保存
}
ui->textEdit->clear();
this->saved = true;
}
}
我没有实现onActionOpenTriggered,这个就留给读者去实现,作为练习。剩余的其他函数我都实现了。notepad.h如下
#ifndef NOTEPAD_H
#define NOTEPAD_H
#include
#include
namespace Ui {
class Notepad;//这个类是ui_notepad.h中的UI界面类,和下面的Notepad不是同一个类
}
class Notepad : public QMainWindow
{
/**
* 每个UI相关的类(或者其他继承自QObject的类)都需要有下面这一行。
* Q_OBJECT是一个宏,暂且不需要了解是什么。
*/
Q_OBJECT
public:
/**
* @brief Notepad 构造函数。
* 这个Notepad类是应用的功能实现,上面的Ui::Notepad是一个定义UI的类。
* @param parent 父类指针
*/
explicit Notepad(QWidget *parent = 0);
~Notepad();
private:
// 界面指针
Ui::Notepad *ui;
// 定义保存路径
QString savePath;
// 是否保存了
bool saved;
signals: //信号
void opened();
public slots: //公有槽定义区,可关联其他类中信号
void onActionNewTriggered(bool checked); //新建
void onActionOpenTriggered(bool checked);//打开
void onActionSaveTriggered(bool checked);//保存
void onActionSaveasTriggered(bool checked);//另存为
void onActionPasteTriggered(bool checked);//粘贴
void onActionUndoTriggered(bool checked);//撤销
void onActionRedoTriggered(bool checked);//重做
private slots: //私有槽定义区,只能关联本类中的信号
void onEdited();
};
#endif // NOTEPAD_H
notepap.cpp如下
#include "notepad.h"
#include "ui_notepad.h"
#include
#include
#include
#include
Notepad::Notepad(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::Notepad) //实例化ui
{
ui->setupUi(this);//把ui和本类关联起来,ui中的事件什么的就可以发送到本类
// 当actionNew的信号triggered被触发时,也就是被点击时,槽onActionNewTriggered会被调用
this->connect(ui->actionNew, //sender
SIGNAL(triggered(bool)),//sender的信号
this,//receiver
SLOT(onActionNewTriggered(bool)));//receiver的槽
// 连接剩下的信号与槽
this->connect(ui->actionOpen,SIGNAL(triggered(bool)),this,SLOT(onActionOpenTriggered(bool)));
connect(ui->actionSave,SIGNAL(triggered(bool)),this,SLOT(onActionSaveTriggered(bool)));
connect(ui->actionSaveas,SIGNAL(triggered(bool)),this,SLOT(onActionSaveasTriggered(bool)));
connect(ui->actionPaste,SIGNAL(triggered(bool)),this,SLOT(onActionPasteTriggered(bool)));
connect(ui->actionRedo,SIGNAL(triggered(bool)),this,SLOT(onActionRedoTriggered(bool)));
connect(ui->actionUndo,SIGNAL(triggered(bool)),this,SLOT(onActionUndoTriggered(bool)));
this->saved = true;
connect(ui->textEdit,SIGNAL(textChanged()),this,SLOT(onEdited()));
}
Notepad::~Notepad()
{
delete ui;
}
void Notepad::onActionNewTriggered(bool checked)
{
qDebug()<<"新建文件..."<saved){//若已经保存
//清空编辑框
ui->textEdit->clear();
this->saved = true;
}else{//若没有保存
// 询问是否保存
int tag = QMessageBox::question(NULL, "注意", "是否保存文件?",
QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
if(tag == QMessageBox::Yes){//点了“yes”
this->onActionSaveTriggered(false);//保存
}
ui->textEdit->clear();
this->saved = true;
}
}
void Notepad::onActionOpenTriggered(bool checked)
{
qDebug()<<__FUNCTION__<<":"<savePath = file_path;
}
// 定义QFile文件
QFile txt(this->savePath);
// 打开文件
bool tag = txt.open(QIODevice::WriteOnly);
if(tag){
// 写入文件
txt.write(ui->textEdit->toPlainText().toStdString().data());
}else{
qDebug()<<"打开文件失败"<saved = true;
}
void Notepad::onActionSaveasTriggered(bool checked)
{
qDebug()<<"另存为..."<savePath.clear();
this->onActionSaveTriggered(checked);
}
void Notepad::onActionPasteTriggered(bool checked)
{
qDebug()<<"粘贴..."<textEdit->paste();
}
void Notepad::onActionUndoTriggered(bool checked)
{
qDebug()<<"撤销..."<textEdit->undo();
}
void Notepad::onActionRedoTriggered(bool checked)
{
qDebug()<<"重做..."<textEdit->redo();
}
void Notepad::onEdited()
{
qDebug()<<"文本被修改..."<