QWidgets and data

published at 13.08.2015 22:16 by Jens Weller

The sixt part of my series about writing applications in C++ using Qt and boost is about my thoughts on widgets and how to interact with data from them. The last post was about writing a generic class for context menus. So, the main focus of this post is the form like panel/widget classes that allow editing of different elements in the tree. Some of the feedback on last weeks part about context menus and widgets was that using QtQuick for UI would be now much more feasible. And wouldn't it fit so well in this series?

Why I opt for QWidgets instead of QtQuick

First, this post is not about comparing both UI implementations. I plan to implement a smaller, not to complex application later this year actually in QWidgets AND QtQuick, as a basis to compare and see where things are heading UI wise in Qt. Currently my code is based on Qt 5.3, a TreeView Element in QtQuick is now only added in Qt 5.5. QtQuick is since Qt5.5 offering most controls which QWidgets has, yet the tooling for QWidgets is in the Creator much nicer, and makes me more productive, then when getting frustrated with the QtQuick editor.

As this is going to be a complex application, I don't want to add a nother layer of complexity, aka QML/Javascript and its Qt driven engine. I do have quite a bit of experience in QWidgets, I couldn't write similar code for QtQuick. Also I do favor C++ over Javascript, and I'm not sure what the benefit is of adding another layer of JS Logic into my code. One goal of this project is to explore the possibilities of creating generic code for use in Qt, such as the generic context menu class. There is no such thing in QML as generic concepts, everything that needs to be exposed to QtQuick needs to go through Qt land first, meaning I have to expose it either as its own QObject derived type or implement a model for it. Writing a Qt wrapper around my Qt-free backend, to expose it to a QtQuick UI will bring me just more work, but no benefits compared to QWidgets. I am much more productive in QWidgets, and as I do have a need to use this program at some day, I opt for the more mature technology, which also has great tooling support in the QtCreator: QWidgets.

In my Introduction into Qt series I already wrote a good basic overview on QWidgets, so I don't want to repeat to much of this here, and more forward to focus on actually using QWidgets to build forms.

QWidgets and data transfer

My application will feature many forms where data is exposed to the user, so changes can be made. I prefer to build those forms in the RAD Editor of QtCreator, but you also can simply stick to gether the needed layouts and controls in any QWidget derived class to get a form like window to display and edit things. The interface which Qt provides for extracting the actual value of an actual control such as QLineEdit, QTextArea, QComboBox etc is a little mess. Here is an overview:

Classes Interface
QLineEdit text()
QComboBox currentText()
QTextEdit

plainText()

html()

QSpinBox

QProgressBar

value()
QCheckBox checkState()

What I'd like to show with this table is, that there is no simple interface, if one wants to transfer values out of a control in a form like class. There is no interface which allows to simply query for the (current) value of a QWidget control, each class has a specific method, most classes are derived from QWidget them selves. There is no common interface, or even a class for it. I'm lucky to know that I only will need mostly QLineEdits, but this makes it really difficult to write generic code that deals with those classes, except one would write specializations for each type.

I might opt later for doing this, but currently its not needed, as my forms mostly consist of line edits, a few combo boxes and a yet to be implemented richttext/html editor.

But then there is also another important question: when should I extract the value of a control to actually set it as a value in the corresponding standard C++ like data wrapping class? Should I update a dir name with each keystroke made in the line edit? And should I also then update any occurrence of this name? I opted for the focus lost of a control, in order to save an edited value. As this form lives inside a tab control and not a dialog, there is no ok button indicating to save the changes. A save button would be an option, but I rather not force the user to click a useless button all the time.

One EventFilter class

Actually, the focus loss of a control is in Qt not available as a signal, it is a QEvent. QEvents usually don't propagate to slots, they are for events coming from the OS such as mouse events or in my case the focus event. Often virtual methods exist, which easily can be overwritten by deriving a class from the actual control. But that would not only produce lots of boilerplate code to implement this, it also would mean, that in the editor I would have to promote my controls to such special controls. Luckily there is another way: one can install an event filter to any QObject based class, and then in a different virtual method just add some code to filter out the interesting events. So, I created the one and only EventFilter class I ever will have to write, taking a callback to the actual code defined else where as a lambda:

class EventFilter : public QObject
{
    Q_OBJECT
public:
    using eventfilter_sig = std::function<bool(QObject*,QEvent*)>;
    explicit EventFilter(eventfilter_sig filter, QObject *parent = 0);
protected:
    bool eventFilter(QObject *obj, QEvent *event){return filter(obj,event);}
    eventfilter_sig filter;
};

This class needs to be instantiated with a lambda (or any other callable object, std::bind to a method/function would do too), it then simply invokes this callable on every all to eventFilter. Here is the code using an event filter for the focus out event:

//install eventfilter
auto filter = [this,updateItem](QObject* obj, QEvent* e){
    if(e->type() == QEvent::FocusOut)
    {
        QLineEdit* le = qobject_cast<QLineEdit*>(obj);
        dir->setName(le->text().toStdString());
        updateItem(item,this);
    }
    return true;
};
ui->txt_dirname->installEventFilter(new EventFilter(filter,this));

Yes, this code is a little bit java-like, but Qt feels in some parts really like the JDK of C++... The strength of the EventFilter class is, that it can filter any event, its very easy to reuse it to filter keystrokes or mouse events. Actually I implemented the class at the beginning to filter the mouse events of the TreeView, but a little research showed, that this wasn't needed, and that the tree view offered slots for the things I wanted to do.

As every form class has a pointer to the instance its actually displaying, its easy to transfer the values from the moment of focus lost now. But what if the opposite needs to be done, how could a change in the lowest data layer written in standard C++ be brought back to Qt? I don't intend to make Qt accessible there, so the signal/slot mechanism isn't an option. Next week, I will look into the options, when messaging needs arise to go beyond Qt...