Enum property not being recognized as such

915 Views Asked by At

I have been trying to implement something that I believe should be pretty straightforward, but am having issues with it. In short, I have a class that has some Q_Properties, some of which I want to be namespace-level enum values. My issue is, if I move the enum and its namespace into a separate file, the QProperty system doesn't recognize the property is an enum value.

Here is a stripped down version to illustrate:

One file contains a namespace with an enum and the class I'm trying to use with the enums:

#ifndef ENUMPROPERTYTEST_H
#define ENUMPROPERTYTEST_H

#include <QObject>
#include "enumTest.h" //contains the other namespace and enum I want to use

namespace Test_SameFile {
Q_NAMESPACE

enum NSEnum_SameFile {
    A1,
    A2,
    A3
};
Q_ENUM_NS(NSEnum_SameFile)

}

class EnumPropertyTest : public QObject
{
    Q_OBJECT
    Q_PROPERTY(int INT1 MEMBER myInt)
    //this enum is declared in the same file and works as expected
    Q_PROPERTY(Test_SameFile::NSEnum_SameFile ENUMVAL1 MEMBER a)
    //this enum is declared in a separate header file and does not
    Q_PROPERTY(Test_DiffFile::NSEnum_DiffFile ENUMVAL2 MEMBER g)

public:
    explicit EnumPropertyTest() : QObject() {}
    ~EnumPropertyTest() {}

private:
    int myInt = 5;
    Test_SameFile::NSEnum_SameFile a = Test_SameFile::A2;
    Test_DiffFile::NSEnum_DiffFile g = Test_DiffFile::T2;
};

#endif // ENUMPROPERTYTEST_H

My header file that contains another namespace and enum:

#ifndef ENUMTEST_H
#define ENUMTEST_H

#include <QObject>

namespace Test_DiffFile {
Q_NAMESPACE

enum NSEnum_DiffFile {
    T1,
    T2,
    T3
};
Q_ENUM_NS(NSEnum_DiffFile)

}

#endif // ENUMTEST_H

My main:

#include <QObject>
#include <QMetaProperty>
#include <QDebug>

#include "enumpropertytest.h"

int main()
{
    auto id1 = qRegisterMetaType<Test_SameFile::NSEnum_SameFile>();
    auto id2 = qRegisterMetaType<Test_DiffFile::NSEnum_DiffFile>();
    auto e1 = QMetaEnum::fromType<Test_SameFile::NSEnum_SameFile>();
    qDebug() << "e1 id: " << id1 << ", valid:" << e1.isValid() << ", scope : " << e1.scope();

    auto e2 = QMetaEnum::fromType<Test_DiffFile::NSEnum_DiffFile>();
    qDebug() << "e2 id: " << id2 << ", valid:" << e2.isValid() << ", scope : " << e2.scope();

    EnumPropertyTest t;

    auto mo = t.metaObject();

    for (int i = mo->propertyOffset(); i < mo->propertyCount(); ++i){
        QString propName = mo->property(i).name();
        auto prop = mo->property(i);
        qDebug() << "Property: " << propName << "is an enum: " << prop.isEnumType();
    }

    return 0;
}

When I run this program, it outputs the following:

e1 id:  1024 , valid: true , scope :  Test_SameFile
e2 id:  1025 , valid: true , scope :  Test_DiffFile
Property:  "INT1" is an enum:  false
Property:  "ENUMVAL1" is an enum:  true
Property:  "ENUMVAL2" is an enum:  false

As you can see, the enum declared in the same file as the class is working as expected with the property system, but the one declared in the header file is not recognized to be an enum. How can I resolve this? I have deleted build folders, re-run qmake, everything obvious I could think of and no change.

2

There are 2 best solutions below

3
santahopar On

I believe that in order for your enum to be recognized by Qt's meta type system, it needs to declared within either a Q_OBJECT or a Q_GADGET. If you don't want to make it part of your class (for understandable reasons like reusability, etc), you can declare a separate struct with the Q_GADGET macro in it and then you can define your enum there. The bottom line is that if you want your type to be recognizable by Qt's meta type system, but don't wanna make it a QObject, you can do so by just making it a Q_GADGET. The difference between Q_OBJECT and Q_GADGET is that a gadget cannot have signals and slots.

So, it will look like this:

#include <QObject>

namespace Test_SameFile {    

struct EnumHolder {
private:
  Q_GADGET
public:

enum NSEnum_SameFile {
    A1,
    A2,
    A3
};
Q_ENUM(NSEnum_SameFile)    
}   

}

You can try it and see if it works.

3
santahopar On

I tried to replicate your source code and I got true for all these case. Please take a look:

This is EnumHolder.h and it defines two namespaces YoYo and YaYo containing enums Fruit and Vegetable respectively:

#ifndef ENUMHOLDER_H
#define ENUMHOLDER_H

#include <QMetaProperty>

namespace YoYo {
Q_NAMESPACE

enum Fruit {
  Apple,
  Pear,
  Watermelon,
};
Q_ENUM_NS(Fruit)

}  // namespace YoYo

namespace YaYo {
Q_NAMESPACE
enum Vegetable {
  Broccoli,
  Tomato,
  Lettuce,
};
Q_ENUM_NS(Vegetable)
}  // namespace YaYo

#endif // ENUMHOLDER_H

And this is Food.h, which defines the enum FoodType in namespace Embedded and it also defines a QObject called Food that contains 3 properties of types YoYo::Fruit, YaYo::Vegetable and Embedded::FoodType:

#ifndef FOOD_H
#define FOOD_H

#include <QMetaProperty>
#include <QObject>

#include "EnumHolder.h"

namespace Embedded {
Q_NAMESPACE
enum FoodType {
  Food1,
  Food2,
  Food3,
};
Q_ENUM_NS(FoodType)
}  // namespace Embedded

class Food : public QObject {
  Q_OBJECT
  Q_PROPERTY(YoYo::Fruit fruit MEMBER _fruit)
  Q_PROPERTY(YaYo::Vegetable vegetable MEMBER _vegetable)
  Q_PROPERTY(Embedded::FoodType foodType MEMBER _foodType)
 private:
  YoYo::Fruit _fruit;
  YaYo::Vegetable _vegetable;
  Embedded::FoodType _foodType;
};

#endif  // FOOD_H

And here is my main.cpp:

int main(int argc, char *argv[]) {
  QCoreApplication app(argc, argv);
  Food food;
  auto mo = food.metaObject();

  for (int i = mo->propertyOffset(); i < mo->propertyCount(); ++i) {
    auto property = mo->property(i);
    qDebug() << property.name() << " is an enum: " << property.isEnumType();
  }

  return app.exec();
}

And here is my output:

fruit  is an enum:  true
vegetable  is an enum:  true
foodType  is an enum:  true

Also, I did not use qRegisterMetaType() or Q_DECLARE_META_TYPE. It just worked the way it is.

Another note that I'd like to make is that it works as a proof of concept, but it's not a good idea in general cause generally don't wanna define too many namespaces in your code unless there is a need for it. And here it feels like just to have the enum in a separate file you have to define a new namespace with a Q_NAMESPACE macro in it. I tried to reuse the same namespace and define the other enum in a different header file and got a MOC error. You can use Q_NAMESPACE only once apparently for a given namespace. And if you don't have it on top of your enum, you can't use Q_ENUM_NS, cause you'll get another error.

So probably it's a good idea to have one header file where you will define all your enums in a namespace and that way you will use Q_NAMESPACE only once for that namespace and add all those enum's to Qt's meta-object system.