一、介绍
1.1 介绍
QT是一套应用程序开发库,但与MFC不同,QT是跨平台的开发类库。
跨平台意味着只需要编写一次程序,在不同平台无需改动或只需少许改动编译,就可以形成在不同平台上运行的版本。
1.2 优点
- 跨平台
- 接口简单,容易上手
- 一定程度上简化了内存回收
二、注意事项
2.1名称跟创建的路径不能含中文!!!
2.2如果不编译,会显示红色
2.3 窗口类
默认创建有窗口类myWidget
,基类有三种选择:QWidget
(类似于空窗口),QMainWindow
以及QDialog
三个
QWidget
是所有用户界面元素的基类,窗口和控件都是直接或间接继承自 QWidget
,QMainWindow
、QWidget
、QDialog
三个类就是用来创建窗口的,可以直接使用也可以继承后再使用。
QMainWindow
窗口可以包含菜单栏、工具栏、状态栏、标题栏等,是最常见的窗口形式,可以作为GUI程序的主窗口。
QDialog
是对话框窗口的基类。对话框主要用来执行短期任务,或与用户进行互动,它可以是模态的也可以是非模态的。QDialog
没有菜单栏、工具栏、状态栏等。
三、创建Qt项目
3.1 pro文件
.pro就是工程文件(project),它是qmake自动生成的用于生产makefile的配置文件。
3.2 一个简单的Qt程序
程序介绍:
#include "widget.h"
#include <QApplication> //包含一个应用程序类的头文件
//main程序入口 argc命令行变量的数量 argv命令行变量的数组
int main(int argc, char *argv[])
{
//a应用程序对象,在Qt中,应用程序对象,有且仅有一个
QApplication a(argc, argv);
//窗口对象 Widget父类 -> Qwidget
Widget w;
//窗口对象 默认不会显示,必须要调用show方法显示窗口
w.show();
//让应用程序对象进入消息循环
//让代码阻塞到这行
return a.exec();
}
3.3 .pro文件解释
QT += core gui Qt包含的模块
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets //大于4版本以上 包含 widget模块
TARGET = 01_FirstProject //目标 生成的.exe程序的名称
TEMPLATE = app //模板 应用程序模板 Application
SOURCES += main.cpp\ //源文件
mywidget.cpp
HEADERS += mywidget.h //头文件
3.4 weidget.h 头文件
#ifndef WIDGET_H //防止重复包含(防止重命名,防止头文件重复编译)
#define WIDGET_H
#include <QWidget> //包含一个头文件 QWidget 窗口类
class Widget : public QWidget
{
Q_OBJECT //Q_OBJECT宏,允许类中使用信号和槽的机制
public:
Widget(QWidget *parent = nullptr); //有参构造函数,默认值
~Widget(); //析构函数
};
#endif // WIDGET_H
3.5 命名规范
类名
首字母大写,单词和单词之间首字母大写
函数名和变量名
首字母可以小写,单词和单词之间首字母大写
3.6 快捷键
注释
ctrl + /
运行
ctrl + r
编译
ctrl + b
字体缩放
ctrl + 鼠标滚轮
查找功能
ctrl + f
整行移动
ctrl + shift + ↑ 或者 ↓
帮助文档
F1
自动对齐
ctrl + i
同名之间的.h和.cpp切换
F4
3.7 main.cpp
#include "widget.h"
#include <QApplication> //包含一个应用程序类的头文件
//main程序入口 argc命令行变量的数量 argv命令行变量的数组
int main(int argc, char *argv[])
{
//a应用程序对象,在Qt中,应用程序对象,有且仅有一个
QApplication a(argc, argv);
//窗口对象 Widget父类 -> Qwidget
Widget w;
//窗口对象 默认不会显示,必须要调用show方法显示窗口
w.show();
//让应用程序对象进入消息循环
//让代码阻塞到这行
return a.exec();
}
3.8 按钮控件常用API
示范如何使用帮助文档:
QPushButton
头文件:要包含 #include<QpushButton>
qmake :所属的模块widgets
Inherits :继承的父类QAbstractButton
Inherited By:子类QCommandLinkButton
再点击QabstractButton
会发现又继承QWidget
3.9 widget.cpp
#include "widget.h"
#include <QPushButton>
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
//①、创建一个按钮
QPushButton * btn = new QPushButton;
//btn->show(); //show以顶层方式弹出窗口控件
//让btn对象,依赖在widget 窗口中
btn->setParent(this);
//显示文本
btn->setText("第一个按钮");
//②、创建第二个按钮 按照控件的大小创建窗口
QPushButton * btn2 = new QPushButton("第二个按钮",this);
//移动btn2按钮
btn->move(100,100);
//重置窗口大小
resize(600,400);
//设置固定窗口大小
setFixedSize(600,400);
//设置窗口标题
setWindowTitle("第一个窗口");
}
Widget::~Widget()
{
}
常用的:
-
创建 QPushButton * btn = new QPushButton
-
设置父亲 setParent(this)
-
设置文本 setText(“文字”)
-
设置位置 move(宽,高)
-
重新指定窗口大小 resize
-
设置窗口标题 setWindowTitle
-
设置窗口固定大小 setFixedSize
3.10 对象模型(对象树)
- 当创建的对象在堆区时候,如果指定的父亲是QObject派生下来的类或者QObject子类派生下来的类,可以不用管理释放的操作,将对象会放入到对象树中。
- 一定程度上简化了内存回收机制
3.11 Qt窗口坐标系
坐标体系:
以左上角为原点(0,0),X向右增加,Y向下增加。
四、信号和槽的机制
4.1 连接函数 connect
connect(信号的发送者, 发送的具体信号,信号的接受者,信号的处理(槽))
注:如果信号的接受者是 this ,那么可以省略
信号槽的优点,松散耦合,信号发送端和接收端本身是没有关联的,通过connect连接,将两端耦合在一起
4.2 参数
- 参数1 信号的发送者
- 参数2 发送的信号(函数的地址)
- 参数3 信号的接受者
- 参数4 处理的槽函数
4.3 松散耦合
4.4 实现 点击按钮 关闭窗口的案例
connect(myBtn,&QPushButton::clicked,this,&QWidget::close);
五、自定义信号和槽
5.1 自定义信号
- 返回void
- 需要声明,不需要实现
- 可以有参数
- 写到signals 下
5.2 自定义槽函数
- 返回void
- 需要声明,也需要实现
- 可以有参数,可以重载
- 写到 public slot 下或者 public 或者 全局函数
5.3 触发自定义的信号
emit 自定义信号
5.4 案例
下课后,老师触发了信号,学生响应信号,请客吃饭
student.h
#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
teacher.h
#ifndef TEACHER_H
#define TEACHER_H
#include <QObject>
class Teacher : public QObject
{
Q_OBJECT
public:
explicit Teacher(QObject *parent = nullptr);
signals:
//自定义信号 写到signal下
//返回值是void 只需要声明,不需要实现
//可以有参数 ,可以重载
void hungry();
};
#endif // TEACHER_H
widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include "student.h"
#include "teacher.h"
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private:
Ui::Widget *ui;
Teacher * zt;
Student * st;
void classIsOver();
};
#endif // WIDGET_H
main.cpp
#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
student.cpp
#include "student.h"
#include <QDebug>
Student::Student(QObject *parent) : QObject(parent)
{
}
void Student::treat()
{
qDebug()<<"请老师吃饭";
}
teacher.cpp
#include "teacher.h"
Teacher::Teacher(QObject *parent) : QObject(parent)
{
}
widget.cpp
#include "widget.h"
#include "ui_widget.h"
//Teacher 类 老师类
//Student 类 学生类
//下课后 ,老师会触发一个信号,饿了,学生响应信号,请客吃饭
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
//创建一个老师对象 放到对象树中new Teacher(this) 就不用管他的释放了
this->zt = new Teacher(this);
//创建一个学生对象
this->st = new Student(this);
//老师饿了 学生请客的链接
connect(zt,&Teacher::hungry,st,&Student::treat);
//调用下课 函数
classIsOver();
}
void Widget::classIsOver()
{
//下课函数 调用后 触发老师饿了的信号
//触发 emit 关键字
emit zt ->hungry();
}
Widget::~Widget()
{
delete ui;
}
六、 当自定义信号和槽发生重载时
6.1 解决函数重载的问题
利用函数指针 明确指向函数的地址:
void( Teacher:: * tSignal )( QString ) = &Teacher::hungry;
6.2 解决输出会带 “ ”的问题
QString 转 char *
- .ToUtf8() 转为 QByteArray
- .Data() 转为 char *
6.3 点击一个 下课的按钮,再触发下课 信号连接槽
#include "widget.h"
#include "ui_widget.h"
#include <QPushButton>
//Teacher 类 老师类
//Student 类 学生类
//下课后 ,老师会触发一个信号,饿了,学生响应信号,请客吃饭
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
//创建一个老师对象 放到对象树中new Teacher(this) 就不用管他的释放了
this->zt = new Teacher(this);
//创建一个学生对象
this->st = new Student(this);
// //老师饿了 学生请客的链接
// connect(zt,&Teacher::hungry,st,&Student::treat);
// //调用下课 函数
// classIsOver();
//连接带参数的 信号和槽 信号发生重载了 要这样写
//指针 -> 地址
//函数指针 -> 函数地址 函数注意点:1.括号不可少 2.类型要匹配
void(Teacher::*teacherSignal)(QString) = &Teacher::hungry;
void(Student::*studentSlot)(QString) = &Student::treat;
connect(zt,teacherSignal,st,studentSlot);
classIsOver();
//点击一个 下课的按钮,再触发下课 信号连接槽
QPushButton * btn = new QPushButton("下课",this);
//重置窗口的大小
this->resize(600,400);
//点击按钮 触发下课
connect(btn,&QPushButton::clicked,this,&Widget::classIsOver);
}
void Widget::classIsOver()
{
//下课函数 调用后 触发老师饿了的信号
//触发 emit 关键字
//emit zt ->hungry();
emit zt->hungry("宫保鸡丁");
}
Widget::~Widget()
{
delete ui;
}
6.4 信号可以连接信号
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
//创建一个老师对象 放到对象树中new Teacher(this) 就不用管他的释放了
this->zt = new Teacher(this);
//创建一个学生对象
this->st = new Student(this);
//无参信号和槽连接
void(Teacher::*teacherSignal2)(void) = &Teacher::hungry;
void(Student::*studentSlot2)(void) = &Student::treat;
connect(zt,teacherSignal2,st,studentSlot2);
//信号连接信号
connect(btn,&QPushButton::clicked,zt,teacherSignal2);
//断开信号 断开哪个复制哪个
disconnect(zt,teacherSignal2,st,studentSlot2);
}
void Widget::classIsOver()
{
//下课函数 调用后 触发老师饿了的信号
//触发 emit 关键字
//emit zt ->hungry();
emit zt->hungry("宫保鸡丁");
}
6.5 断开信号 disconnect
//断开信号 断开哪个复制哪个
disconnect(zt,teacherSignal2,st,studentSlot2);
七、拓展
1.信号可以连接信号
2.一个信号可以连接多个槽函数
3.多个信号 可以连接 同一个槽函数
4.信号和槽函数的参数 必须类型一一的对应
5.信号和槽的参数个数 是不是要一致?信号的个数 可以多于槽函数的参数个数
比如void(Teacher::*teacherSignal)(QString) = &Teacher::hungry;
connect(btn,&QPushButton::clicked,zt,teacherSignal); 就不行,因为clicked参数是bool类型,后面是QString
6.Qt4版本以前的信号和槽连接方式
利用Qt4信号槽 连接无参数
connect(zt,SIGNAL(hungry()),st,SLOT(treat()));
Qt4版本优点,参数直观;缺点,类型不做检测 比如connect(zt,SIGNAL(hungry()),st,SLOT(treat(QString))); 这样错的,但是不报错
//emit zt ->hungry();
emit zt->hungry(“宫保鸡丁”);
}
## 6.5 断开信号 disconnect
```cpp
//断开信号 断开哪个复制哪个
disconnect(zt,teacherSignal2,st,studentSlot2);
八、Lambda表达式
8.1 Lambda介绍
C++11中的Lambda表达式**用于定义并创建匿名的函数对象**,以简化编程工作。
首先看一下Lambda表达式的基本构成:
[capture] (parameters) mutable ->return-type
{
statement
}
[函数对象参数] (操作符重载函数参数) mutable ->返回值{函数体}
①、函数对象参数:
-
[] ,标识一个Lambda的开始,这部分必须存在,不能省略。函数对象参数是传递给编译器自动生成的函数对象类的构造函数的。函数对象参数只能使用那些到定义Lambda为止时Lambda所在作用范围内可见的局部变量(包括Lambda所在类的this)。函数对象参数有以下形式:
空。没有使用任何函数对象参数。 -
=。函数体内可以使用Lambda所在作用范围内所有可见的局部变量(包括Lambda所在类的this),并且是值传递方式(相当于编译器自动为我们按值传递了所有局部变量)。
-
&。函数体内可以使用Lambda所在作用范围内所有可见的局部变量(包括Lambda所在类的this),并且是引用传递方式(相当于编译器自动为我们按引用传递了所有局部变量)。
-
this。函数体内可以使用Lambda所在类的成员变量。
-
a。将a按值进行传递。按值进行传递时,函数体内不能修改传递进来的a的拷贝,因为默认情况下函数是const的。要修改传递进来的a的拷贝,可以添加mutable修饰符。
例如以下这个就是错误,因为没有传递btn2
QPushButton * btn2 = new QPushButton; [btn](){ btn->setText("aaa"); btn2->setText("bbb"); // btn2看不到 }();
-
&a。将a按引用进行传递。
-
a, &b。将a按值进行传递,b按引用进行传递。
-
=,&a, &b。除a和b按引用进行传递外,其他参数都按值进行传递。
-
&, a, b。除a和b按值进行传递外,其他参数都按引用进行传递。
②、操作符重载函数参数:
标识重载的()操作符的参数,没有参数时,这部分可以省略。参数可以通过按值(如:(a,b))和按引用(如:(&a,&b))两种方式进行传递。
③、可修改标识符:
mutable声明,这部分可以省略。按值传递函数对象参数时,加上mutable修饰符后,可以修改按值传递进来的拷贝(注意是能修改拷贝,而不是值本身)。
例如:[m] ()mutable { m = 100 + 10; qDebug() << m; }
不加mutable不可以修改
//mutable关键字 用于修饰值传递的遍历,修改的是拷贝,而不是本体
QPushButton * myBtn = new QPushButton (this);
QPushButton * myBtn2 = new QPushButton (this);
myBtn2->move(100,100);
int m = 10;
connect(myBtn,&QPushButton::clicked,this,[m] ()mutable { m = 100 + 10; qDebug() << m; });
connect(myBtn2,&QPushButton::clicked,this,[=] () { qDebug() << m; });
qDebug() << m;
④、函数返回值:
->返回值类型,标识函数返回值的类型,当返回值为void,或者函数体中只有一处return的地方(此时编译器可以自动推断出返回值类型)时,这部分可以省略。
例如:
int ret = []()->int{return 10;}();
qDebug()<<"ret = "<<ret;
输出ret = 10
⑤、是函数体:
{},标识函数的实现,这部分不能省略,但函数体可以为空。
使用注意事项:
如果Lambda表达式后面不加()只是一个函数的声明,加了才是函数的调用。
/*
[=](){
btn->setText("aaa");
} ;这样不行,这样只是一个函数声明,还要函数的调用,再最后加一个小括号
*/
[=](){
btn->setText("aaa");
}();
8.2 案例
//利用Lambda表达式 实现点击按钮 关闭窗口
QPushButton * btn2 = new QPushButton;
btn2->setText("关闭");
btn2->move(100,0);
btn2->setParent(this);
connect(btn2,&QPushButton::clicked,this,[=](){
this->close();
emit zt->hungry("宫保鸡丁");
});
九、练手
9.1 练手一
点击open打开第二个窗口,点击close关闭第二个窗口
#include "mywidget.h"
#include "ui_mywidget.h"
#include <QPushButton>
#include <QDebug>
#include "secondwidget.h"
myWidget::myWidget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::myWidget)
{
ui->setupUi(this);
//open按钮,放到对象树中
QPushButton * btn = new QPushButton(this);
btn->setText("open");
//close按钮,放到对象树中
QPushButton * btn2 = new QPushButton(this);
btn2->setText("close");
btn2->move(200,200);
//此处不能放到对象树中
QWidget * second = new QWidget;
connect(btn,&QPushButton::clicked,this,[=]{
qDebug()<<"第二个画面出现";
second->show();
});
connect(btn2,&QPushButton::clicked,second,&QWidget::close);
}
myWidget::~myWidget()
{
delete ui;
}
9.2 练手二
点击open打开第二个窗口,按钮显示close,点击close关闭第二个窗口,按钮变open
#include "mywidget.h"
#include "ui_mywidget.h"
#include <QPushButton>
#include <QDebug>
myWidget::myWidget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::myWidget)
{
ui->setupUi(this);
//按钮
QPushButton * btn = new QPushButton(this);
btn->setText("open");
//第二个页面
QWidget * second_widget = new QWidget;
// QString text = btn->text();
// qDebug()<<text;
connect(btn,&QPushButton::clicked,this,[=]{
if(btn->text() == "open")
{
second_widget->show();
btn->setText("close");
qDebug()<<btn->text();
}
else
{
second_widget->close();
btn->setText("open");
qDebug()<<btn->text();
}
});
}
myWidget::~myWidget()
{
delete ui;
}
十、总结
信号需要声明,不需要实现;
槽函数需要声明,也需要实现;