以下是本人学习笔记
一、创建项目
初学者基类可以选择QWidget,QWidget是QMainWindow和QDialog的基类
二、创建第一个Qt程序
讲解见代码注释
main.cpp
#include "mywidget.h"
#include <QApplication> // 包含一个应用程序类的头文件
int main(int argc, char *argv[])
{
// a:应用程序对象,Qt中,有且仅有一个应用程序对象
QApplication a(argc, argv);
// 窗口对象,mywidget父类:QWidget
myWidget w;
// 窗口对象默认不会显示,必须调用show方法显示窗口
w.show();
// 让应用程序对象进入消息循环机制(对象一直在捕获消息)
// 使代码阻塞在该行
return a.exec();
}
mywidget.h
#ifndef MYWIDGET_H
#define MYWIDGET_H
#include <QWidget> // 包含头文件QWidget窗口类
class myWidget : public QWidget
{
Q_OBJECT // Q_OBJECT宏,允许类中使用信号和槽的机制
public:
// 构造函数(parent默认值为0,默认值的设定在声明和构造中只能有一处有)
myWidget(QWidget *parent = 0);
~myWidget();
};
#endif // MYWIDGET_H
三、命名规范及快捷键
- 命名规范:类名使用驼峰法;函数名使用小驼峰命名法
- 快捷键:
整行移动:ctrl+shift+上/下
帮助文档:F1
自动对齐:ctrl+i
同名之间的.h和.cpp文件之间的切换:F4
四、QPushButton的创建
mywidget.cpp
#include "mywidget.h"
#include <QPushButton>
myWidget::myWidget(QWidget *parent)
: QWidget(parent)
{
// 创建一个按钮
QPushButton* btn1 = new QPushButton;
btn1->setParent(this);
btn1->setText("hello");
// 创建按钮2:更快捷,但是有弊端:会按照控件的大小创建窗口
QPushButton* btn2 = new QPushButton("hello again", this);
// 移动按钮2
btn2->move(100, 100);
// 所以可以reset窗口大小
resize(600, 400);
// 设置固定窗口大小
setFixedSize(600, 400);
// 设置窗口标题
setWindowTitle("this is a window");
}
myWidget::~myWidget()
{
}
- QPushButton的父类的父类是QWidge,如上所述,窗口对象默认不会弹出,因此需要调用方法使其出现
- show函数默认以顶层的方式弹出,即控件会单独有一个窗口
- 调用setParent函数设置控件的父亲,使得它父亲出现的时候,这个控件就出现
this:指向当前对象的指针 - 后创建的控件如果于先创建的控件重合会将其覆盖,因此需要设置每个控件的位置
五、对象树
在Qt中创建对象会提供一个Parent对象指针
- QObject是以对象树的形式组织起来的
在创建QObject对象时,可以提供一个其父对象,我们创建的这个QObject对象会自动添加到其父对象的Children()列表;
当父对象析构时,这个列表中的所有对象也会被析构;
当创建的对象在堆区时,如果指定的父亲是QObject派生下来的类或者QObject子类派生下来的类,可以不用管理释放的操作,将对象放入对象树中,一定程度上简化了内存回收的机制 - QWidget是能够在屏幕上显示一切组件的父类
- 局部对象的析构顺序与其创建顺序相反
- 在Qt中,尽量在构造时就指定parent对象,并且大胆在堆上创建
例:创建一个新类
MyPushButton.h
#ifndef MYPUSHBUTTON_H
#define MYPUSHBUTTON_H
#include <QPushButton>
class MyPushButton : public QPushButton
{
Q_OBJECT
public:
explicit MyPushButton(QPushButton *parent = 0);
~MyPushButton();
signals:
public slots:
};
#endif // MYPUSHBUTTON_H
MyPushButton.cpp
#include "mypushbutton.h"
#include <QDebug>
MyPushButton::MyPushButton(QPushButton *parent) : QPushButton(parent)
{
qDebug() << "call button class";
}
MyPushButton::~MyPushButton()
{
qDebug() << "~MyPushButton";
}
六、Qt中的坐标系
- 左上角为(0,0)
- x以右为正方向
- y以下为正方向
七、信号和槽
connect(信号的发送者, 发送的具体信号, 信号的接收者, 信号的处理(槽))
- 信号的发送者和接收者:一般为对象
- 发送的具体信号:函数的地址
- 槽函数(slot):信号处理函数,传入函数地址(官方文档中查找对象父类的相关函数)
信号槽的优点:松散耦合,信号发送端和信号接收端本身是没有关联的,通过connect函数连接,将两端耦合在一起
1.使用系统函数
mywidget.cpp
#include "mywidget.h"
#include <QPushButton>
#include "mypushbutton.h"
myWidget::myWidget(QWidget *parent)
: QWidget(parent)
{
// 创建一个按钮
QPushButton* btn1 = new QPushButton;
btn1->setParent(this);
btn1->setText("hello");
// 创建按钮2:更快捷,但是有弊端:会按照控件的大小创建窗口
QPushButton* btn2 = new QPushButton("hello again", this);
// 移动按钮2
btn2->move(100, 100);
btn2->resize(100, 100);
// 创建按钮3
MyPushButton* btn3 = new MyPushButton;
btn3->setText("hello a and a");
btn3->setParent(this);
btn3->move(200,200);
// 所以可以reset窗口大小
resize(600, 400);
// 设置固定窗口大小
setFixedSize(600, 400);
// 设置窗口标题
setWindowTitle("this is a window");
// 需求:点击按钮,关闭窗口
connect(btn1, &MyPushButton::clicked, this, &myWidget::close);
}
myWidget::~myWidget()
{
}
2.自定义的信号和槽
Student类
#ifndef STUDENT_H
#define STUDENT_H
#include <QObject>
class Student : public QObject
{
Q_OBJECT
public:
explicit Student(QObject *parent = nullptr);
signals:
public slots:
// 早期Qt版本,槽函数必须写到public slots,高级版本可以写到public或者全局下
// 返回值void,需要声明,也需要实现
// 可以有参数,可以发生重载
void treat();
};
#endif // STUDENT_H
#include "student.h"
#include <QDebug>
Student::Student(QObject *parent) : QObject(parent)
{
}
void Student::treat()
{
qDebug() << "treat teacher";
}
Teacher类
#ifndef TEACHER_H
#define TEACHER_H
#include <QObject>
class Teacher : public QObject
{
Q_OBJECT
public:
explicit Teacher(QObject *parent = nullptr);
signals:
// 自定义信号写到signals下
// 返回值是void,只需要声明,不需要实现
// 可以有参数,可以重载
void hungry();
public slots:
};
#endif // TEACHER_H
#include "teacher.h"
Teacher::Teacher(QObject *parent) : QObject(parent)
{
}
MyWidget
#ifndef MYWIDGET_H
#define MYWIDGET_H
#include <QWidget>
#include "teacher.h"
#include "student.h"
namespace Ui {
class MyWidget;
}
class MyWidget : public QWidget
{
Q_OBJECT
public:
explicit MyWidget(QWidget *parent = 0);
~MyWidget();
private:
Ui::MyWidget *ui;
Teacher* sir;
Student* son;
void classOver();
};
#endif // MYWIDGET_H
#include "mywidget.h"
#include "ui_mywidget.h"
// 模拟场景
// Teacher类 Student类
// 下课后,老师会触发一个信号,饿了,学生响应信号,请客吃饭
MyWidget::MyWidget(QWidget *parent) :
QWidget(parent),
ui(new Ui::MyWidget)
{
ui->setupUi(this);
// 创建一个老师对象
this->sir = new Teacher(this);
// 创建一个学生对象
this->son = new Student(this);
// connect
connect(sir, &Teacher::hungry, son, &Student::treat);
// 调用下课函数
classOver();
}
void MyWidget::classOver()
{
// 下课函数,触发老师饿了的信号
emit sir->hungry();
}
MyWidget::~MyWidget()
{
delete ui;
}
注意连接和触发信号的顺序!
触发自定义信号:emit 自定义信号
3.自定义的信号和槽发生重载的解决
mywidget.cpp
#include "mywidget.h"
#include "ui_mywidget.h"
// 模拟场景
// Teacher类 Student类
// 下课后,老师会触发一个信号,饿了,学生响应信号,请客吃饭
MyWidget::MyWidget(QWidget *parent) :
QWidget(parent),
ui(new Ui::MyWidget)
{
ui->setupUi(this);
// 创建一个老师对象
this->sir = new Teacher(this);
// 创建一个学生对象
this->son = new Student(this);
// connect
// connect(sir, &Teacher::hungry, son, &Student::treat);
// 指针指向地址
// 函数指针指向函数地址
void(Teacher:: *teacherSignal)(QString) = &Teacher::hungry;
void(Student:: *studentSlot)(QString) = &Student::treat;
// 连接带参数的信号和槽
connect(sir, teacherSignal, son, studentSlot);
// 调用下课函数
classOver();
}
void MyWidget::classOver()
{
// 下课函数,触发老师饿了的信号
// emit sir->hungry();
emit sir->hungry("cola");
}
MyWidget::~MyWidget()
{
delete ui;
}
如果函数有重载,在连接信号槽时就需要创建函数指针,寻找到指定函数,明确指向函数地址
4.信号连接信号
按钮点击信号->老师信号->学生请客
详见注释
mywidget.cpp
#include "mywidget.h"
#include "ui_mywidget.h"
#include <QPushButton>
// 模拟场景
// Teacher类 Student类
// 下课后,老师会触发一个信号,饿了,学生响应信号,请客吃饭
MyWidget::MyWidget(QWidget *parent) :
QWidget(parent),
ui(new Ui::MyWidget)
{
ui->setupUi(this);
// 创建一个老师对象
this->sir = new Teacher(this);
// 创建一个学生对象
this->son = new Student(this);
// 指针指向地址
// 函数指针指向函数地址:带参数的信号和槽
void(Teacher:: *teacherSignal1)(QString) = &Teacher::hungry;
void(Student:: *studentSlot1)(QString) = &Student::treat;
// no带参数的信号和槽
void(Teacher:: *teacherSignal2)(void) = &Teacher::hungry;
void(Student:: *studentSlot2)(void) = &Student::treat;
// 连接带参数的信号和槽
connect(sir, teacherSignal1, son, studentSlot1);
// 连接no参数的信号和槽
connect(sir, teacherSignal2, son, studentSlot2);
// press button -> class over
QPushButton* btn1 = new QPushButton("class over 1", this);
QPushButton* btn2 = new QPushButton("class over 2", this);
btn2->move(100, 0);
this->resize(600, 400);
// 信号连接槽
connect(btn1, &QPushButton::clicked, this, &MyWidget::classOver);
// 信号连接信号
connect(btn2, &QPushButton::clicked, sir, teacherSignal2);
// 断开信号
// disconnect(sir, teacherSignal1, son, studentSlot1);
disconnect(sir, teacherSignal2, son, studentSlot2);
}
void MyWidget::classOver()
{
// 下课函数,触发老师饿了的信号
emit sir->hungry();
emit sir->hungry("cola");
}
MyWidget::~MyWidget()
{
delete ui;
}
- 信号可以连接信号
- 一个信号可以连接多个槽函数
- 多个信号可以连接同一个槽函数
- 信号和槽函数的参数必须类型一一对应(相当于信号传参给槽函数)
- 信号的参数个数可以大于槽函数的参数,但是剩下的参数的类型需要一一对应(要求顺序)
八、Lambda表达式
qt版本5.4及之前需要在.pro文件中添加CONFIG += c++11才可以使用Lambda表达式(在文件末尾添加即可)
c++11中的Lambda表达式用于定义并创建匿名的函数对象
一个基础的Lambda表达式结构:
[](){}
函数声明
[=](){
btn->setText("btnn");
}
函数声明及调用
[=](){
btn->setText("btnn");
}()
解释:
- [ ]:函数对象参数:函数对象参数只能使用到那些到定义Lambda为止时Lambda所在作用范围内可见的局部变量(包括Lambda所在类的this),函数对象参数有以下形式:【ps:推荐使用=】
空: 没有使用任何函数对象参数
=: 函数体内可以使用上述范围中的所有参数,值传递
&: 函数体内可以使用上述范围中的所有参数,引用传递
this: 可以使用Lambda所在类中的成员变量
a: 只能使用a的值,函数体内不能修改传递进来的a的拷贝,因为默认情况下函数是const的。要修改传递进来的a的拷贝,可以添加mutable修饰符(但是无法修改参数本身)
&a: a引用传递
=,&a: 除了a引用传递,其余值传递
&,a: 除了a值传递,其余引用传递
- ():操作符重载函数参数
- ()和{}之间可以有 可修改标识符:mutable
- ()和{}之间可以有 函数返回值
int ret = []()->int{return 1000;}();
- {}:函数体
例:使用Lambda表达式 实现点击按钮 关闭窗口
// 信号连接信号(带参数)
connect(btn2, &QPushButton::clicked, sir, [=](){
emit sir->hungry("cola");
btn2->resize(120, 50);
});
使用lambda的好处:连接时,无参的信号不可以连接有参的槽函数/信号,使用Lambda表达式可以在函数体中触发有参信号
练习
设计一个按钮,点击时弹出新窗口,再次点击时新窗口关闭