当前位置: 首页 > article >正文

【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 自定义槽

  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("设置标题成功");
}

在这里插入图片描述

  1. 使用图形化设计自定义槽——使用函数名的方式进行将信号和槽进行关联

在这里插入图片描述

选择槽函数类型
在这里插入图片描述
此时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 一对一

  1. 一个信号连接一个槽

我们上述举的例子都是一个信号连接一个槽。

  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 一对多 && 多对一

  1. 一个信号关联多个槽
    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";
}
  1. 多个信号关联一个槽

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倍(这是定位信号的接收对象所需的开销;遍历所有关联;编组/解组传递的参数;多线程时,信号可能需要排队),这种调⽤速度对性能要求不是⾮常⾼的场景是可以忽略的,是可以满足绝大部分场景。

http://www.kler.cn/a/329348.html

相关文章:

  • More Effective C++ Item 7:区别使用()和{}创建对象
  • 小试牛刀-Anchor安装和基础测试
  • 【后端】版本控制
  • 一文了解 inductive bias(归纳偏好)
  • 【电子设计】按键LED控制与FreeRTOS
  • 如何在C#中处理必盈接口返回的股票数据?
  • Jenkins: fontconfig head is null, check your fonts or fonts configuration;
  • Hive数仓操作(十一)
  • mac访达查找文件目录
  • harproxy
  • zabbix7.0通过端口监控服务案例详解
  • 如何配置路由器支持UDP
  • post请求失败failed The system cannot find the path specified
  • Redis篇(缓存机制 - 基本介绍)(持续更新迭代)
  • Mac安装Manim并运行
  • Python中流行的开源OCR项目
  • 10/02赛后总结
  • 【Android 源码分析】Activity生命周期之onStop-1
  • 【重学 MySQL】四十七、表的操作技巧——修改、重命名、删除与清空
  • 前端学习第一天笔记 HTML5 CSS初学以及VSCODE中的常用快捷键
  • 基于AutoDL复现Nice-slam
  • C++入门基础 (超详解)
  • Thinkphp/Laravel基于vue的实验室上机管理系统
  • 基于Python的屏幕录制转GIF工具
  • VisionPro - 基础 - 模板匹配技术-应用3 - Search\PMAline\PatMax\Alignment Guidelines
  • 使用VBA快速生成Excel工作表非连续列图片快照