PDA

View Full Version : Q_PROPERTY of type QMap



eclarkso
29th October 2009, 16:33
I'd like to have a Q_PROPERTY of type QMap<GtVariant,int>, where GtVariant subclasses QVariant and implements operator< and a global qHash function. The docs state:


For QMap, QList, and QValueList properties, the property value is a QVariant whose value is the entire list or map. Note that the Q_PROPERTY string cannot contain commas, because commas separate macro arguments. Therefore, you must use QMap as the property type instead of QMap<QString,QVariant>
http://doc.trolltech.com/4.5/properties.html

I parse that to mean my property declaration should look like:



Q_PROPERTY(QMap values READ getValues WRITE setValues)

public:
QMap<GtVariant,int> getValues();
void setValues(QMap<GtVariant,int> values);


Perhaps that's incorrect? Regardless, I'm getting inscrutable-to-me moc errors (see below). If the above is indeed correct, then I suspect my problem relates to the interaction of my QVariant-derived class within a QMap; I can post additional details assuming I get a response to this. Thanks to anyone for your review.


debug\moc_abstractfilterwidget.cpp: In member function `virtual int AbstractFilterWidget::qt_metacall(QMetaObject::Cal l, int, void**)':
debug\moc_abstractfilterwidget.cpp:93: error: no match for 'operator=' in '*(QMap<QString, QVariant>*)_v = AbstractFilterWidget::getValues()()'
c:/Qt/2009.04/qt/include/QtCore/../../src/corelib/tools/qmap.h:396: note: candidates are: QMap<aKey, aT>& QMap<Key, T>::operator=(const QMap<Key, T>&) [with Key = QString, T = QVariant]
debug\moc_abstractfilterwidget.cpp:100: error: no matching function for call to `AbstractFilterWidget::setValues(QMap<QString, QVariant>&)'
debug\../abstractfilterwidget.h:26: note: candidates are: void AbstractFilterWidget::setValues(QMap<GtVariant, int>)

high_flyer
29th October 2009, 17:33
Does the following compile?:

Q_PROPERTY(QMap<GtVariant,int> values READ getValues WRITE setValues)

eclarkso
29th October 2009, 18:20
Does the following compile?:

Q_PROPERTY(QMap<GtVariant,int> values READ getValues WRITE setValues)


No--per the docs, Q_PROPERTY cannot contain commas (error is "`Q_PROPERTY` does not name a type").

As futher information, the stuff about my QVariant-derived class and such seems irrelevant now, as replacing 'GtVariant' with 'QString' above triggers the same moc errors.

Below is the entire widget header for further reference, which triggers the same moc error output as the original post:



#ifndef ABSTRACTFILTERWIDGET_H
#define ABSTRACTFILTERWIDGET_H

#include <QtGui>
#include <QMap>

class AbstractFilterWidget : public QFrame
{

Q_OBJECT

public:
Q_PROPERTY(QString name READ getName WRITE setName)
Q_PROPERTY(QMap values READ getValues WRITE setValues)

AbstractFilterWidget(QWidget * parent = 0);
AbstractFilterWidget(QString n, QString valQueryString, QWidget * parent = 0);
AbstractFilterWidget(QString n, QMap<QString,int> values, QWidget * parent = 0);

QString getName();
void setName(QString name);

QMap<QString,int> getValues();
void setValues(QMap<QString,int> values);

private:
QString facetName;
QMap<QString,int> facetValues;

void initialize(QString n, QMap<QString,int> & vals, QWidget * parent);
};

#endif // ABSTRACTFILTERWIDGET_H

high_flyer
29th October 2009, 18:47
No--per the docs, Q_PROPERTY cannot contain commas (error is "`Q_PROPERTY` does not name a type").
Oops, sorry.

When I read the doc you quoted:

For QMap, QList, and QValueList properties, the property value is a QVariant whose value is the entire list or map.

I understand the following:

Q_PROPERTY(QMap values READ getValues WRITE setValues)

QVariant getValues();
void setValues(QVariant values);



