(6)元对象系统与信号与槽机制

1. 元对象系统

        元对象系统是一个基于标准C++的扩展,为Qt提供了信号与槽机制、实时类型信息、动态属性系统。

 什么是元对象

        在计算机科学中,元对象是这样一个东西:它可以操纵、创建、描述、或执行其他对象。元对象描述的对象称为基对象。元对象可能存在这样的信息:基础对象的类型、接口、类、方法、属性、变量、控制结构等。

元对象系统组成

QObject

        QObject是 QT 对象模型的核心,绝大部分的 Qt类都是从这个类继承而来。

Q_OBJECT

        Q_OBJECT宏必须出现在类定义的私有部分,用来开启信号和槽、动态属性系统,或Qt元对象系统提供的其他服务。

#define Q_OBJECT \
public: \
    QT_WARNING_PUSH \
    Q_OBJECT_NO_OVERRIDE_WARNING \
    static const QMetaObject staticMetaObject; \
    virtual const QMetaObject *metaObject() const; \
    virtual void *qt_metacast(const char *); \
    virtual int qt_metacall(QMetaObject::Call, int, void **); \
    QT_TR_FUNCTIONS \
private: \
    Q_OBJECT_NO_ATTRIBUTES_WARNING \
    Q_DECL_HIDDEN_STATIC_METACALL static void qt_static_metacall(QObject *, QMetaObject::Call, int, void **); \
    QT_WARNING_POP \
    struct QPrivateSignal {}; \
    QT_ANNOTATE_CLASS(qt_qobject, "")

MOC

         Qt 的信号和槽机制是采用标准 C++ 来实现的。该实现使用 C++ 预处理器和 Qt 所包括 的 moc(元对象编译器)。元对象编译器读取应用程序的头文件,并生成必要的代码,以支 持信号和槽机制。

        元对象编译器 moc(meta object compiler)对 C++文件中的类声明进行分析并产生用 于初始化元对象的 C++代码,元对象包含全部信号和槽的名字以及指向这些函数的指针。

        moc 读 C++源文件,如果发现有 Q_OBJECT 宏声明的类,它就会生成另外一个 C++源文件,这个新生成的文件中包含有该类的元对象代码。例如,假设我们有一个头文件Widget.h,在这个文件中包含有信号或槽的声明,然后在同名源文件 Widget.cpp中包含这个头文件,并实现类,那么在编译之前 moc 工具就会根据该文件自动生成一个名为moc_Widget.cpp 的 C++源文件并将其提交给编译器。

2. 信号与槽机制

        信号与槽是 Qt 框架引以为傲的机制之一。所谓信号与槽,实际就是观察者模式(发布-订阅模式)。当某个事件发生之后,比如,按钮检测到自己被点击了一下,它就会发出一个信号(signal)。这种发出是没有目的的,类似广播。如果有对象对这个信号感兴趣,它就会使用连接(connect)函数,意思是,将想要处理的信号和自己的一个函数(称为槽(slot))绑定来处理这个信号。也就是说,当信号发出时,被连接的槽函数会自动被回调。这就类似观察者模式:当发生了感兴趣的事件,某一个操作就会被自动触发。信号和槽是Qt特有的信息传输机制,是Qt设计程序的重要基础,它可以让互不干扰的对象建立一种联系。

 信号与槽的本质

        信号是由对象发送出去的消息,信号实际上是一个特殊的函数,不需要由程序员实现,而是由Qt的Qt Meta Object System实现的。

        槽实际上就是普通的函数,成员函数、全局函数、静态函数、lambda函数都可以作为槽函数。

        当我们把对象的信号和槽绑定在一起之后,当信号触发时,与之绑定的槽函数将会自动调用,并把信号的参数传递给槽函数。

信号与槽的绑定

        在Qt中信号和槽函数都是独立的个体,本身没有任何联系,但是由于某种特性需求我们可以将二者连接到一起,好比牛郎和织女想要相会必须要有喜鹊为他们搭桥一样。

信号与槽绑定使用QObject::connent()函数实现,其基本格式如下:

[static] QMetaObject::Connection connect(
     const QObject *sender, 
     const QMetaMethod &signal, 
     const QObject *receiver, 
     const QMetaMethod &method,
 	, Qt::ConnectionType type = Qt::AutoConnection)
     
 [static] QMetaObject::Connection connect(
     const QObject *sender, 
     PointerToMemberFunction signal, 
     Functor functor)

