Qt第一个项目(元对象系统)

效果是这样的,点击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文件里加入项目名称

差不多就这些了,到此第一个项目就完美结束了。

不得不说写一个简单的小程序还是很难的。算法更多的是难懂,项目更多的是复杂性,结构和逻辑都很重要,

  • 32
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值