Click here to Skip to main content
15,035,600 members
Articles / Desktop Programming / QT
Article
Posted 13 Jan 2020

Tagged as

Stats

6K views
275 downloads
6 bookmarked

QtTreePropertyBrowser vs. PropertyGrid: Application to Scientific Computing

Rate me:
Please Sign up or sign in to vote.
5.00/5 (3 votes)
13 Jan 2020CPOL4 min read
Property browsing is a common task and many standalone libraries already exist, but let's explore the built-in functionality in Qt library to handle this.

Introduction

In scientific computing and numerical modeling, there is often a situation where a model's behavior is determined by a set of parameters, such as gravitational acceleration, coefficient of friction, and timestep size. Most of these parameters remain constant throughout the course of the simulation, but it is desirable to adjust them quickly on the fly. Other numerical output often must be displayed, such as the total kinetic energy of the system, the current timestep number, etc.

Image 1

High-level languages usually come with standard UI components that implement property browsing and editing. In C#, for example, one can employ a PropertyGrid connected to an object. Since C# has an extensive metadata functionality, PropertyGrid picks up the object's properties and presents them accordingly. Adding new numerical fields in this case is very straightforward.

Implementing the same functionality in C++ is not trivial. The often-used graphical API OpenGL does not directly implement text output or user interface components. There is a good selection of libraries built on top of OpenGL that implement property browsing. One example, SPlisHSPlasH, uses a custom-written library to store parameter values and another library, Anttweakbar, to implement the GUI. Nvidia's FleX demo uses a different implementation, Imgui, for user interface.

Image 2

But what if the code is already based on Qt? Is there a way to present a list of properties? After all, Qt Designer has such component.

Image 3

None of my Qt books say word about QPropertyBrowser - an elusive widget used in the designer. To utilize its magic, one must first include qtpropertybrowser.pri, typically situated in ~/Qt/5.12.6/Src/qttools/src/shared/qtpropertybrowser/.

If you do not have this folder, then install the Qt source via MaintenanceTool. And if your project is using cmake instead of qmake, then you are out of luck. Refer to this solution for including the appropriate source files.

Instantiating the Widget

One option is to utilize QPropertyBrowser directly, but it will not provide all the desired functionality. This widget does show and edit properties, but these won't be the properties of your object. The widget is created and populated as follows:

C++
QtVariantPropertyManager *variantManager = new QtVariantPropertyManager;
QtTreePropertyBrowser browser;
browser.setFactoryForManager(variantManager, new QtVariantEditorFactory);
QtVariantProperty *p = variantManager->addProperty(QVariant::Int, "Property1");
browser.addProperty(p);

Include the following headers:

C++
#include "qteditorfactory.h"
#include "qttreepropertybrowser.h"
#include "qtpropertymanager.h"
#include "qtvariantproperty.h"

An in-depth tutorial about instantiating this component is provided here. Note that this widget has no support in the designer and must be instantiated in code. At this point, we populated it manually with a single editable field.

Showing Properties of an Object

C++ language does not have object properties, neither is there any access to metadata. Qt extends C+ with its own property system, but how do we browse them via GUI? Let's create an ObjectPropertyBrowser.

C++
class ObjectPropertyBrowser : public QtTreePropertyBrowser
{
    Q_OBJECT

public:
    ObjectPropertyBrowser(QWidget* parent);
    void setActiveObject(QObject *obj); // connect to QObject using this

private:
    QtVariantPropertyManager *variantManager;
    QObject *currentlyConnectedObject = nullptr;
    QMap<QtProperty *, const char*> propertyMap;

private slots:
    void valueChanged(QtProperty *property, const QVariant &value);

public slots:
    void objectUpdated(); // call this whenever currentlyConnectedObject is updated
};

We extend the original widget with setActiveObject(QObject*), which links the properties of any QObject with our widget. This is the most difficult part, as we must loop through the object's properties and add them to the dictionary. The first item on this list is always the object's name, which we don't care much about, so we skip it. Feel free to include it if you need it.

