Qt元对象系统 day4
元对象
- 元对象系统是一个基于标准C++的扩展,为Qt提供了信号与槽机制、实时类型信息、动态属性系统。
- 元对象可以操作、创建、描述或是执行其他对象,元对象又称为基对象
- 元对象组成
- QObject: QT 对象模型的核心,绝大部分的 Qt类都是从这个类继承而来
- Q_OBJECT:Q_OBJECT宏必须出现在类定义的私有部分,用来开启信号和槽、动态属性系统,或Qt元对象系统提供的其他服务。使用信号与槽时就得包含这个宏
- MOC:MOC编译器为QObject子类提供了一些实现元对象特性所需要的一些代码。就比如说信号,大家知识在类声明的时候声明了所需要的信号,在MOC编译时,会为信号添加函数定义。
#include <QApplication>
#include <QWidget>
class Widget :public QWidget
{
Q_OBJECT
public:
Widget(QWidget* parent =nullptr):QWidget(parent)
{
}
};
int main(int argc, char* argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
//如果把类和main这个文件写在了同一个文件,那么必须在代码最后加上#include[空格]"文件名.moc"
//这行预处理指令,告诉moc这个文件需要进行元编译,以实现Q_OBJECT宏中声明的函数
#include "main.moc"
使用按钮控件
- 包含头文件#include <QPushButton>
#include <QApplication>
#include <QWidget>
#include <QPushButton>
class Widget :public QWidget
{
//只要用到信号与槽就必须加这个宏
Q_OBJECT
public:
Widget(QWidget* parent =nullptr):QWidget(parent)
{
//设置窗口大小
resize(600, 600);
//添加按钮,放到在自己主屏幕上
QPushButton *btn = new QPushButton(this);
//添加文本
btn->setText("小瓜");
//移动按钮位置
btn->move({300,300});
//当点击按钮的时候进行自定义操作
connect(btn, &QPushButton::clicked, this, &Widget::on_btn_clicked);
}
//槽函数
void on_btn_clicked()
{
qDebug() << "你点击了小瓜";
}
};
int main(int argc, char* argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
//如果把类和main这个文件写在了同一个文件,那么必须在代码最后加上#include[空格]"文件名.moc"
//这行预处理指令,告诉moc这个文件需要进行元编译,以实现Q_OBJECT宏中声明的函数
#include "main.moc"
- 运行结果
信号与槽
-
信号与槽:实际就是一个观察者模式(发布-订阅模式),例如按钮检测到自己被点击了一下,它就会发出一个信号(signal)。这种发出是没有目的的,类似广播。如果有对象对这个信号感兴趣,它就会使用连接(connect)函数,意思是,将想要处理的信号和自己的一个函数(称为槽(slot))绑定来处理这个信号。也就是说,当信号发出时,被连接的槽函数会自动被回调。这就类似观察者模式:当发生了感兴趣的事件,某一个操作就会被自动触发。这样就让互不干扰的对象建立了联系
-
槽实际上就是普通的函数,成员函数、全局函数、静态函数、lambda函数都可以!
-
当我们把对象的信号和槽绑定在一起之后,当信号触发时,与之绑定的槽函数将会自动调用,并把信号的参数传递给槽函数
绑定信号与槽
- 信号与槽绑定使用
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函数独有的参数,表示信号和槽的连接类型;有默认值,一般不需要修改
标准信号与槽
- 在Qt提供的很多类中都可以对用户触发的某些特定事件进行检测, 当事件被触发后就会产生对应的信号, 这些信号都是Qt类内部自带的, 因此称之为标准信号。
- 系统自带的信号和槽通常如何查找呢,这个就需要利用帮助文档了,在帮助文档中比如我们上面的按钮的点击信号,在帮助文档中输入QPushButton,首先我们可以在
Contents
中寻找关键字signals
,信号的意思,但是我们发现并没有找到,这时候我们应该看当前类从父类继承下来了哪些信号,因此我们去他的父类QAbstractButton中就可以找到该关键字,点击signals索引到系统自带的信号有如下几个 - QPushButton的信号
- QWidget的槽
- 断开连接和连接是一样的语法
QObject::disconnect(m_btn, &QPushButton::released, this, &Widget::on_btn_released);
- 使用连接标识断开连接
QMetaObject::Connection con = QObject::connect(m_btn,&QPushButton::clicked,this,&Widget::on_btn_released);
QObject::disconnect(con);
#include <QApplication>
#include <QWidget>
#include <QPushButton>
class Widget :public QWidget
{
//只要用到信号与槽就必须加这个宏
Q_OBJECT
public:
//初始化按钮成员
Widget(QWidget* parent =nullptr):QWidget(parent),m_btn(new QPushButton("小瓜", this))
{
//设置窗口大小
resize(300, 300);
//连接m_btn信号
m_con = connect(m_btn, &QPushButton::clicked, this, &Widget::on_btn_clicked);
connect(m_btn, &QPushButton::pressed, this, &Widget::on_btn_pressed);
connect(m_btn, &QPushButton::released, this, &Widget::on_btn_released);
}
//槽函数
void on_btn_clicked()
{
qDebug() << "click";
}
void on_btn_pressed()
{
//如果按钮按下,断开released信号连接
disconnect(m_btn, &QPushButton::released, this, &Widget::on_btn_released);
//使用连接标识断开连接
disconnect(m_con);
qDebug() << "press";
}
void on_btn_released()
{
qDebug() << "releas";
}
protected:
QPushButton* m_btn{};
//使用连接标识断开连接
QMetaObject::Connection m_con{};
};
int main(int argc, char* argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
//如果把类和main这个文件写在了同一个文件,那么必须在代码最后加上#include[空格]"文件名.moc"
//这行预处理指令,告诉moc这个文件需要进行元编译,以实现Q_OBJECT宏中声明的函数
#include "main.moc"
- 运行结果
04 各种槽(成员函数、静态函数、全局函数、labmda)
- 槽函数的返回值必须是void,槽函数的参数个数不能多于信号的参数个数,信号也可以作为槽函数
#include <QApplication>
#include <QWidget>
#include <QPushButton>
void on_btn_clicked_global()
{
qDebug() << __FUNCTION__;
}
class Widget :public QWidget
{
//只要用到信号与槽就必须加这个宏
Q_OBJECT
public:
//初始化按钮成员
Widget(QWidget* parent =nullptr):QWidget(parent),m_btn(new QPushButton("小瓜", this))
{
//设置窗口大小
resize(300, 300);
//连接m_btn信号
m_con = connect(m_btn, &QPushButton::clicked, this, &Widget::on_btn_clicked);
connect(m_btn, &QPushButton::pressed, this, &Widget::on_btn_pressed);
connect(m_btn, &QPushButton::released, this, &Widget::on_btn_released);
//把静态函数作为槽函数
connect(m_btn, &QPushButton::released, this, &Widget::on_btn_clicked_static);
//全局函数作为槽函数
connect(m_btn, &QPushButton::released, on_btn_clicked_global);
//lambda表达式作为槽函数
connect(m_btn, &QPushButton::clicked, []() {qDebug() << "lambda"; });
//lambda捕获组件中的文本
connect(m_btn, &QPushButton::clicked, [this]() {qDebug() << m_btn->text(); });
//获取信号传递的参数
connect(m_btn, &QPushButton::clicked, [this](bool checked) {qDebug() << m_btn->text() << checked; });
m_btn->setCheckable(true);//设置按钮可以选中
}
//一般槽函数都加上slots这个标识宏
public slots:
void on_btn_clicked()
{
qDebug() << "click";
}
void on_btn_pressed()
{
//如果按钮按下,断开released信号连接
disconnect(m_btn, &QPushButton::released, this, &Widget::on_btn_released);
//使用连接标识断开连接
disconnect(m_con);
qDebug() << "press";
}
void on_btn_released()
{
qDebug() << "releas";
}
static void on_btn_clicked_static()
{
qDebug() << __FUNCTION__;
}
protected:
QPushButton* m_btn{};
//使用连接标识断开连接
QMetaObject::Connection m_con{};
};
int main(int argc, char* argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
//如果把类和main这个文件写在了同一个文件,那么必须在代码最后加上#include[空格]"文件名.moc"
//这行预处理指令,告诉moc这个文件需要进行元编译,以实现Q_OBJECT宏中声明的函数
#include "main.moc"
- 运行结果
自定义信号和重载解决方案
- 信号是类的成员函数,并且返回类型必须是 void 类型
- 信号函数只需要声明, 不需要定义(没有函数体实现)
- 参数可以随意指定, 信号也支持重载
- 信号需要使用 signals 关键字进行声明, 使用方法类似于public等关键字
- 在程序中发送自定义信号: 发送信号的本质就是调用信号函数
emit mysignals(); //发送信号
- emit是一个空宏,没有特殊含义,仅用来表示这个语句是发射一个信号,不写当然可以,但是不推荐。
#include <QApplication>
#include <QWidget>
#include <QPushButton>
#include <QLineEdit>
class Login :public QWidget
{
Q_OBJECT
public:
//初始化登录界面的组件
Login(QWidget* parent = nullptr) :QWidget(parent)
, userNameEdit(new QLineEdit(this))
, passwordEdit(new QLineEdit(this))
, login(new QPushButton("登录", this))
{
//设置窗口大小
resize(600, 400);
//设置控件位置居中位,窗口宽度-控件的宽度/2,高度就自行设置
userNameEdit->move((width() - userNameEdit->width()) / 2, 50);
passwordEdit->move((width() - passwordEdit->width()) / 2, 100);
login->move((width() - login->width()) / 2, 150);
//连接信号与槽
connect(login, &QPushButton::clicked, [=]()
{
auto userName = userNameEdit->text();
auto password = passwordEdit->text();
//是否验证成功
if (1)
{
emit sig_loginSucceed();//emit无任何作用,仅作为标识
emit sig_loginSucceed(userName, password);
}
});
//信号转发
connect(this, QOverload<>::of(&Login::sig_loginSucceed),this, &Login::login_OK);
}
//signals 下面只能放信号
signals:
void sig_loginSucceed();
void sig_loginSucceed(const QString& userName,const QString& password);
void login_OK();
protected:
QLineEdit* userNameEdit{};
QLineEdit* passwordEdit{};
QPushButton* login{};
};
int main(int argc, char* argv[])
{
QApplication a(argc, argv);
Login w;
w.show();
//信号重载的二义性问题
//1.使用函数指针解决
void (Login:: * sig_loginSucceed_ptr)(const QString & userName, const QString & password) = &Login::sig_loginSucceed;
QObject::connect(&w, sig_loginSucceed_ptr, [](const QString& userName, const QString& password)
{
qDebug() << "登录成功" << "用户名:" << userName << "密码:" << password;
});
//2.使用QOverload类解决
QObject::connect(&w, QOverload<const QString&,const QString&>::of(&Login::sig_loginSucceed), [](const QString& userName, const QString& password)
{
qDebug() << "登录成功2" << "用户名:" << userName << "密码:" << password;
});
//信号转发
QObject::connect(&w, &Login::login_OK, []()
{
qDebug() << "login_Ok";
});
return a.exec();
}
//如果把类和main这个文件写在了同一个文件,那么必须在代码最后加上#include[空格]"文件名.moc"
//这行预处理指令,告诉moc这个文件需要进行元编译,以实现Q_OBJECT宏中声明的函数
#include "main.moc"
- 运行结果
窗口切换
- 新建几个头文件与cpp,在CMAkeLists中添加这几个资源
Widget.h
#ifndef WIDGET_H_
#define WIDGET_H_
#include <QWidget>
#include <QPushButton>
#include "SubWidget.h"
class Widget :public QWidget
{
Q_OBJECT
public:
Widget(QWidget* parent = nullptr);
protected:
//初始化为空指针
SubWidget* m_subWidget{};
QPushButton* m_curBtn{};
};
#endif // !WIDGET_H_
Widget.cpp
#include "Widget.h"
Widget::Widget(QWidget* parent) :QWidget(parent)
,m_subWidget(new SubWidget)
,m_curBtn(new QPushButton("切换到子窗口",this))
{
//设置标题
setWindowTitle("主窗口");
resize(600, 400);
connect(m_curBtn, &QPushButton::clicked, [=]()
{
//隐藏主窗口组件
this->hide();
//切换到子窗口
m_subWidget->show();
}
);
//接收信号,切换回主窗口
connect(m_subWidget, &SubWidget::showMainWidget, [=]()
{
this->show();
m_subWidget->hide();
});
}
SubWidget.h
#ifndef SUBWIDGET_H_
#define SUBWIDGET_H_
#include <QWidget>
#include <QPushButton>
class SubWidget :public QWidget
{
Q_OBJECT
public:
SubWidget(QWidget* parent = nullptr);
signals:
//切换窗口信号
void showMainWidget();
protected:
QPushButton* m_btn{};
};
#endif // !WIDGET_H_
SubWidget.cpp
#include "SubWidget.h"
SubWidget::SubWidget(QWidget* parent):QWidget(parent)
,m_btn(new QPushButton("切换到主窗口",this))
{
//设置标题
setWindowTitle("子窗口");
resize(600, 400);
connect(m_btn, &QPushButton::clicked, [=]() {
this->hide();
//发送切换窗口信号
emit showMainWidget();
});
//或者一句话搞定,和上面一样
//connect(m_btn, &QPushButton::clicked, this, &SubWidget::showMainWidget);
}
- 运行结果