Niklas
Niklas

Reputation: 249

QVariant to QList with unkown list template parameter

I have several QObject classes, which may have QList properties, which store lists of QObject derived classes:

class Object : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QList<AnotherQObject*> otherObjects READ otherObjects)
public:
    QList<AnotherQObject*> otherObjects() const;
}

QList<AnotherObject*> has of course been registered with Q_DECLARE_METATYPE and qRegisterMetaType().

Now I want to read this property with Qt's meta object system. At the point where I want to read the list, I only care about (and know) the fact that the list contains pointers to objects, which have been derived from QObject, but not the exact type.

When I convert the property's QVariant to QList<QObject *>the list is empty.

QObject *object = getObjectFromSomeWhere();
const char* propertyName = getPropertyNameFromSomeWhere(); // "otherObjects"

QVariant list = object->property(name);
QList<QObject *> objectList = list.value<QList<QObject *> >(); // empty

QList<AnotherQObject*> anotherObjectList = 
    static_cast<Object*>(object)->otherObjects() // not empty

Is it possible to access this list? Do you have other suggestions?

EDIT: Although I posted my solution, I would still love comments :-)

Upvotes: 2

Views: 3175

Answers (2)

dbrianj
dbrianj

Reputation: 472

If you're using Qt 5.2 or higher there is a QSequentialIterable class that allows you to do this without the overhead of extra templates, modified macros, and additional registrations.

http://qt-project.org/doc/qt-5/qsequentialiterable.html

If you have a QList<T*> stored in a QVariant, where T is derived from QObject, you can get a QSequentialIterable instance from the variant, which allows you to iterate over each item in the list as a QVariant. Then you can simply get the value of each item as a QObject*, adding to a QList<QObject*> as you go (make sure you test the assumption that the type is derived from QObject, though):

QList<QObject*> qobjectList;
if (variant.canConvert(QMetaType::QVariantList)) {
    QSequentialIterable iterable = variant.value<QSequentialIterable>();
    foreach (const QVariant& item, iterable) {
        QObject* object = item.value<QObject*>();
        if (object) { qobjectList.append(object); }
    }
}

Upvotes: 3

Niklas
Niklas

Reputation: 249

I think I solved the issue quite minimalistically. I created a template class, which can convert a QVariant containing a QList<TemplateParameter *> to QList<QObject *>.

Also I have a singleton, which stores a QMap<int, Converter *>, which maps from QVariant::userType() to the according converter.

This allows me to register each class, which I want to store in the properties as follows:

VariantToObjectListConverter::instance()->registerConverter<AnotherQObject>();

I can then convert unknown QVariants:

QVariant list = object->property("otherObjects");
QList<QObject *> objects = VariantToObjectListConverter::instance()->convert(variant);

This is the necessary code:

class ConverterBase
{
public:
    virtual ~ConverterBase() {}
    virtual QList<QObject *> convert(const QVariant &variant) const = 0;
};

template<class Object>
class Converter : public ConverterBase
{
public:
    QList<QObject *> convert(const QVariant &variant) const
    {
        QList<Object *> list = variant.value<QList<Object *> >();
        QList<QObject *> result;
        Q_FOREACH(Object *object, list) result.append(object);
        return result;
    }
};

class VariantToObjectListConverter
{
public:
    Q_GLOBAL_STATIC(VariantToObjectListConverter, instance)

    ~VariantToObjectListConverter()
    {
        QHashIterator<int, ConverterBase *> i(m_converters);
        while (i.hasNext()) {
            i.next();
            delete i.value();
        }
    }

    template<class Object>
    void registerConverter()
    {
        QVariant v = QVariant::fromValue<QList<Object *> >(QList<Object *>());
        m_converters.insert(v.userType(), new Converter<Object>());
    }

    QList<QObject *> convert(const QVariant &variant) const
    {
        ConverterBase *converter = m_converters.value(variant.userType());
        if (!converter)
            return QList<QObject *>();

        return converter->convert(variant);
    }

private:
    QHash<int, ConverterBase *> m_converters;

    VariantToObjectListConverter() {}
};

I could now add a macro, which calls registerConverter(), Q_DECLARE_METATYPE and qRegisterMetaType(), so that the user only has to call

REGISTER_TYPE(TypeName)

Upvotes: 2

Related Questions