C++
void ObjectPropertyBrowser::setActiveObject(QObject *obj)
{
    clear();
    variantManager->clear();
    propertyMap.clear();
    if(currentlyConnectedObject) currentlyConnectedObject->disconnect(this);
    currentlyConnectedObject = obj;
    if(!obj) return;

    for(int i=1; i< obj->metaObject()->propertyCount();i++) {
        QMetaProperty mp = obj->metaObject()->property(i);
        QtVariantProperty *property = variantManager->addProperty(mp.type(),mp.name());
        property->setEnabled(mp.isWritable());
        propertyMap[property] = mp.name();
        addProperty(property);
    }
    connect(obj, SIGNAL(propertyChanged()), this, SLOT(objectUpdated()));
    objectUpdated();
}

The valueChanged() slot is invoked whenever a user changes a value via UI. The change should be passed on to currentlyConnectedObject, otherwise it will have no effect on the stored data. Thankfully, this is done in a single line of code, since our propertyMap tells which QtVariantProperty property corresponds to which object's property.

C++
void ObjectPropertyBrowser::valueChanged(QtProperty *property, const QVariant &value)
{
    currentlyConnectedObject->setProperty(propertyMap[property], value);
    objectUpdated();
}

Note that a change in one property can cause changes in the others, so after the update, we refresh the whole UI by calling objectUpdated() that enumerates through the propertyMap and transfers the updated values to the browser. It can be called manually to update the UI after the object's data was changed externally, i.e., by a separate thread. We temporarily disconnect the signals to avoid circular notifications.

C++
void ObjectPropertyBrowser::objectUpdated()
{
    disconnect(variantManager, SIGNAL(valueChanged(QtProperty*, QVariant)), 
               this, SLOT(valueChanged(QtProperty*, QVariant)));
    QMapIterator<qtproperty*, char="" const=""> i(propertyMap);
    while(i.hasNext()) {
        i.next();
        variantManager->setValue(i.key(), currentlyConnectedObject->property(i.value()));
    }
    connect(variantManager, SIGNAL(valueChanged(QtProperty*, QVariant)), 
            this, SLOT(valueChanged(QtProperty*, QVariant)));
}
</qtproperty*,>

Ideally, our QObject will emit a propertyChanged signal whenever a property is updated. But this requires a specifically designed QObject:

C++
class TestData : public QObject
{
    Q_OBJECT
    Q_PROPERTY(double Value1 MEMBER m_value1 NOTIFY propertyChanged)

signals:
    void propertyChanged();
private:
    double m_value1;
};

If QObject does not emit this signal, no error will appear, but UI will not be automatically updated and the programmer will be responsible for calling the browser's objectUpdated() whenever UI refresh is required. The screenshot and the included source code shows the final result, where the object's properties are fully editable, and any external changes are immediately reflected in the UI.

Image 4

Conclusion

Editing an object's properties is a common task, and there is no super-easy way of achieving this in C++, as the language was conceived before the graphical user interface appeared. Various Qt-based solutions were posted as early as 2008 and have not been maintained, but still function quite well. Other solutions, like QtnProperty, are quite complex and have a steep learning curve. Since Qt Designer already includes a property browser, it seems like the most straightforward option within Qt framework and with a guarantee of long-term maintenance. If needed, a designer plugin can be created for the suggested ObjectPropertyBrowser to visually handle this widget in Qt Designer. I hope that you find this information helpful. Thank you for any comments.

History

  • 13th January, 2020: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Igor Gribanov
Engineer
Canada Canada
I am a researcher working on numerical models for deformation, crushing and flow of ice. The models are based on continuum mechanics, where numerical approaches include particle-based methods and finite elements.

Comments and Discussions

 
PraiseThank you very much for sharing! Pin
Uglylab22-Apr-20 22:11
MemberUglylab22-Apr-20 22:11 
GeneralOne to keep Pin
colins214-Jan-20 1:31
Membercolins214-Jan-20 1:31 
GeneralRe: One to keep Pin
Igor Gribanov14-Jan-20 8:57
professionalIgor Gribanov14-Jan-20 8:57 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.