首页 > 代码库 > Qt5官方demo解析集30——Extending QML - Binding Example

Qt5官方demo解析集30——Extending QML - Binding Example

本系列所有文章可以在这里查看http://blog.csdn.net/cloud_castle/article/category/2123873

接上文Qt5官方demo解析集29——Extending QML - Property Value Source Example


还记得我们曾经在Qt5官方demo解析集17——Chapter 3: Adding Property Bindings一文中接触过QML自定义类型的属性绑定吗?如果不记得了,可以移步进行了解。因为项目尺寸的原因,那个例子可能更好理解。

这个例子也是我们Extending QML(扩展QML)系列的最后一个例子了,虽然相较前一个例子也只有小小的改动,不过我们还是把整个工程都完整的看一遍吧~



binding.qrc中是我们的qml文件,它实例化了BirthdayParty类以及其所有的子对象。


Person类建立了一个自定义的QML类型,由于它并不是一个可视化组件,且QML任何组件均基于Qt 的元对象系统,因此继承自QObject。

接着定义了ShoeDescription用来对Person类的shoe属性进行描述,使用特定的方法,我们在对shoe赋值时不需要实例化这个ShoeDescription组件。

再定义两个Person的派生类Boy、Girl,可以用来对Person对象分类。


person.h:

#ifndef PERSON_H
#define PERSON_H

#include <QObject>
#include <QColor>

class ShoeDescription : public QObject
{
    Q_OBJECT
    Q_PROPERTY(int size READ size WRITE setSize NOTIFY shoeChanged)        // NOTIFY用在属性绑定,当该属性值发生改变时发出信号shoeChanged
    Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY shoeChanged)  // 通过该信号,我们就能使得被绑定的属性值随之发生改变
    Q_PROPERTY(QString brand READ brand WRITE setBrand NOTIFY shoeChanged)
    Q_PROPERTY(qreal price READ price WRITE setPrice NOTIFY shoeChanged)
public:
    ShoeDescription(QObject *parent = 0);

    int size() const;
    void setSize(int);

    QColor color() const;
    void setColor(const QColor &);

    QString brand() const;
    void setBrand(const QString &);

    qreal price() const;
    void setPrice(qreal);
signals:
    void shoeChanged();                      // 定义该shoeChanged()信号

private:
    int m_size;
    QColor m_color;
    QString m_brand;
    qreal m_price;
};

class Person : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
// ![0]
    Q_PROPERTY(ShoeDescription *shoe READ shoe CONSTANT)
// ![0]
public:
    Person(QObject *parent = 0);

    QString name() const;
    void setName(const QString &);

    ShoeDescription *shoe();
signals:
    void nameChanged();

private:
    QString m_name;
    ShoeDescription m_shoe;
};

class Boy : public Person
{
    Q_OBJECT
public:
    Boy(QObject * parent = 0);
};

class Girl : public Person
{
    Q_OBJECT
public:
    Girl(QObject * parent = 0);
};

#endif // PERSON_H




person.cpp:

#include "person.h"

ShoeDescription::ShoeDescription(QObject *parent)
: QObject(parent), m_size(0), m_price(0)
{
}

int ShoeDescription::size() const
{
    return m_size;
}

void ShoeDescription::setSize(int s)
{
    if (m_size == s)
        return;

    m_size = s;
    emit shoeChanged();                   // 该信号应该在该属性被正确写入后发出
}

QColor ShoeDescription::color() const
{
    return m_color;
}

void ShoeDescription::setColor(const QColor &c)
{
    if (m_color == c)
        return;

    m_color = c;
    emit shoeChanged();
}

QString ShoeDescription::brand() const
{
    return m_brand;
}

void ShoeDescription::setBrand(const QString &b)
{
    if (m_brand == b)
        return;

    m_brand = b;
    emit shoeChanged();
}

qreal ShoeDescription::price() const
{
    return m_price;
}

void ShoeDescription::setPrice(qreal p)
{
    if (m_price == p)
        return;

    m_price = p;
    emit shoeChanged();
}

Person::Person(QObject *parent)
: QObject(parent)
{
}

QString Person::name() const
{
    return m_name;
}

void Person::setName(const QString &n)
{
    if (m_name == n)
        return;

    m_name = n;
    emit nameChanged();
}

ShoeDescription *Person::shoe()
{
    return &m_shoe;
}


Boy::Boy(QObject * parent)
: Person(parent)
{
}


Girl::Girl(QObject * parent)
: Person(parent)
{
}