参数解析:

  • sender:信号发送者,需要传递一个QObject族的对象指针

  • signal:发出的具体信号,需要传递一个函数指针

  • receiver:信号接收者,需要传递一个QObject族的对象指针

  • method:接收到信号之后,信号接收者处理动作,需要传递一个函数指针(槽函数)

  • type:第一个connect函数独有参数,表示信号和槽的连接类型;有默认值,一般不需修改。

 信号与槽的断开

        信号与槽连接之后,还可以断开连接,断开之后,信号出发之后,槽函数就不会调用了。注意,信号与槽的断开时必须与连接时的参数一致。当然也可以直接使用connect函数的返回值断开连接。

bool disconnect(const QObject *sender, 
                const QMetaMethod &signal, 
                const QObject *receiver, 
                const QMetaMethod &method)
bool disconnect(const QMetaObject::Connection &connection)

3. 标准信号与槽的使用

使用信号与槽机制必须在该类的定义私有部分包含Q_OBJECT宏。

        在Qt提供的很多类中都可以对用户触发的某些特定事件进行检测,  当事件被触发后就会产生对应的信号, 这些信号都是Qt类内部自带的, 因此称之为标准信号。

        同样的,在Qt的很多类内部为我了提供了很多功能函数,并且这些函数也可以作为触发的信号的处理动作,有这类特性的函数在Qt中称之为标准槽函数。

#include <QApplication>
#include <QWidget>

//去掉控制台
#pragma comment(linker,"/subsystem:windows /entry:mainCRTStartup")

class Widget : public QWidget
{
	Q_OBJECT							// 使用信号与槽机制必须包含此宏
public:
	Widget(QWidget* parent = nullptr)
		:QWidget(parent)
	{
		setWindowTitle("窗口标题");		// 设置窗口标题
		resize(640, 480);				// 设置窗口大小

	}
	~Widget()
	{

	}
private:
};

int main(int argc, char* argv[]) {

	QApplication a(argc, argv);
	Widget w;
	w.show();

	return a.exec();
}
#include "main.moc"

如上代码将会得到一个窗口:

当在Widget构造函数中添加以下代码会出现:

Widget(QWidget* parent = nullptr)
	:QWidget(parent)
{
	setWindowTitle("窗口标题");		// 设置窗口标题
	resize(640, 480);				// 设置窗口大小

	//按钮的使用
	QPushButton* btn = new QPushButton;
	btn->setText("Button");
	btn->show();
}

按钮为什么没有显示在窗口上面而是独立显示,那是因为没有给按钮设置父对象,给按钮设置父对象之后就不需要调用show()了,按钮会跟窗口一起显示。

//按钮的使用
QPushButton* btn = new QPushButton;
btn->setText("Button");
btn->setParent(this);

 然后尝试使用信号和槽实现点击按钮关闭窗口的效果:

class Widget : public QWidget
{
	Q_OBJECT							// 使用信号与槽机制必须包含此宏
public:
	Widget(QWidget* parent = nullptr)
		:QWidget(parent)
	{
		setWindowTitle("窗口标题");		// 设置窗口标题
		resize(640, 480);				// 设置窗口大小

		m_btn = new QPushButton("Button", this);
		//关联信号与槽
		auto con = connect(m_btn, &QPushButton::clicked, this, &QWidget::close);

	}
	~Widget()
	{

	}
private:
	QPushButton* m_btn = nullptr;
};

int main(int argc, char* argv[]) {

	QApplication a(argc, argv);
	Widget w;
	w.show();

	return a.exec();
}
#include "main.moc"

使用disconnect函数解除信号与槽的绑定

//关联信号与槽
auto con = connect(m_btn, &QPushButton::clicked, this, &QWidget::close);

//解绑信号与槽
disconnect(m_btn, &QPushButton::clicked, this, &QWidget::close);
//disconnect(con);    // 与上一行代码一样的效果

使用帮助文档查看标准的信号与槽

直接使用帮助文档查看控件,如果该控件没有信号或者槽就继续查找它的父类是否有。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

石小浪♪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值