Model, model on the wall...

...who is doing QML at all? Wut? Is he doing drugs? Nope, I was just in the mood for a rhyme like in wine dine 69.

Now take that!

Ever expected that rhyme in a technical blog?

As I have written in Organize your data or stop being all over the map I am still playing with thoughts about data storage / structure and how to support that with code. But in the spirit of laziness or code manageability to sound more professional it shall be as few code lines as possible.

If you use widgets in Qt, it can be quite easy to use SQL databases. If you use QML with Qt the story gets an aftertaste. Things that were easy before are not anymore. Let's don't keep on ranting and focus on a solution to the problems that ocured.

If you use(d) "QAbstractItemModel you will have come across the Qt::ItemDataRole enum. When a view requests data from your QAbstractItemModel, it simply calls the data() method for some QModelIndex and a Qt::ItemDataRole. The index describes the position of the needed data, e.g. column and row in a table. The role indicate the type of the needed data. Qt::DisplayRole will ask for the data that should be shown as text, Qt::ForegroundRole will determine the font color needed to draw the data and so on.

Here is a code snippet taken from http://qt-project.org.

QSqlTableModel *model = new QSqlTableModel(parentObject, database);
model->setTable("employee");
model->setEditStrategy(QSqlTableModel::OnManualSubmit);
model->select();
model->removeColumn(0); // don't show the ID
model->setHeaderData(0, Qt::Horizontal, tr("Name"));
model->setHeaderData(1, Qt::Horizontal, tr("Salary"));
QTableView *view = new QTableView;
// this one liner is enough
view->setModel(model);
view->show();

One line of code is enough to tell the view about the model, the rest is done automagically behind the scenes.

h3. A new king in town

With QML the rules (or shall I say roles?) have changed. If you present a QSqlTableModel to a QML view via QQmlContext::setContextProperty, the result is something like that's all Greek to me.

If you write your own C++ classes that should expose data to the QML world, you will face the Q_PROPERTY or Q_INVOKABLE macro. Those macros provide the glue between both worlds, or in other words: things known to the property system can be reached in the QML world. For whatever reason a QSqlTableModel does not build the needed property bindings on its own. Thus the QML world does not know anything about those objects or at least not enough or the C++ world does not know how to respond to questions from the QML side.

The keyword is roles here. Those are the vocabulary that QML uses to squeeze some data out of a C++ object. Since Qt does not do the job for you, we will find a way to do it on our own. The fact that you have to do something on your own is battlesome but does not change the situation in the end. If you want to use any descendants of QAbstractItemModel, that's your way to go.

Let's assume you've got a SQLite database with a persons table and id, firstname, lastname columns. Those shall be presented in a ListView and your C++ model is known to the QML world via QQmlContext::setContextProperty by the name cppPersonsModel. Somewhere in the delegate for the ListView you reach out for this data:

cppPersonsModel.firstname
cppPersonsModel.lastname

To achieve this the QML side will ask for every Qt::DisplayRole in your QSqlTableModel by calling roleNames(). This returns a QHash<int, QByteArray> and is searched for the column names.

Slow please.

Ok,

here once again:

  • you have written cppPersonsModel.firstname in your QML code
  • roleNames() is called (only once I guess)
  • QHash<int, QByteArray> is returned from the C++ model
  • firstname is looked up in the QByteArray
  • the matching int is the value for the role parameter when the model is queried with a call of data(const QModelIndex & index, int role = Qt::DisplayRole) const

Thus your model must be able the deliver the role names and must know how to translate the role (name) to the database column. Since Qt5 the void QAbstractItemModel::setRoleNames ( const QHash<int, QByteArray> & roleNames ) is marked as deprecated and is protected. This and the second requirement (translate role into column names) cause that you must subclass QSqlTableModel and code the needed features on your own.

That is what I've done as an example and you can look at it on Github.

The code to use this subclass in the end is as follows:

QScopedPointer<ProxyQSqlTableModel> personsModel(new ProxyQSqlTableModel(&app, database));
personsModel->setTable("person");
personsModel->setEditStrategy(QSqlTableModel::OnManualSubmit);
personsModel->select();
// skipped some stuff unrelated to QSqlTableModel 
context->setContextProperty("cppPersonsModel", personsModel.data());

As you can see, now it's as easy as with the old widgets. Time for coffee :-)