接下来是我们的主类BirthdayParty,它也是example.qml中的根项目。它有一个以Person指针为参数的host属性,用来指明寿星;有一个以Person列表指针为参数guests属性,用来指明客人,并且该属性被设置为默认属性,这样在QML中没有指明属性的值将被划归它的名下;一个announcement属性,用来被动态改变以播放歌词。另外,该类还定义了一个partyStarted()信号,我们可以在QML中使用onPartyStarted 来响应该信号。


此外,再定义一个BirthdayPartyAttached类,它用来为BirthdayParty提供一个附加属性。


birthdayparty.h:

#ifndef BIRTHDAYPARTY_H
#define BIRTHDAYPARTY_H

#include <QObject>
#include <QDate>
#include <QDebug>
#include <qqml.h>
#include "person.h"

class BirthdayPartyAttached : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QDate rsvp READ rsvp WRITE setRsvp NOTIFY rsvpChanged)  // 该例中大多数属性均定义了属性绑定
public:
    BirthdayPartyAttached(QObject *object);

    QDate rsvp() const;
    void setRsvp(const QDate &);

signals:
    void rsvpChanged();

private:
    QDate m_rsvp;
};

class BirthdayParty : public QObject
{
    Q_OBJECT
// ![0]
    Q_PROPERTY(Person *host READ host WRITE setHost NOTIFY hostChanged)
// ![0]
    Q_PROPERTY(QQmlListProperty<Person> guests READ guests)
    Q_PROPERTY(QString announcement READ announcement WRITE setAnnouncement)
    Q_CLASSINFO("DefaultProperty", "guests")
public:
    BirthdayParty(QObject *parent = 0);

    Person *host() const;
    void setHost(Person *);

    QQmlListProperty<Person> guests();
    int guestCount() const;
    Person *guest(int) const;

    QString announcement() const;
    void setAnnouncement(const QString &);

    static BirthdayPartyAttached *qmlAttachedProperties(QObject *);

    void startParty();
signals:
    void partyStarted(const QTime &time);
    void hostChanged();

private:
    Person *m_host;
    QList<Person *> m_guests;
};

QML_DECLARE_TYPEINFO(BirthdayParty, QML_HAS_ATTACHED_PROPERTIES)

#endif // BIRTHDAYPARTY_H

birthdayparty.cpp:

#include "birthdayparty.h"

BirthdayPartyAttached::BirthdayPartyAttached(QObject *object)
: QObject(object)
{
}

QDate BirthdayPartyAttached::rsvp() const
{
    return m_rsvp;
}

void BirthdayPartyAttached::setRsvp(const QDate &d)
{
    if (d != m_rsvp) {
        m_rsvp = d;
        emit rsvpChanged();
    }
}


BirthdayParty::BirthdayParty(QObject *parent)
: QObject(parent), m_host(0)
{
}

Person *BirthdayParty::host() const
{
    return m_host;
}

void BirthdayParty::setHost(Person *c)
{
    if (c == m_host) return;
    m_host = c;
    emit hostChanged();
}

QQmlListProperty<Person> BirthdayParty::guests()
{
    return QQmlListProperty<Person>(this, m_guests);
}

int BirthdayParty::guestCount() const
{
    return m_guests.count();
}

Person *BirthdayParty::guest(int index) const
{
    return m_guests.at(index);
}

void BirthdayParty::startParty()
{
    QTime time = QTime::currentTime();
    emit partyStarted(time);
}

QString BirthdayParty::announcement() const
{
    return QString();
}

void BirthdayParty::setAnnouncement(const QString &speak)
{
    qWarning() << qPrintable(speak);
}

BirthdayPartyAttached *BirthdayParty::qmlAttachedProperties(QObject *object)
{
    return new BirthdayPartyAttached(object);
}



在该系列第9个例子中,我们接触到了HappyBirthdaySong类,它是一个自定义的Property Value Source,用来为QML属性提供随时间变化的能力,类似于Animation。在该例子中,它被用于announcement属性。

happybirthdaysong.h:

#ifndef HAPPYBIRTHDAYSONG_H
#define HAPPYBIRTHDAYSONG_H

#include <QQmlPropertyValueSource>
#include <QQmlProperty>

#include <QStringList>

class HappyBirthdaySong : public QObject, public QQmlPropertyValueSource
{
    Q_OBJECT
    Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
    Q_INTERFACES(QQmlPropertyValueSource)
public:
    HappyBirthdaySong(QObject *parent = 0);

    virtual void setTarget(const QQmlProperty &);

