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);
}
- 运行结果