1.QObject
只有继承了QObject类的类,才具有信号槽的能力。所以,为了使用信号槽,必须继承QObject。凡是QObject类(不管是直接子类还是间接子类),都应该在第一行代码写上Q_OBJECT。不管是不是使用信号槽,都应该添加这个宏。这个宏的展开将为我们的类提供信号槽机制、国际化机制以及 Qt 提供的不基于 C++ RTTI 的反射能力。因此,如果你觉得你的类不需要使用信号槽,就不添加这个宏,就是错误的。其它很多操作都会依赖于这个宏。
示例:
#include <QMainWindow>
#include <QString>
class A:public QObject{
public:
A(QObject* parent=NULL):QObject(parent){
qInfo()<<this<<"被构造";
};
~A(){qInfo()<<this<<"被销毁";};
};
int main(int argc, char* argv[]){
A objA;
A* pA2=new A(&objA); //将pA2挂在到objA下
A* pA3 = new A(pA2);
objA.dumpObjectTree();
}
这样子就会形成一个树结构。
QObject:: A
QObject:: pA2
QObject:: pA3
2.事件与信号
GUI应用程序都由事件驱动,事件主要由应用程序的用户生成,例如点击按钮,控件。或者由其他接触发生如:Internet连接,窗口管理器或计时器。当调用exec方法时,应用程序进入主循环。主循环将获取事件并发送到对象。
信号与槽
信号和槽用于对象之间的通信。
//signal1调用到obj2的slot1
connect(Object1,signal1,Object2,slot1);
//signal1调用到obj3的slot1
connect(Object1,signal1,Object3,slot1);
slot是普通的C++函数,当与之相连的信号发出时将调用。
连接信号和插槽的方式:
1.成员函数指针
connect(senderPtr,&QObject::destoryed,this,&MyObject::objectDestroyed);
2.仿函数或lambda表达式作为slot
connect(sender,&QObject::destoryed,this,[=](){this->m_object.remove(sender);});
学习示例:
头文件
#ifndef MYHEAD1_H_
#define MYHEAD1_H_
#include <QCoreApplication>
#include <QDebug>
class Sender : public QObject
{
Q_OBJECT
public:
explicit Sender(QObject* parent = nullptr);
private:
int m_age = 10;
public:
void incAge();
signals:
// 信号函数无需定义,只需声明,并且不能有返回参数,但可以有输入参数
void ageChanged(int value);
};
class Receiver : public QObject
{
Q_OBJECT
public:
explicit Receiver(QObject* parent = nullptr);
public slots:
//槽函数为普通函数,需要定义,但也不能有返回值
void ageChange(int age);
};
#endif // MYHEAD1_H_
在main函数中调用:
#include "myhead1.h"
int main(int argc, char* argv[])
{
Sender senderObj;
senderObj.incAge();
Receiver recriverObj;
//传递信号,通过指针的方式传递
QObject::connect(&senderObj,&Sender::ageChanged,&recriverObj,&Receiver::ageChange);
//建立连接后,每次emit发送信号都会传递给reciver然后调用ageChange
senderObj.incAge();
senderObj.incAge();
//断开连接
QObject::disconnect(&senderObj,&Sender::ageChanged,&recriverObj,&Receiver::ageChange);
senderObj.incAge();
return 0;
}
当建立连接后,每次emit发送信号后,都会执行相应的槽(slots),而段凯连接后则不会继续调用槽。
3.鼠标键盘响应
在MainWindow构造函数中注册事件,在触发时让其发出信号调用对应处理槽.
头文件
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
// QWidget interface
protected:
void keyPressEvent(QKeyEvent *event);
void mouseMoveEvent(QMouseEvent *event);
};
#endif // MAINWINDOW_H
实现文件
#include "mainwindow.h"
#include <QtWidgets>
MainWindow::MainWindow(QWidget* parent)
: QMainWindow(parent)
{
// 开启鼠标跟踪
setMouseTracking(true);
// 创建一个按钮对象,入参为按钮显示名字和操作对象
auto* quitBtn = new QPushButton("Quit", this);
// 设置按钮位置和大小
quitBtn->setGeometry(50, 25, 100, 50);
// 创建连接,当按钮点击事件出发时,调用循环的退出函数
connect(quitBtn, &QPushButton::clicked, qApp, &QApplication::quit);
}
MainWindow::~MainWindow()
{
}
void MainWindow::keyPressEvent(QKeyEvent* event)
{
// 如果当前按键事件是esc键,则退出程序
if (event->key() == Qt::Key_Escape)
qApp->quit();
}
void MainWindow::mouseMoveEvent(QMouseEvent* event)
{
// 获取当前鼠标X坐标
int x = event->pos().x();
// 获取y坐标
int y = event->pos().y();
QString text = "坐标:" + QString::number(x) + "," + QString::number(y);
this->statusBar()->showMessage(text);
}
在其中,使用new QPushButton创建了一个按钮,并且在按钮中显示了文字,同时使用按钮中的方法来指定按钮的位置和大小(按照x,y轴来判断位置和创建按钮大小的).最后通过指针绑定按钮的点击事件,当按钮被按下时触发QPushButton::clicked,然后调用槽QApplication::quit用来退出程序。
其余的键盘检测按键和鼠标位置是通过重写QMainWindow类中的抽象函数来实现,当在窗口中检测到时会自动的进行调用.
4.控件与自定义槽
QWidget是用户界面的原子类。它接收鼠标、键盘和来自系统的其他事件,并在屏幕上将它们绘制出来。每个Widget都是矩形的,并按照Z-order(Z轴)进行排序。一个Widget夹在它的Parent和它前面的Widget之间。
没有嵌入parent widget中的Widget称为Window。通常情况下,Windows有一个Frame和标题栏(当然也可以通过window flags来取消这些项)。Qt中,QMainWindow和QDialog的多种多样的子类是最常见的Window类型.
这就是一个定义好的QMainWindow,其布局已经是默认规定好的,无法再去增加布局,但是可以创建布局然后替换对应的布局,并放入组件。
头文件:
#ifndef MAINWINDOW_H_
#define MAINWINDOW_H_
#include <QMainWindow>
class QPushButton;
class QLabel;
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget* parent = nullptr);
~MainWindow();
private:
QPushButton* clickBtn;
QLabel* label;
// QObject interface
protected:
void timerEvent(QTimerEvent *event);
public slots:
void onClick();
void onCheck(int state);
};
#endif // MAINWINDOW_H
实现文件:
#include "mainwindow.h"
#include <QtWidgets>
MainWindow::MainWindow(QWidget* parent)
: QMainWindow(parent)
{
// 创建一个布局
QWidget* myWidget = new QWidget(this);
// 替换到中心布局中
setCentralWidget(myWidget);
// 创建按钮
clickBtn = new QPushButton("点击", myWidget);
// 创建点击事件
QCheckBox* cb = new QCheckBox("Connect", myWidget);
// 设置点击事件默认状态
cb->setCheckState(Qt::Checked);
label = new QLabel(QTime::currentTime().toString(), myWidget);
//横向的展示组件
QHBoxLayout* hbox = new QHBoxLayout(myWidget);
hbox->addWidget(clickBtn);
hbox->addWidget(cb);
hbox->addWidget(label);
startTimer(1000);
// 以指针的方式传入对象和函数
connect(clickBtn, &QPushButton::clicked, this, &MainWindow::onClick);
connect(cb, &QCheckBox::stateChanged, this, &MainWindow::onCheck);
}
MainWindow::~MainWindow()
{
}
void MainWindow::timerEvent(QTimerEvent* event)
{
// 标识这个形参没有用到
Q_UNUSED(event);
label->setText(QTime::currentTime().toString());
}
void MainWindow::onClick()
{
// 在底部标题栏展示信息
statusBar()->showMessage("按钮被点击");
}
void MainWindow::onCheck(int state)
{
statusBar()->showMessage("");
// 根据QCheckBox状态来执行对应函数
if (state == Qt::Checked)
connect(clickBtn, &QPushButton::clicked, this, &MainWindow::onClick);
else
disconnect(clickBtn, &QPushButton::clicked, this, &MainWindow::onClick);
}
从这个程序中,我们可以将组件装入到我们自己创建的widget布局中,然后将该布局设置为中心布局,这样就可以在中心区域展示组件了,同时创建了自定义的槽,当触发相应事件的时候调用了自定义槽进行响应。同时也可以根据信号的实时状态来进行连接和断开。