And I guess you go something like:

QMap<GtVariant,int> values = pObject->getValues().value<QMap<GtVariant,int> >();



When you want to use it.
See docs about QVariant and userTypes.

eclarkso
29th October 2009, 19:29
Oops, sorry.

When I read the doc you quoted:


For QMap, QList, and QValueList properties, the property value is a QVariant whose value is the entire list or map.

I understand the following:

Q_PROPERTY(QMap values READ getValues WRITE setValues)

QVariant getValues();
void setValues(QVariant values);




Hm, I read the doc. w/r/t storing QMaps, etc. in a QVariant as something it did internally, not something that the user needs to mess with. Especially because of the next sentence:


Therefore, you must use QMap as the property type instead of QMap<QString,QVariant>.

In any case, the usage of QVariant in the getters/setters mixed with a QMap property type as above isn't right, since declaring a Q_PROPERTY with a QMap type is going to demand getters and setters that use that class (I verified this). Did you mean for the property type to be QVariant too?



And I guess you go something like:

QMap<GtVariant,int> values = pObject->getValues().value<QMap<GtVariant,int> >();



When you want to use it.
See docs about QVariant and userTypes.

I'll try this, but that seems awfully cumbersome. I wish there were example somewhere, since I'm not the first to have this uncertainty:

http://lists.trolltech.com/qt-interest/2006-03/thread01529-0.html

wysota
29th October 2009, 19:48
Let's start from the beginning maybe... Why do you need the GtVariant class?

eclarkso
29th October 2009, 20:13
Let's start from the beginning maybe... Why do you need the GtVariant class?

I'm happy to go into that, but note above that this issue is independent of using that class(I should change the thread title to "Q_PROPERTY of type QMap").

But if you REALLY want to know: I need a widget that stored pairs of values and counts (at lets me do contains() and count retrieval easily). A QMap or QHash is a pretty standard way of handling that kind of thing.

My values can be numbers or strings, but either way they're handled exactly the same way within the widget class--so it would be good to use a single class that could store data of either type. QVariant is an obvious candidate, but it can't be used as the key type for a QMap (no operator<) or QHash (no global qHash). My solution was subclassing QVariant and implementing one or both of the above functions, which seems preferable to having WidgetForStrings and WidgetForInt with literally the only difference being a property/member variable QMap<QString,int> vs. QMap<int,int>.

But as I said, the original error occurs regardless of what key type I use for QMap, so I'm not sure what I'm doing wrong.

wysota
29th October 2009, 21:57
I'm happy to go into that, but note above that this issue is independent of using that class(I should change the thread title to "Q_PROPERTY of type QMap").
Not really. Using QMap in Q_PROPERTY is easy, just use typedef or subclass to avoid using a comma inside the macro. That's not a problem.


QVariant is an obvious candidate,
How about just QString? It can store both integers and texts as well. That would leave you with QVariantMap (or even QMap<QString,int>) which is already supported.


but it can't be used as the key type for a QMap (no operator<) or QHash (no global qHash). My solution was subclassing QVariant and implementing one or both of the above functions,
You don't need to subclass QVariant to do that. Just implement the following function, it should work:

bool operator<(const QVariant &v1, const QVariant &v2);

On the other hand I'm not sure how you would like to use this structure...


QMap<QVariant, QVariant> map;
map[1000] = "x";
map["1000"] = "x";
// which key is "less", the first or the second one?


which seems preferable to having WidgetForStrings and WidgetForInt with literally the only difference being a property/member variable QMap<QString,int> vs. QMap<int,int>.
There are templates, you know...


template <typename T> MyClass {
QMap<T, int> map;
};

eclarkso
29th October 2009, 23:34
Not really. Using QMap in Q_PROPERTY is easy, just use typedef or subclass to avoid using a comma inside the macro. That's not a problem.

I suppose, though I thought it easier just to remove the property declaration and not introducing basically unnecessary typedef/subclassing.



