一. QT简介
一. 窗口
1. 设置窗口大小
resize(600,400);
setFixedSize(600,400); // 设置固定窗口大小
2. 设置窗口标题
setWindowTitle(“窗口1”)
例子:
#include "mywidget.h"
#include <QPushButton>
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MyWidget w;
w.resize(600,400);
w.setWindowTitle("hello");
w.show();
return a.exec();
}
3. 创建第二个窗口
在main文件中实例化窗口2
#include "mainwidget.h"
#include "subwidget.h" //导入窗口2头文件
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWidget w;
w.setWindowTitle("窗口1");
w.resize(600,400);
w.show();
SubWidget w2;
w2.setWindowTitle("窗口2");
w2.resize(300,200);
w2.show();
return a.exec();
}
二. 按钮
#include "mywidget.h"
#include <QPushButton>
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MyWidget w;
QPushButton btn1;
btn1.setParent(& w);
btn1.setText("OK");
btn1.move(300,100);
QPushButton btn2(&w);
btn2.setText("OK2");
btn2.move(300,150);
w.show();
return a.exec();
}
三. 以后不再修改main.cpp
在main.cpp执行过程中, 我们实例化了一个w (MyWidget w;
), 这个w的实例化过程, 调用了 MyWidget 类的构造函数, 所以以后我们写程序不要修改main文件了, 直接去下游操作.
MyWidget 类的构造函数在MyWidget.cpp中, 我们以后在这写程序
我们把刚刚的按钮程序修改成在MyWidget.cpp中的构造函数中
首先在MainWidget.h
中声明变量(下例中我的主窗口类命名为了MainWidget), 这里我们声明了两个按钮, 一个是普通变量, 一个是指针
#ifndef MAINWIDGET_H
#define MAINWIDGET_H
#include <QWidget>
#include <QPushButton>
class MainWidget : public QWidget
{
Q_OBJECT
public:
MainWidget(QWidget *parent = nullptr);
~MainWidget();
private:
QPushButton btn1;
QPushButton *btn2;
};
#endif // MAINWIDGET_H
然后在MainWidget.cpp
中的构造函数中实现
#include "mainwidget.h"
MainWidget::MainWidget(QWidget *parent)
: QWidget(parent)
{
btn1.setParent(this);
btn1.setText("OK1");
btn1.move(300,100);
btn2 = new QPushButton(this);
btn2->setText("OK2");
btn2->move(300,150);
}
MainWidget::~MainWidget()
{
}
四. 对象树机制
qt有对象树机制, 使其有了自动内存管理机制, 很多时候不用我们自己清理内存了.
简单的说 : 指定父对象后, 子对象如果是动态分配空间的(new出来的), 系统会自动析构并释放空间
五. 创建类
因为对象树的机制, 在QT中创建类要按照以下方法
创建类, 并指定父类
注意 : 关于继承哪个类, 要看你新类的需求
完成后, 系统会自动添加.h .cpp文件, 同时自动挂到对象树中
六. 信号与槽机制
- 信号发送端负责发生事件 ( 信号, signal )
- 信号接收端负责接收并处理信号 ( 槽, slot )
- 信号发送端 和 信号接收端是独立的, 通过
connect
函数链接在一起,完成耦合
以下是举例:
#include "mainwidget.h"
MainWidget::MainWidget(QWidget *parent)
: QWidget(parent)
{
btn1.setParent(this);
btn1.setText("关闭窗口1");
btn1.move(300,100);
//按钮发射被点击的信号, 窗口接收, 并给槽中传入了close函数, 完成关闭
connect(&btn1,& QPushButton::clicked,this,& QWidget::close);
// 发射者 信号 接收者 槽
btn2 = new QPushButton(this);
btn2->setText("关闭窗口2");
btn2->move(300,150);
//按钮发射被点击的信号, 窗口接收, 并给槽中传入了close函数, 完成关闭
connect(btn2,& QPushButton::clicked,this,& QWidget::close);
// 发射者 信号 接收者 槽
}
MainWidget::~MainWidget()
{
}
这里说一下此例子中, 如何在帮助手册
中 找 信号
和槽
比如: 我的btn1是 QPushButton类的实例, 我们选中QPushButton这几个字, 按F1查看帮助(按两下F1是全屏,按ESC是退出帮助), 去找 signals 相关内容, 并没有找到, 只找到了如下内容, 说明QPushButton的信号是继承自父类的
从图中我们可以看出: 它有4个信号继承自 QAbstractButton, 3个继承自QWidget....
我们点击链接, 去QAbstractButton类中, 发现它有signals信号机制, 有我们想触发的clicked
接收方应该填写槽函数, 查找自带的槽函数和上面的方法类似, 找slots就可以了
七. 自定义槽
刚刚,我们体验了信号和槽机制,连接的两个函数都是自带的函数,现在我们尝试绑定自定义槽
比如: 我们想点击按钮实现终端打印,说起来很简单,其实就是在通过信号和槽机制触发自定义的槽函数
在.h文件中声明槽函数
在.cpp中实现槽函数并连接
八. 自定义信号
现在, 我们来学习如何自定义信号, 比如每次我们让程序开始执行时, 加载完成后让窗口最大化, 那么我们可以在构造函数中发一个加载完成的信号, 去触发窗口最大化的槽函数
- 信号函数可以只声明不实现,槽函数要声明和实现
- 信号函数必须声明在signals关键字下
1. 首先, 我们在"mainwidget.h"中创建信号
#ifndef MAINWIDGET_H
#define MAINWIDGET_H
#include <QWidget>
#include <QPushButton>
#include <QDebug>
class MainWidget : public QWidget
{
Q_OBJECT
public:
MainWidget(QWidget *parent = nullptr);
~MainWidget();
signals:
void loaded(); //创建信号
};
#endif // MAINWIDGET_H
2. 在 "mainwidget.cpp" 中触发信号
触发自定义信号使用 : emit 关键字 emit是关键字不是函数
#include "mainwidget.h"
MainWidget::MainWidget(QWidget *parent)
: QWidget(parent)
{
connect(this,&MainWidget::loaded,this,&QWidget::showMaximized);
qDebug()<<"加载中...";
qDebug()<<"加载中...";
qDebug()<<"加载中...";
qDebug()<<"加载中...";
emit this->loaded();
qDebug()<<"加载完成!";
}
MainWidget::~MainWidget()
{
}
3. 有个问题
可以看到, 程序运行成功了! 但是有一个警报:
这是因为: 自己给自己发信号是不合理的, 我们完全可以直接 this->showMaximized();
但, 通过上面的例子, 我们已经弄清了如何自定义信号
九. 自定义信号和槽 (窗口间)
现在我们来学习创建双窗口, 更准确的使用自定义信号和自定义槽,
我们创建一个子窗口, 方法见第二节, 不同的是, 我们在mainwidget中实例化它
这次我们想实现的功能是: 点击主窗口按钮, 跳转子窗口, 点击子窗口按钮, 跳转主窗口
思路 :
- 在主窗口
mainwidget.h
中实例化第二窗口 - 点击主窗口按钮时, 会发送点击信号, 创建一个点击信号槽函数, 用于处理点击事件
- 点击事件中, 隐藏主窗口, 显示子窗口
- 点击子窗口按钮时, 会发送点击信号, 创建一个点击信号槽函数, 用于处理点击事件
- 点击事件中, 隐藏子窗口, 但子窗口找不到主窗口, 只能发出想返回的信号
- 主窗口监听子窗口想返回的信号, 显示自己
#ifndef MAINWIDGET_H
#define MAINWIDGET_H
#include <QWidget>
#include <QPushButton>
#include <QDebug>
#include "subwidget.h" //包含子窗口头文件
class MainWidget : public QWidget
{
Q_OBJECT
public:
MainWidget(QWidget *parent = nullptr);
~MainWidget();
private:
SubWidget sub_w; //实例化子窗口
QPushButton btn1; //创建一个按钮用于跳转窗口
void btn1_clicked_handler(); // 主窗口按钮点击触发的槽
};
#endif // MAINWIDGET_H
十. 自定义信号和槽 (对象间)
我们先新建个Teacher类和Student类, 让teacher发送信号, 让同学接收并处理
- 信号函数可以只声明不实现,槽函数要声明和实现
- 信号函数 / 槽函数 的参数是一一对应的
- 信号函数 / 槽函数 都没有返回值!
- 与系统预设的信号/槽相比, 多了一个手动触发的环节
(1). 首先在teacher.h中, 声明要触发的信号
注意 : 这个信号, 只需要在.h中声明, 不需要在.cpp中实现
(2). 在student.h中, 声明槽函数
(3). 在student.cpp中, 实现这个槽函数
(4). 建立信号与槽的连接, 触发信号
十一. 用函数指针connect (为了方便重载)
进化上面的程序, 用函数指针代替函数地址(为了后续的重载)
十二. 信号传参, 信号和槽的重载问题
比如我有两个信号函数,两个槽函数,一对儿不带参数触发,一对儿带参数触发,该如何区分呢?
十三. lambda表达式 connect
1. lambda表达式的结构
[捕获列表](参数){函数体}
其中, 只有传送到捕获列表中的外部变量才能被lambda使用,
[] : 不捕获
[a] : 捕获变量a
[this] : 捕获对象 this
[=] : 捕获一切 (按值捕获)
[&] : 捕获一切 (按地址捕获)
2. lambda表达式用于信号/槽的连接
- clash推荐使用4参数的connect函数
- .h中声明的变量, 传this进去,不要单独传
- 动态创建的变量可以单独传
- 推荐使用[=]
- 不推荐使用[&]捕获
#include "mainwidget.h"
#include <QDebug>
MainWidget::MainWidget(QWidget *parent)
: QWidget(parent)
{
// btn1在.h中声明了(指针类型)
btn1=new QPushButton(this);
btn1->setParent(this);
btn1->setText("btn1");
//btn2在.h中声明了(变量类型)
btn2.setParent(this);
btn2.setText("btn2");
btn2.move(100,0);
// 声明一个变量
int a = 5;
// 动态声明一个按钮
QPushButton *btn3 = new QPushButton;
btn3->setParent(this);
btn3->setText("btn3");
btn3->move(200,0);
//捕获列表不捕获外部变量
connect(btn1,&QPushButton::clicked,this,[](){qDebug()<<"hello";});
//把this传入lambda
connect(btn1,&QPushButton::clicked,this,[this](){btn1->setText("OK");});
connect(&btn2,&QPushButton::clicked,this,[this](){btn2.setText("OK2");});
//把动态变量传入lambda
connect(btn1,&QPushButton::clicked,this,[a](){qDebug()<<"a:"<<a;}); //把a传入lambda
connect(btn3,&QPushButton::clicked,this,[btn3](){btn3->setText("OK3");}); //把btn3传入lambda
//把一切传入lambda
connect(btn1,&QPushButton::clicked,this,[=](){qDebug()<<"a:"<<a;});
connect(btn1,&QPushButton::clicked,this,[=](){setWindowTitle("OKOKOK");});
}
MainWidget::~MainWidget()
{
}
3. 带参数的lambda
比如: clicked信号就有一个参数
上面的程序我们都没一一对应接收, 那是因为这个参数是有默认值的, 现在我们用lambda接收过来
connect(btn1,&QPushButton::clicked,this,[](bool checked_flag){qDebug()<<"checked_flag:"<<checked_flag;});
十四. 断开连接
disconnect(ddd,teacherSingal,anny,studentSlot);
十五. 槽函数中找到信号发射者
比如我现在有两个按钮, 我给他们绑定同一个槽函数, 但是希望在槽函数中根据不同按钮做不同的事情, 那应该怎么做呢? 我们可以传参解决, 也可以在槽函数中找到信号发射者
我们使用sender()函数找到信号发射者
注意: sender()函数的返回值是QObject类型. 所以要强转一下
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
connect(ui->btn1,&QPushButton::clicked,this,&MainWindow::dealEvent);
connect(ui->btn2,&QPushButton::clicked,this,&MainWindow::dealEvent);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::dealEvent()
{
QObject *mySender = sender();
QPushButton *p = (QPushButton *) mySender;
if(p != NULL){
ui->label->setText(p->text());
}
}