Search code examples
qtsignals-slots

Connect a signal to the slot of a QMetaProperty


I'm not sure if something like this is possible, but I am attempting to dynamically generate a GUI based on properties that have been registered into Qt's property system. My assumption is that since I have registered a property using Q_PROPERTY() in this fashion:

Q_PROPERTY(propertyType propertyName WRITE setPropertyName READ getPropertyName NOTIFY propertynameSignal)

I should be able to retrieve the signature of the write or read functions for connection using the connect() function. The goal of this is to have a dialog connected to this object for modifying properties, but without having to hand write all of it. Is this possible, am going about it the wrong way, or should I just hardcode the dialog?

EDIT 1: I'll share some of the code (stripped down) I have which will hopefully make it more obvious as to what I'm trying to do:

Class declaration for particular class:

class MyObject : public QObject
{
    Q_OBJECT
    Q_PROPERTY(bool someBool READ getBool WRITE setBool)
  public:
    bool someBool;

    bool getBool();
    void setBool(bool);
}

QDialog subclass that points to this Object:

void MyDialog::generateUI()
{
    const QMetaObject* metaObject = _MyObjectPtr->metaObject();
    for (int i = 0; i < metaObject->propertyCount(); ++i)
    {
        QMetaProperty property = metaObject->property(i);
        if (!strcmp(property.typeName(), "bool")
        {
            QCheckBox* checkBox = new QCheckBox(this);
            bool state = metaObject->property(property->name()).toBool();
            checkBox->setCheckState((state) ? Qt::Checked : Qt::Unchecked);

            // Add checkBox to QDialog layout widgets here
        }
    }
}

Forgive all the My* renamings, but I need to keep my actual class/member names private.

I can confirm that the object pointed to by MyObjectPtr is being read from properly, because the starting values reflect values that I expect when I change them around. The trouble is connecting this back to that object. I can change the values inside the checkbox GUI-side, but the values aren't being sent to the actual object pointed to by _MyObjectPtr.


Solution

  • To retrieve the signature of QObject's methods (signals, slots, etc.) you can use meta object (QMetaObject) information. For example the following code (taken from Qt documentation) extracts all methods' signatures of the object:

    const QMetaObject* metaObject = obj->metaObject();
    QStringList methods;
    for(int i = metaObject->methodOffset(); i < metaObject->methodCount(); ++i)
        methods << QString::fromLatin1(metaObject->method(i).signature());
    

    To check whether the method is a slot or a signal, you can use QMetaMethod::methodType() function. For signature use QMetaMethod::signature() (refer to the example above).

    QMetaObject reference

    UPDATE After @HD_Mouse updated the question with additional information about his idea to created dynamic GUI based on an object's properties, I came up with the following code that could solve the problem:

    Add member variable that will store the mapping between GUI component and corresponding property index:

    class MyDialog : public QDialog
    {
        [..]
    private:
        /// Mapping between widget and the corresponding property index.
        QMap<QObject *, int> m_propertyMap;
    };
    

    When a GUI component created (check box), connect its changing signal to the special slot that will handle the corresponding property update.

    void MyDialog::generateUI()
    {
        const QMetaObject* metaObject = _MyObjectPtr->metaObject();
        for (int i = 0; i < metaObject->propertyCount(); ++i)
        {
            QMetaProperty property = metaObject->property(i);
            if (!strcmp(property.typeName(), "bool")
            {
                QCheckBox* checkBox = new QCheckBox(this);
                bool state = metaObject->property(property->name()).toBool();
                checkBox->setCheckState((state) ? Qt::Checked : Qt::Unchecked);
    
                // Add checkBox to QDialog layout widgets here
    
                // Store the property and widget mapping.
                connect(checkBox, SIGNAL(stateChanged(int)),
                        this, SLOT(onCheckBoxChanged(int)));
                m_propertyMap[checkBox] = i;
            }
        }
    }
    

    When a check box state changed, find the corresponding property (use mapping) and update it according to the check box state:

    void MyDialog::onCheckBoxChanged(int state)
    {
        QObject *checkBox = sender();
        QMetaObject* metaObject = _MyObjectPtr->metaObject();
        int propertyIndex = m_propertyMap.value(checkBox);
        QMetaProperty property = metaObject->property(i);
    
        // Update the property
        _MyObjectPtr->setProperty(property.name(), bool(state == Qt::Checked));
    }