...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_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:
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.
here once again:
- you have written
cppPersonsModel.firstnamein your QML code
roleNames()is called (only once I guess)
QHash<int, QByteArray>is returned from the C++ model
firstnameis looked up in the
- the matching
intis the value for the
roleparameter 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 :-)