    QString name() const;
    void setName(const QString &);

private slots:
    void advance();

signals:
    void nameChanged();
private:
    int m_line;
    QStringList m_lyrics;
    QQmlProperty m_target;
    QString m_name;
};

#endif // HAPPYBIRTHDAYSONG_H


happybirthdaysong.cpp:

#include "happybirthdaysong.h"
#include <QTimer>

HappyBirthdaySong::HappyBirthdaySong(QObject *parent)
: QObject(parent), m_line(-1)
{
    setName(QString());
    QTimer *timer = new QTimer(this);
    QObject::connect(timer, SIGNAL(timeout()), this, SLOT(advance()));
    timer->start(1000);
}

void HappyBirthdaySong::setTarget(const QQmlProperty &p)
{
    m_target = p;
}

QString HappyBirthdaySong::name() const
{
    return m_name;
}

void HappyBirthdaySong::setName(const QString &name)
{
    if (m_name == name)
        return;

    m_name = name;

    m_lyrics.clear();
    m_lyrics << "Happy birthday to you,";
    m_lyrics << "Happy birthday to you,";
    m_lyrics << "Happy birthday dear " + m_name + ",";
    m_lyrics << "Happy birthday to you!";
    m_lyrics << "";
    
    emit nameChanged();
}
    
void HappyBirthdaySong::advance()
{
    m_line = (m_line + 1) % m_lyrics.count();

    m_target.write(m_lyrics.at(m_line));
}

在main.cpp中将这些C++类注册成QML类型后,我们就可以在QML中创建一个实例化的BirthdayParty,并对其属性赋值:

example.qml:

import People 1.0
import QtQuick 2.0  // For QColor

// ![0]
BirthdayParty {
    id: theParty

    HappyBirthdaySong on announcement { name: theParty.host.name }      // 属性绑定

    host: Boy {
        name: "Bob Jones"
        shoe { size: 12; color: "white"; brand: "Nike"; price: 90.0 }
    }
// ![0]
    onPartyStarted: console.log("This party started rockin' at " + time);


    Boy {
        name: "Leo Hodges"
        BirthdayParty.rsvp: "2009-07-06"
        shoe { size: 10; color: "black"; brand: "Reebok"; price: 59.95 }
    }
    Boy {
        name: "Jack Smith"
        shoe { size: 8; color: "blue"; brand: "Puma"; price: 19.95 }
    }
    Girl {
        name: "Anne Brown"
        BirthdayParty.rsvp: "2009-07-01"
        shoe.size: 7
        shoe.color: "red"
        shoe.brand: "Marc Jacobs"
        shoe.price: 699.99
    }

// ![1]
}
// ![1]

最后,在main.cpp调用这个属性的信息,并基于一定的规则输出这些信息:
#include <QCoreApplication>
#include <QQmlEngine>
#include <QQmlComponent>
#include <QDebug>
#include "birthdayparty.h"
#include "happybirthdaysong.h"
#include "person.h"

int main(int argc, char ** argv)
{
    QCoreApplication app(argc, argv);
    qmlRegisterType<BirthdayPartyAttached>();
    qmlRegisterType<BirthdayParty>("People", 1,0, "BirthdayParty");
    qmlRegisterType<HappyBirthdaySong>("People", 1,0, "HappyBirthdaySong");
    qmlRegisterType<ShoeDescription>();
    qmlRegisterType<Person>();
    qmlRegisterType<Boy>("People", 1,0, "Boy");
    qmlRegisterType<Girl>("People", 1,0, "Girl");

    QQmlEngine engine;
    QQmlComponent component(&engine, QUrl("qrc:example.qml"));
    BirthdayParty *party = qobject_cast<BirthdayParty *>(component.create());

    if (party && party->host()) {
        qWarning() << party->host()->name() << "is having a birthday!";

        if (qobject_cast<Boy *>(party->host()))
            qWarning() << "He is inviting:";
        else
            qWarning() << "She is inviting:";

        for (int ii = 0; ii < party->guestCount(); ++ii) {
            Person *guest = party->guest(ii);

            QDate rsvpDate;
            QObject *attached = 
                qmlAttachedPropertiesObject<BirthdayParty>(guest, false);
            if (attached)
                rsvpDate = attached->property("rsvp").toDate();

            if (rsvpDate.isNull())
                qWarning() << "   " << guest->name() << "RSVP date: Hasn't RSVP'd";
            else
                qWarning() << "   " << guest->name() << "RSVP date:" << qPrintable(rsvpDate.toString());
        }

        party->startParty();
    } else {
        qWarning() << component.errors();
    }

    return app.exec();
}

输出如下: