效果是这样的,点击boy长大一岁或者girl长大一岁
qt的文件构造都是两个头文件三个源文件,源文件中有个cpp文件,它是程序的入口,一个项目中只能有一个main函数
#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
//QApplication 类管理 GUI 应用程序的控制流和主设置
QApplication a(argc, argv);
//Widget w;: 创建一个 Widget 类的实例 w。这通常会设置应用程序的主窗口以及其内部的各种控件和局
//Widget类继承自QWidget,所以自带show方法
Widget w;
//w.show();: 调用 Widget 实例的 show 方法来显示窗口。在创建窗口后,默认情况下窗口是隐藏的,调
//用 show 方法会使窗口可见。
w.show();
//调用 exec 方法启动应用程序的事件循环。
return a.exec();
}
接下来实现一个元对象,首先,我们把这个元对象命名为TPerson
首先创建元对象New file->C++class->选择->命名为TPerson->Base class(基类)选择QObject->下一步->完成
#ifndef TPERSON_H
#define TPERSON_H
#include <QObject>
class TPerson : public QObject
{
Q_OBJECT
//首先我们定义了类的信息(class information),后续可以通过元对象系统查询
Q_CLASSINFO("author","Yu");
Q_CLASSINFO("company","SCNU");
Q_CLASSINFO("version","1.0.0");
//用于声明属性,READ是读取方法,WRITE是写入方法,NOTIFY是信号,MEMBER是成员变量
//目的:增加代码可读性。
Q_PROPERTY(int age READ age WRITE setAge NOTIFY ageChanged);
Q_PROPERTY(QString name MEMBER m_name);
Q_PROPERTY(int score MEMBER m_score);
public:
//元对象要有名字和基类,而该基类设为nullptr
explicit TPerson(QString name,QObject *parent = nullptr);
signals:
private:
//QString m_sex;
//定义这个类的信息,一个人有名字,有年龄……
QString m_name;
int m_age=10;
int m_score=79;
};
#endif // TPERSON_H
其中explicit(明确的)关键字可以避免隐式类型转换,及要明确指明类型,而不能由其它类型转换为该类型。
例如
class MyClass {
public:
explicit MyClass(int x) { /* ... */ }
};
void func(MyClass m) { /* ... */ }
int main() {
func(10); // 编译错误,不能隐式转换(int转MyClass)
func(MyClass(10)); // 明确的转换,符合预期
}
接下来我们在元对象的public下面实现四个接口
public:
explicit TPerson(QString name,QObject *parent = nullptr);
~TPerson();
int age();
void setAge(quint8 ageValue);
void incAge();
右键接口->重构(reconfiguration)到tperson.cpp文件当中。接下来我们在重构中实现这几个函数 。
//析构函数,退出销毁时打印一个销毁信息
TPerson::~TPerson()
{
qDebug("TPerson类的对象被删除");
}
//age函数,调用时返回该对象的年龄
int TPerson::age()
{
return m_age;
}
//设置年龄,ageValue是替换值,而m_age是它原本的年龄,如果你希望年龄只能增不能减可以把
//m_age!=ageValue改成m_age<ageValue,emit用于后面的槽函数
void TPerson::setAge(quint8 ageValue)
{
if(m_age!=ageValue){
m_age=ageValue;
//发送信号
emit ageChanged(m_age);
}
}
//年龄增加函数,调用时++,发送回去
void TPerson::incAge()
{
++m_age;
emit ageChanged(m_age);
}
下面我们要用signal关键字定义一个信号,用于上面的emit,
在 Qt 中,signals 关键字用于声明信号。信号是 Qt 对象间通信的一种机制,通过它,一个对象可以广播特定事件的发生,而其他对象可以通过槽(slots)来监听这些事件。void ageChanged(int ageValue); 是信号的声明。具体来讲,这一行定义了一个名为 ageChanged 的信号,当某个对象的年龄改变时,这个信号可以被发射(emit)。int ageValue 是传给监听该信号的槽函数的参数,表明年龄已更改为 ageValue。
signals:
void ageChanged(int ageValue);
目前的流程是定义函数->定义信号->在函数里使用emit发送信号,接下来还需要定义槽函数->使用connect函数把signals信号函数(ageChanged)和槽函数连接起来。
现在我们先来捋一下目前都做了哪些事情1.定义的两个类一个Widget类它的基类是QWidget,第二个是TPerson类它的基类是QObject。2.在TPerson中使用Q_CLASSINFO写了几个信息。定义了一个信号ageChanged并在三个函数中使用。在public中实现了三个函数age用于返回对象的年龄,setage用于更改对象的年龄,incage用于增加对象的年龄
现在TPerson写好了,该写Widget了。在这之前我们要先在ui界面中把按钮放在合适的位置。并更改名称。
详细见下面这个up主的视频,拖动图片的操作不好讲,看视频更加合适
3.3.5元对象系统功能示例_哔哩哔哩_bilibili
有前置操作。我们再来写Widget
这是头文件
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
//声明TPerson类才能使用
class TPerson;
QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private:
//声明两个对象,一个boy一个girl
TPerson* boy;
TPerson* girl;
private slots:
//声明六个槽函数
void do_ageChanged(int value);
void do_spinChanged(int arg1);
void on_boyinc_clicked();
void on_girlinc_clicked();
void on_clear_clicked();
void on_metainfo_clicked();
private:
Ui::Widget *ui;
};
#endif // WIDGET_H
接下来写具体实现
在这之前先讲讲connect,第一个参数是对象发送者,也就是TPerson,在它的实现里,是不是实现了两个带有emit的函数,触发时他会发送SIGNAL给到接收者也就是第三个参数,然后执行SLOT中的函数。
//把带有定义的头文件都引入
#include "widget.h"
#include "ui_widget.h"
#include "tperson.h"
#include<QMetaProperty>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
//创建并用setProperty设置信息,后面会用property获取这些信息
boy=new TPerson("小明",this);
boy->setProperty("sex","boy");
boy->setProperty("age","10");
boy->setProperty("score","70");
girl=new TPerson("小丽",this);
girl->setProperty("sex","girl");
girl->setAge(20);
//便于后面找到它是男孩还是女孩,我猜测setProperty应该是个map,映射结构那种
ui->boyage->setProperty("isBoy",true);
ui->girlage->setProperty("isBoy",false);
//当触发信号时,会传递给boyage,boyage是一个spinbox,本身自带setValue函数用于修改界面的显
//示.
connect(boy,SIGNAL(ageChanged(int)),ui->boyage,SLOT(setValue(int)));
connect(girl,SIGNAL(ageChanged(int)),ui->girlage,SLOT(setValue(int)));
//触发控件,do_ageChanged用来将信息打印到plainTextEdit上,详见下面的实现
//为什么使用this,因为do_ageChanged函数是Widget类里的,
connect(boy,SIGNAL(ageChanged(int)),this,SLOT(do_ageChanged(int)));
connect(girl,SIGNAL(ageChanged(int)),this,SLOT(do_ageChanged(int)));
//这个也是同理,说白了,就是一个类里的函数发送了信号,另一个类调用指定函数
//两个类之间的通讯
connect(ui->boyage,SIGNAL(valueChanged(int)),this,SLOT(do_spinChanged(int)));
connect(ui->girlage,SIGNAL(valueChanged(int)),this,SLOT(do_spinChanged(int)));
}
//构析函数,没什么好讲的
Widget::~Widget()
{
delete ui;
}
//
void Widget::do_ageChanged(int value)
{
//获取发送者
TPerson*person=qobject_cast<TPerson*>(sender());
//用property调用信息,名字,性别年龄
QString str=QString("%1,%2,年龄=%3").arg(person->property("name").toString()).arg(person->property("sex").toString()).arg(value);
//显示到plainTextEdit上
ui->plainTextEdit->appendPlainText(str);
}
void Widget::do_spinChanged(int arg1)
{
Q_UNUSED(arg1);
QSpinBox *spinBox=qobject_cast<QSpinBox*>(sender());
//true就set boy age
if(spinBox->property("isBoy").toBool())
boy->setAge(arg1);
else
girl->setAge(arg1);
}
//下面两个其实没用到
void Widget::on_boyinc_clicked()
{
boy->incAge();
}
void Widget::on_girlinc_clicked()
{
girl->incAge();
}
//调用自带的clear函数
void Widget::on_clear_clicked()
{
ui->plainTextEdit->clear();
}
void Widget::on_metainfo_clicked()
{
//获取元对象
const QMetaObject * meta=boy->metaObject();
ui->plainTextEdit->appendPlainText(QString("类名称:%1\n").arg(meta->className()));
ui->plainTextEdit->appendPlainText("属性:");
//offset开端,count数量
for(int i=meta->propertyOffset();i<meta->propertyCount();i++){
//用property(i).name()获取键
const char* propName=meta->property(i).name();
//获取值
QString propValue=boy->property(propName).toString();
//打印到plain上,arg参数
ui->plainTextEdit->appendPlainText(QString("属性名称=%1,属性值=%2").arg(propName).arg(propValue));
}
ui->plainTextEdit->appendPlainText("\n类信息(classInfo):");
//获取类信息,类信息类型是QMetaClassInfo
for(int i=meta->classInfoOffset();i<meta->classInfoCount();i++){
QMetaClassInfo classInfo=meta->classInfo(i);
ui->plainTextEdit->appendPlainText(QString("Name=%1,Value=%2").arg(classInfo.name()).arg(classInfo.value()));
}
}
总结一下:
1.类名->setProperty(键,值)对应char * name=property(i).name()键,property(name)值。propertyOffset起始下标,propertyCount()数量
2. 同理,Q_CLASSINFO("author","Yu");设置键值对,classInfo()是一个pair(QMetaClassInfo类型),classInfo.name键,value值。
3.ui->setupUi(this);设置这个QWidget为界面。
4.connect(ui->boyage,SIGNAL(valueChanged(int)),this,SLOT(do_spinChanged(int)));发送者,发送者里的函数,接受者,接受者里的函数。
5.spinbox具有valueChanged(类型)函数还有setValue(类型)函数。
6.signals:可以定义信号emit可以发送信号。sender()可以获取发送者地址
7.当新增文件时需要在pro文件里加入项目名称
差不多就这些了,到此第一个项目就完美结束了。
不得不说写一个简单的小程序还是很难的。算法更多的是难懂,项目更多的是复杂性,结构和逻辑都很重要,