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++ modelfirstname
is looked up in theQByteArray
- the matching
int
is the value for therole
parameter when the model is queried with a call ofdata(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 :-)