Connect a signal to the slot of a QMetaProperty
Asked Answered
D

3

5

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.

Dagenham answered 13/9, 2013 at 19:23 Comment(1)
I'm actually trying to do something similar, but I took a slightly different approach. I'm creating a class I call QPropertyModel that creates a one-row model where the columns map to properties in an arbitrary QObject. Then I use QDataWidgetMapper to attach widgets to properties. The resulting GUI code looks like: model = new QPropertyModel(pObject); mapper = new QDataWidgetMapper(model); mapper->setModel(model); mapper->addMapping(aTextEdit, model.column("property name"));Aspire
H
5

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));
}
Homochromous answered 13/9, 2013 at 20:7 Comment(8)
Thank you for your response. This code gets me a list of all the methods inside this QObject, I get that, but I was hoping to be able to retrieve the read and write functions associated with the QMetaProperty. It gets registered when you invoke Q_PROPERTY with the READ and WRITE sections, and internally, qt is definitely using them when you call setProperty or getProperty, so why can't I access those functions to use with connect? If I used the method listed in the above code, I would need to somehow identify the method I want, but how can I with just a QMetaProperty object?Dagenham
@HD_Mouse, but don't you know the methods names beforehand? Aren't read and write functions associated with the QMetaProperty the same methods? Can't you find them in the list of all methods?Homochromous
Well, I do outside of the class, but the idea is to iterate over the QObject's QMetaPropertys that were returned from QObject::property(), then through QMetaProperty, connect the read and write slots through there. The names of the read and write functions ARE in the list, but there isn't a method in QMetaProperty that associates it with their slots.Dagenham
@HD_Mouse, hm, but why QMetaProperty should point to the associated slot, if you can change it directly? Maybe you could think in that direction?Homochromous
Well, the particular object this dialog is pointing to has quite a few properties associated with it with more on the way. The goal was to design this dialog so that it would expand on its own when we added properties without having to hardcode the GUI.Dagenham
@HD_Mouse, thanks for the source code. It made the problem explanation much clearer. Please find my updated answer with possible solution.Homochromous
Thanks for the help! It's working quite well now. Small note, I put the QMap<QWidget*, int> item inside the dialog because the other object really doesn't need to know about it, plus the dialog wouldn't be able to access it since it's a private member.Dagenham
@HD_Mouse, oh, right. The mapping should be the part of the dialog of course - my fault. I have fixed the sample code. I am glad that idea works well for you.Homochromous
A
2

I'm not sure if this will work for you, but I went the other way. I wrote the gui and tied the widgets to a data model based on properties. You might be able to use the model to get the properties to dynamically generate the gui and then tie your generated widgets to the model. I wrote a class, QPropertyModel, that creates a single row data model for any QObject with properties. The properties correspond to columns in the model. I posted the class in a gist and here's an example of it in use:

https://gist.github.com/sr105/7955969

QPropertyModel *model = new QPropertyModel(myQObjectPtr, this);
qDebug() << "model props:" << model->propertyNames();
QDataWidgetMapper *_mapper = model->mapper();
_mapper->addMapping(ui->lblBalance, model->columnForProperty("balance"), "text");
_mapper->addMapping(ui->lineEdit, model->columnForProperty("balance"));
_mapper->toFirst();
Aspire answered 14/12, 2013 at 6:53 Comment(0)
T
0

I also had some thoughts about automatic UI generation based on QObject properties. But at the moment I don't work in this direction because usually, I need specific UI layout in order to make it a little bit user-friendly.

While I have nothing to say about automatic UI generation, I have something for automatic UI binding. The code is not perfect and a bit hard-coded, but the main idea is visible. I think that something like this may also be used for automatic UI generation.

Than answered 6/7, 2017 at 2:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.