How about just QString? It can store both integers and texts as well. That would leave you with QVariantMap (or even QMap<QString,int>) which is already supported.

That's something I did just to get some further testing done, but in the larger scope I could be using Date or DateTime values as well, and at some point it becomes difficult and/or error prone to determine the 'actual' datatype from the string content.



You don't need to subclass QVariant to do that. Just implement the following function, it should work:


bool operator<(const QVariant &v1, const QVariant &v2);

On the other hand I'm not sure how you would like to use this structure...


It might should work... but it didn't for me (same moc errors). As for how it would be used: operator< is arbitrary for variants of different types and of types that don't make sense (i.e., not int/double/String/Date/etc.), and otherwise returns the natural operation.



There are templates, you know...


template <typename T> MyClass {
QMap<T, int> map;
};

Templates are a great idea and would be my preferred solution were it not for the interaction of the Q_OBJECT macro with templates (since my class is QWidget-derived and needs signals/slots, I need Q_OBJECT).

http://lists.trolltech.com/qt-interest/2006-02/thread00693-0.html
http://doc.trolltech.com/qq/qq15-academic.html

There are workarounds, they don't really appeal to me. My solution (which I've verified works) is just to not declare my QMap as a Q_PROPERTY (but keep the same getters/setters), and I can use my GtVariant subclass (and use it with a QHash if I need to later). I lose property introspection and whatnot, but for my specific use case it's not such a big deal.

Thanks for the help everyone.

wysota
30th October 2009, 00:04
That's something I did just to get some further testing done, but in the larger scope I could be using Date or DateTime values as well, and at some point it becomes difficult and/or error prone to determine the 'actual' datatype from the string content.
I guess it all boils down to a question whether you can have more than one data type as a key at once. If not, then QVariant will just be a liability sooner or later.


As for how it would be used: operator< is arbitrary for variants of different types and of types that don't make sense (i.e., not int/double/String/Date/etc.), and otherwise returns the natural operation.
But you have to return something meaningful in all cases. And if you want to detect variant types, you always have to explicitly go through QVariant constructor, i.e. you can't write:

map["2009-09-01"] = x;
You have to write:
map[QDate::fromString("2009-09-01", Qt::ISODate)] = x;


Templates are a great idea and would be my preferred solution were it not for the interaction of the Q_OBJECT macro with templates (since my class is QWidget-derived and needs signals/slots, I need Q_OBJECT).
This is easy to overcome actually. First subclass a base class with Q_OBJECT macro and then subclass again with templates (and without Q_OBJECT macro).


class X : public QObject {
Q_OBJECT
};

template <typename T> Y {
QMap<T,int> map;
};

Of course you can't use Q_PROPERTY in the final subclass.

eclarkso
30th October 2009, 14:44
I agree the QVariant-as-key starts to get a little dangerous, but necessary I think for me. In particular, we have both float values and version numbers as values. The string representations can be indistinguishable (e.g., 4.5), so we need to retain type information somehow. This is also important because farther down the chain we make decisions on how to display the values based on their type (e.g., as a list of nominal categories or as a line chart).

I appreciate your points re: template workarounds, but having all these functionally unnecessary subclasses and whatnot to work around offends my sense of neatness :). Basically, it offense my neatness less to remove the Q_PROPERTY declaration and not template-enforce that QMap values will be of the same type per instance.

[And for any future googlers of this thread, I'm still confused as to the proper declaration/usage of Q_PROPERTY with QMap/QHash, sorry.]

wysota
30th October 2009, 18:43
Doesn't it offend your sense neatness to use QVariant in the fiirst place? Especially that if you intend to use QVariant as the key, it will lead to spaghetti code when trying to determine the type of the key.


[And for any future googlers of this thread, I'm still confused as to the proper declaration/usage of Q_PROPERTY with QMap/QHash, sorry.]
I already told you how to do it.


typedef QMap<QString, int> StringIntMap;
class X : public QObject {
Q_OBJECT
Q_PROPERTY(StringIntMap name READ name WRITE setName)
//...
};