我现在已经使用Qt了一段时间,我觉得他们的Property系统是如何运作的。
QPushButton *button = new QPushButton; // inherits a QObject
button->setProperty("down", true);
button->setProperty("angle", 35.0);
QVariant value = button->property("angle");
我开始想知道如何实现它。是什么让这么容易使用?
答案 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"