如何实现类似Qt的Property系统?

时间:2016-09-02 21:15:51

标签: c++ qt

我现在已经使用Qt了一段时间,我觉得他们的Property系统是如何运作的。

QPushButton *button = new QPushButton; // inherits a QObject
button->setProperty("down", true);
button->setProperty("angle", 35.0);

QVariant value = button->property("angle");

我开始想知道如何实现它。是什么让这么容易使用?

1 个答案:

答案 0 :(得分:1)

一旦你有一个合适的变体类,这很容易。您只需要一个从名称到变体的地图:

#include <map>
#include <string>
#include <cassert>
#include <boost/variant.hpp>

class WithProperties {
public:
    using variant = boost::variant<std::string, int, double, bool>;
    template <typename T> T property(const char * name) const {
        auto it = m_properties.find(name);
        if (it != m_properties.end()) return boost::get<T>(it->second);
        return T{};
    }
    void setProperty(const char * name, const variant & value) {
        m_properties[name] = value;
    }
    std::vector<std::string> propertyNames() const {
        std::vector<std::string> keys;
        keys.reserve(m_properties.size());
        for (auto prop : m_properties)
            keys.push_back(prop.first);
        return keys;
    }
private:
    std::map<std::string, variant> m_properties;
};

int main() {
    WithProperties prop;
    prop.setProperty("down", true);
    prop.setProperty("angle", 35.0);
    prop.setProperty("name", std::string{"foo"});
    assert(prop.property<bool>("down") == true);
    assert(prop.property<double>("angle") == 35.0);
    assert(prop.property<std::string>("name") == "foo");
}

如果你想知道如何使用Qt的类型完成它,那就更容易了,因为QVariant实现了有用的operator== 构造函数,知道如何处理所有基本的C值类型。

#include <QtCore>

class WithProperties {
public:
    QVariant property(const char * name) const {
        auto it = m_properties.find(name);
        if (it != m_properties.end()) return *it;
        return QVariant{};
    }
    void setProperty(const char * name, const QVariant & value) {
        m_properties[name] = value;
    }
    QList<QByteArray> propertyNames() const {
        return m_properties.keys();
    }
private:
    QMap<QByteArray, QVariant> m_properties;
};

int main() {
    WithProperties prop;
    prop.setProperty("down", true);
    prop.setProperty("angle", 35.0);
    prop.setProperty("name", "foo");
    Q_ASSERT(prop.property("down") == true);
    Q_ASSERT(prop.property("angle") == 35.0);
    Q_ASSERT(prop.property("name") == "foo");
}

Qt的属性系统还做了一件事:它使用了使用Q_PROPERTY声明的静态命名属性。这些可通过元数据获得,并与动态属性集成,如上所示。您可以按如下方式实现它(这不是从Qt代码复制的):

#include <QtCore>
#include <cstring>

QMetaProperty findMetaProperty(const QMetaObject * obj, const char * name) {
    auto count = obj->propertyCount();
    for (int i = 0; i < count; ++i) {
        auto prop = obj->property(i);
        if (strcmp(prop.name(), name) == 0)
            return prop;
    }
    return QMetaProperty{};
}

class WithProperties {
    Q_GADGET
    Q_PROPERTY(QString name READ name WRITE setName)
    QString m_name;
public:
    QString name() const { return m_name; }
    void setName(const QString & name) { m_name = name; }
    QVariant property(const char * name) const {
        auto metaProperty = findMetaProperty(&staticMetaObject, name);
        if (metaProperty.isValid())
            return metaProperty.readOnGadget(this);
        auto it = m_properties.find(name);
        if (it != m_properties.end()) return *it;
        return QVariant{};
    }
    void setProperty(const char * name, const QVariant & value) {
        auto metaProperty = findMetaProperty(&staticMetaObject, name);
        if (metaProperty.isValid())
            return (void)metaProperty.writeOnGadget(this, value);
        m_properties[name] = value;
    }
    QList<QByteArray> dynamicPropertyNames() const {
        return m_properties.keys();
    }
private:
    QMap<QByteArray, QVariant> m_properties;
};

int main() {
    WithProperties prop;
    prop.setProperty("down", true);
    prop.setProperty("angle", 35.0);
    prop.setProperty("name", "foo");
    Q_ASSERT(prop.property("down") == true);
    Q_ASSERT(prop.property("angle") == 35.0);
    Q_ASSERT(prop.property("name") == "foo");
    Q_ASSERT(prop.dynamicPropertyNames().size() == 2);
}
#include "main.moc"