【Qt】Qt中的窗口坐标 信号与槽
Qt中的窗口坐标 && 信号与槽
- 1. Qt中的窗口坐标
- 2. 信号与槽的概述
- 3. 信号和槽的使用
- 3.1 connect函数的使用
- 3.2 查看内置信号和槽
- 3.2 connect的参数类型不匹配问题
- 4. 自定义信号 && 自定义槽
- 4.1 自定义槽
- 4.2 自定义信号
- 5. 带参数的信号和槽
- 6. 信号与槽的关联方式
- 6.1 一对一
- 6.2 一对多 && 多对一
- 6.3 多对多
- 7. 信号与槽的断开
- 8. 使用Lambda表达式定义槽
- 9. 信号和槽的优缺点
1. Qt中的窗口坐标
在Qt中坐标系的开始是左上角(0,0),从在左上角往右依次增加的是x轴,从左上角往下依次增加的是y轴。
而在一个窗口中不止一个坐标系。对于嵌套窗口来讲,它的坐标是相对于父窗口来讲的。
#include "widget.h"
#include "ui_widget.h"
#include <QPushButton>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 这个 button的父窗口就是Widget
QPushButton* button = new QPushButton(this);
button->setText("按钮");
button->move(200, 200); // 这个就是设置坐标的函数,单位是像素点
// 而对于Widget来讲,它的坐标轴就是我们当前的电脑屏幕
this->move(200, 0);
}
Widget::~Widget()
{
delete ui;
}
我们可以将鼠标对准move函数然后按F1可以快速的查看文档。
2. 信号与槽的概述
-
在 Qt 中,用户和控件的每次交互过程称为⼀个事件。比如 "用户点击按钮"是⼀个事件,“用户关闭窗口” 也是⼀个事件。每个事件都会发出⼀个信号,例如用户点击按钮会发出 “按钮被点击” 的信号,用户关闭窗口会发出 “窗口被关闭” 的信号。
-
Qt 中的所有控件都具有接收信号的能力,⼀个控件还可以接收多个不同的信号。对于接收到的每个信号,控件都会做出相应的响应动作。例如,按钮所在的窗口接收到 “按钮被点击” 的信号后,会做出 “关闭自己” 的响应动作;在 Qt 中,对信号做出的响应动作就称之为槽。 信号和槽是 Qt 特有的消息传输机制,它能将相互独立的控件关联起来。比如,“按钮” 和 “窗口” 本⾝是两个独立的控件,点击 “按钮” 并不会对 “窗口” 造成任何影响。通过信号和槽机制,可以将 “按钮” 和 “窗口” 关联起来,实现 “点击按钮会使窗口关闭” 的效果。
信号的本质:
-
信号是由于用户对窗口或者控件进行操作,导致窗口或者控件触发了某些事件,这个时候Qt对应的窗口类会发出特定的信号,来对用户的操作进行处理。所以信号的本质就是事件。
-
而在Qt中常见的事件右:点击按钮,窗口刷新,移动鼠标,点击鼠标,键盘输入……。
信号的传递方式:
- 首先我们对哪个窗口进行操作,哪个窗口就可以捕捉到这些触发的事件。
- 对于使用者讲,触发的一个事件Qt框架都会给我们发出某个特定的信号。
- 信号的呈现方式其实就是函数,也就是说事件发生了,Qt框架会调用这个函数通知事件触发者。
- Qt中信号的发出者是某个示例化的类对象。
槽的本质:
- 槽就是对信号做出响应的函数。槽就是⼀个函数,与⼀般的 C++ 函数是⼀样的,可以定义在类的任何位置
( public、protected 或 private )
,可以具有任何参数,可以被重载,也可以被直接调用(但是不能有默认参数)。槽函数与⼀般的函数不同的是:槽函数可以与⼀个信号关联,当信号被触发时,关联的槽函数被自动执行。
槽和信号底层其实本质上都是函数称之为槽函数和信号函数,他们之间的关联方式其实就是函数之间的互相调用关系。例如按下按钮的信号函数式clicked()
,关闭窗口的槽函数是close()
。实现点击按钮关闭窗口其实就是信号函数clicked()
函数调用了槽函数close()
。
信号函数和槽函数通常位于某个类中,和普通的成员函数相比,它们的特别之处在于:
- 信号函数用 signals 关键字修饰,槽函数用
public slots、protected slots
或者private slots
修饰signals
和slots
是 Qt 在 C++ 的基础上扩展的关键字,专门用来指明信号函数和槽函数; - 信号函数只需要声明,不需要定义(实现),而槽函数需要定义(实现)。
3. 信号和槽的使用
3.1 connect函数的使用
在QObject类中提供了一个静态函数connect(),该函数专门用来关联指定的信号函数和槽函数。其中QObject是Qt内置的一个类,Qt中提供的很多内置类都是直接或者间接的继承自QObject,所以像QWidget,QPushButton等都可以直接使用connect函数。
connect的函数原型:
QObject::connect(const QObject *sender, // 信号的发送者
const char *signal, // 信号的类型
const QObject *receiver, // 信号的接收者
const char *method, // 信号的处理方式
Qt::ConnectionType type = Qt::AutoConnection // ⽤于指定关联⽅式,默认的关联⽅式为 Qt::AutoConnection,通常不需要⼿动设定。
)
所以我们就可以写一个demo代码来进行测试一下:
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 这个 button的父窗口就是Widget
QPushButton* button = new QPushButton(this);
button->setText("点击关闭函数");
button->move(200, 200); // 这个就是设置坐标的函数,单位是像素点
// 关联处理函数
connect(button, &QPushButton::clicked, this, &QWidget::close);
}
3.2 查看内置信号和槽
在我们下载IDE的时候下载过了一个离线版本的文档。这个时候就需要去文档中寻找。
我们直接找到QPushButton类中找信号。但是因为Qt中存在许多的继承关系,所以我们搜素的类可能找不到对应的函数或者成员,这个时候就许需要到父类中查找。
所以这里我们就需要到QPushButton的父类中查找信号。
同理槽函数也是一样的道理。
3.2 connect的参数类型不匹配问题
这里我们重新回到connect函数的参数类型中我们会发现connect的第二个参数和第三个参数的的类型是const char*,但是我们实际传递的参数却是函数指针,这明显在C++中编译是会进行报错的,但是这里为什么没有报错呢?
在Qt5之前,我们是需要使用SIGNAL和SLOT宏来进行处理的,也就是写成:
connect(button, SINGAL(&QPushButton::clicked), this, SLOT(&QWidget::close));
但是在Qt5后进行了简化,并且还对connect函数进行了重载,我们可以去看看源码,这样就可以接收任意类型的了,而之所以文档写的是const char*,是因为没有对文档进行更新,所以一般我们看文档的时候都需要结合源码进行观看。
template <typename Func1, typename Func2>
static inline QMetaObject::Connection connect(const typename QtPrivate::FunctionPointer<Func1>::Object *sender, Func1 signal,
const typename QtPrivate::FunctionPointer<Func2>::Object *receiver, Func2 slot,
Qt::ConnectionType type = Qt::AutoConnection)
{
4. 自定义信号 && 自定义槽
4.1 自定义槽
- 使用纯代码方式进行自定义槽
自定义槽函数其实跟定义一个函数是一样的
Widget.h
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
public slots: // 在Qt4之前必须写上这个,这里也建议写上,利于区分
void setTitle();
private:
Ui::Widget *ui;
};
Widget.cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
QPushButton* button = new QPushButton(this);
button->setText("点击设置标题");
button->move(200,200);
connect(button, &QPushButton::clicked, this, &Widget::setTitle);
}
Widget::~Widget()
{
delete ui;
}
void Widget::setTitle()
{
this->setWindowTitle("设置标题成功");
}
- 使用图形化设计自定义槽——使用函数名的方式进行将信号和槽进行关联
选择槽函数类型
此时Qt会自动生成一个在Widget.h帮我们生成一个函数声明,在Widget.cpp生成一个函数定义,我们就可以在这个函数定义里面写我们的事件了。
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
}
Widget::~Widget()
{
delete ui;
}
// 这个就是Qt自动帮我们生成的
void Widget::on_pushButton_clicked()
{
this->setWindowTitle("设置标题成功");
}
而这里我们也发现发现了,Qt帮我们生成的这个信号和槽并没有使用到connect函数,就算我们在qmake根据widget.ui生成的C++代码ui_widget.h代码中也没有使用到connect函数进行讲信号和槽进行关联起来。
这是因为Qt可以通过函数名来将信号和槽进行关联起来,我们可以发现Qt给我们生成的槽函数的命令为on_pushButton_clicked,我们可以分成三部分on,pushButton,clicked,其中这三部分on是固定的,pushButton是我们使用图形化界面拖拽出来的按钮的名称,而clicked是我们选择的槽函数,所以合起来的意思就是将pushButton这个按钮和clicked进行关联,一旦触发信号就执行on_pushButton_clicked这个函数。
而其实这是因为Qt调用道理ui_widget.h中的的一个函数自动连接了信号槽的规则:
QMetaObject::connectSlotsByName(Widget);
4.2 自定义信号
自定义信号有以下几个规则:
- 自定义信号函数必须写到"signals”下。
- 返回值必须是void,可以有参数,可以进行重载。
- 只需要实现声明,不需要实现定义。
发送信号:
- 使用 “emit” 关键字发送信号 。“emit” 是⼀个空的宏。“emit” 其实是可选的,没有什么含义,只是为了提醒开发⼈员。
Widget.h
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
signals:
void Mysignal(); // 只需要实现声明即可
private slots:
void on_pushButton_clicked();
void Myslots();
private:
Ui::Widget *ui;
};
Widget.cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
connect(this, &Widget::Mysignal, this, &Widget::Myslots); // 将自定义信号和槽进行关联
}
Widget::~Widget()
{
delete ui;
}
void Widget::on_pushButton_clicked()
{
emit Mysignal(); // 发送信号
}
void Widget::Myslots()
{
this->setWindowTitle("设置标题成功");
}
5. 带参数的信号和槽
信号和槽其实是可以带参数的,但是前提条件是槽函数的参数列表要和信号的参数列表一致,也就是说信号函数的参数类型是什么,槽函数的参数类型也必须是一样的,但是信号函数的参数列表可以比槽函数的参数列表多,前提是相同部分的位置的参数列表要相同。
Widget.h
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
signals:
void Mysignal(const QString&); // 只需要实现声明即可
private slots:
void on_pushButton_clicked();
void Myslots(const QString&);
private:
Ui::Widget *ui;
};
Widget.cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
connect(this, &Widget::Mysignal, this, &Widget::Myslots);
}
Widget::~Widget()
{
delete ui;
}
void Widget::on_pushButton_clicked()
{
emit Mysignal("设置标题成功"); // 发送信号
}
void Widget::Myslots(const QString& str)
{
this->setWindowTitle(str);
}
这里也提醒一下,我们一个类要使用信号和槽的话必须包含Q_OBGECT这个宏,不然的话就会出错报错。
6. 信号与槽的关联方式
6.1 一对一
- 一个信号连接一个槽
我们上述举的例子都是一个信号连接一个槽。
- 一个信号连接另一个信号
Widget.h
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
signals:
void mysignal1();
public slots:
void myslots();
private:
Ui::Widget *ui;
};
Widget.cpp
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
QPushButton* button = new QPushButton(this);
button->setText("点击按钮");
connect(this, &Widget::mysignal1, &Widget::myslots);
//信号关联信号
connect(button, &QPushButton::clicked, this, &Widget::mysignal1);
}
Widget::~Widget()
{
delete ui;
}
void Widget::myslots()
{
this->setWindowTitle("设置标题成功");
}
6.2 一对多 && 多对一
- 一个信号关联多个槽
widget.h
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
public slots:
void myslots1();
void myslots2();
void myslots3();
private:
Ui::Widget *ui;
};
widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include <QPushButton>
#include <QDebug>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
QPushButton* button = new QPushButton(this);
button->setText("点击按钮");
// 一个信号关联多个槽
connect(button, &QPushButton::clicked, this, &Widget::myslots1);
connect(button, &QPushButton::clicked, this, &Widget::myslots2);
connect(button, &QPushButton::clicked, this, &Widget::myslots3);
}
Widget::~Widget()
{
delete ui;
}
void Widget::myslots1()
{
qDebug() << "myslots1";
}
void Widget::myslots2()
{
qDebug() << "myslots2";
}
void Widget::myslots3()
{
qDebug() << "myslots3";
}
- 多个信号关联一个槽
widget.h
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
signals:
void mysignal1();
void mysignal2();
void mysignal3();
public slots:
void myslots1();
void sentsignal();
private:
Ui::Widget *ui;
};
widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include <QPushButton>
#include <QDebug>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
QPushButton* button = new QPushButton(this);
button->setText("点击按钮");
connect(button, &QPushButton::clicked, this, &Widget::sentsignal);
// 多个信号关联一个槽函数
connect(this, &Widget::mysignal1, this, &Widget::myslots1);
connect(this, &Widget::mysignal2, this, &Widget::myslots1);
connect(this, &Widget::mysignal3, this, &Widget::myslots1);
}
Widget::~Widget()
{
delete ui;
}
void Widget::myslots1()
{
qDebug() << "myslots1";
}
void Widget::sentsignal()
{
emit mysignal1();
emit mysignal1();
emit mysignal1();
}
6.3 多对多
其实到这里都大差不差了,同样是复用上述代码
connect(this, &Widget::mysignal1, this, &Widget::myslots1);
connect(this, &Widget::mysignal2, this, &Widget::myslots2);
connect(this, &Widget::mysignal2, this, &Widget::myslots1);
connect(this, &Widget::mysignal3, this, &Widget::myslots1);
7. 信号与槽的断开
使用 disconnect 即可完成断开.
disconnect 的用法和 connect 基本⼀致.
disconnect(this, &Widget::mysignal1, this, &Widget::myslots1);
8. 使用Lambda表达式定义槽
Lambda表达式 是 C++11 增加的特性。C++11 中的 Lambda表达式 用于定义并创建匿名的函数对象,以简化编程⼯作。
[ capture ] ( params ) opt -> ret {
Function body;
};
- capture 捕获列表
- params 参数表
- opt 函数选项
- ret 返回值类型
- Function body 函数体
符号 | 说明 |
---|---|
[ ] | 局部变量捕获列表。Lambda表达式不能访问外部函数体的任何局部变量 |
[a] | 在函数体内部使⽤值传递的⽅式访问a变量 |
[&b] | 在函数体内部使用引用传递的方式访问b变量 |
[=] | 函数外的所有局部变量都通过值传递的方式使用, 函数体内使用的是副本 |
[&] | 以引用的方式使用Lambda表达式外部的所有变量 |
[=, &foo] | foo使用引用方式, 其余是值传递的方式 |
[&, foo] | foo使用值传递方式,其余引用传递 |
[this] | 在函数内部可以使用类的成员函数和成员变量,= 和 & 形式也都会默认引入 |
- 由于使用引用方式捕获对象会有局部变量释放了而Lambda函数还没有被调⽤的情况。如果执⾏Lambda函数,那么引⽤传递方式捕获进来的局部变量的值不可预知。所以绝大多数场合使用的形式为: [=] () { }
•* 早期版本的 Qt,若要使用Lambda表达式,要在 “.pro” ⽂件中添加: CONFIG += C++11 因为 Lambda表式 是 C++11 标准提出的。Qt5 以上的版本无需手动添加,在新建项目时会自动添加。
#include "widget.h"
#include "ui_widget.h"
#include <QPushButton>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
QPushButton* button = new QPushButton(this);
button->move(200,200);
button->setText("按钮");
connect(button, &QPushButton::clicked, this, [=](){
button->setWindowTitle("按钮被按下");
button->move(300, 300);
});
}
Widget::~Widget()
{
delete ui;
}
9. 信号和槽的优缺点
优点: 松散耦合
- 信号发送者不需要知道发出的信号被哪个对象的槽函数接收,槽函数也不需要知道哪些信号关联了自己,Qt的信号槽机制保证了信号与槽函数的调用。支持信号槽机制的类或者父类必须继承于 QObject 类。
缺点: 效率较低
- 与回调函数相比,信号和槽稍微慢⼀些,因为它们提供了更高的灵活性,尽管在实际应⽤程序中差别不大。通过信号调用的槽函数比直接调用的速度慢约10倍(这是定位信号的接收对象所需的开销;遍历所有关联;编组/解组传递的参数;多线程时,信号可能需要排队),这种调⽤速度对性能要求不是⾮常⾼的场景是可以忽略的,是可以满足绝大部分场景。