How to Use a QSqlQueryModel in QML: Difference between revisions

From Qt Wiki
Jump to navigation Jump to search
m (JKSH moved page How to use a QSqlQueryModel in QML to How to Use a QSqlQueryModel in QML: Match localised titles)
(Missed part of the category name)
 
(8 intermediate revisions by 6 users not shown)
Line 1: Line 1:
{{Cleanup | reason=Auto-imported from ExpressionEngine.}}
{{Cleanup | reason=Auto-imported from ExpressionEngine.}}
 
[[Category:Snippets::QML]]
[[Category:Snippets]]
[[Category:HowTo]]
[[Category:HowTo]]
[[Category:Developing_with_Qt::Qt Quick]]
[[Category:Developing with Qt::Qt Quick::QML]]
[[Category:Developing_with_Qt::Qt Quick::QML]]
{{LangSwitch}}
[[Category:Developing_with_Qt::Qt-Quick::Tutorial]]
 
'''English''' [[How_to_use_a_QSqlQueryModel_in_QML_Spanish|Spanish]] [[HowToUseAQSqlQueryModelInQML_Italian|Italian]]
 
[toc depth="3" align_right="no"]
 
= How to use a QSqlQueryModel in QML =


== Initial fully detailed approach ==
== Initial fully detailed approach ==
Line 35: Line 27:
class ArtistsSqlModel : public QSqlQueryModel
class ArtistsSqlModel : public QSqlQueryModel
{
{
Q_OBJECT
    Q_OBJECT
public:
public:
explicit ArtistsSqlModel(QObject '''parent);
    explicit ArtistsSqlModel(QObject *parent);
void refresh();
    void refresh();
QVariant data(const QModelIndex &index, int role) const;
    QVariant data(const QModelIndex &index, int role) const;
 
signals:
signals:
public slots:
public slots:
private:
private:
const static char''' COLUMN_NAMES[];
    const static char* COLUMN_NAMES[];
const static char* SQL_SELECT;
    const static char* SQL_SELECT;
};
};
</code>
</code>
Line 52: Line 47:
I always have two constant static variables in each of my models that derive from QSqlQueryModel, COLUMN_NAMES and SQL_SELECT. The order of the column names in COLUMN_NAMES must match the order they are listed in the SELECT statement
I always have two constant static variables in each of my models that derive from QSqlQueryModel, COLUMN_NAMES and SQL_SELECT. The order of the column names in COLUMN_NAMES must match the order they are listed in the SELECT statement


<code>const char* ArtistsSqlModel::COLUMN_NAMES[] = {
<code>
"artist",
const char* ArtistsSqlModel::COLUMN_NAMES[] = {
"title",
    "artist",
"year",
    "title",
NULL
    "year",
    NULL
};
};
const char* ArtistsSqlModel::SQL_SELECT =
const char* ArtistsSqlModel::SQL_SELECT =
"SELECT artists.artist, albums.title, albums.year"
    "SELECT artists.artist, albums.title, albums.year"
" FROM albums"
    " FROM albums"
" JOIN artists ON albums.artistid = artists.id";
    " JOIN artists ON albums.artistid = artists.id";
</code>
</code>


Line 68: Line 64:
This is where all the magic really happens. The QML will reference the different columns by the role names set on the model.
This is where all the magic really happens. The QML will reference the different columns by the role names set on the model.


<code>ArtistsSqlModel::ArtistsSqlModel(QObject *parent) :
<code>
QSqlQueryModel(parent)
ArtistsSqlModel::ArtistsSqlModel(QObject *parent) :
    QSqlQueryModel(parent)
{
{
int idx = 0;
    int idx = 0;
QHash<int, QByteArray> roleNames;
    QHash<int, QByteArray> roleNames;
while( COLUMN_NAMES[idx]) {
    while( COLUMN_NAMES[idx]) {
roleNames[Qt::UserRole + idx + 1] = COLUMN_NAMES[idx];
        roleNames[Qt::UserRole + idx + 1] = COLUMN_NAMES[idx];
idx++;
        idx++;
}
    }
setRoleNames(roleNames);
    setRoleNames(roleNames);
refresh();
    refresh();
}</code>
}
</code>


=== Step 4: implement the data() method and the refresh() method: ===
=== Step 4: implement the data() method and the refresh() method: ===
Line 85: Line 83:
As long as the role that is requested is not a user role, return the default. But if the role is a user role, return the correct column:
As long as the role that is requested is not a user role, return the default. But if the role is a user role, return the correct column:


<code>QVariant ArtistsSqlModel::data(const QModelIndex &index, int role) const
<code>
QVariant ArtistsSqlModel::data(const QModelIndex &index, int role) const
{
{
QVariant value = QSqlQueryModel::data(index, role);
    QVariant value = QSqlQueryModel::data(index, role);
if(role < Qt::UserRole)
    if(role < Qt::UserRole)
{
    {
value = QSqlQueryModel::data(index, role);
        value = QSqlQueryModel::data(index, role);
}
    }
else
    else
{
    {
int columnIdx = role - Qt::UserRole - 1;
        int columnIdx = role - Qt::UserRole - 1;
QModelIndex modelIndex = this->index(index.row(), columnIdx);
        QModelIndex modelIndex = this->index(index.row(), columnIdx);
value = QSqlQueryModel::data(modelIndex, Qt::DisplayRole);
        value = QSqlQueryModel::data(modelIndex, Qt::DisplayRole);
}
    }
return value;
    return value;
} </code>
}
</code>


The refresh() method is the most important, without it, the model won't show anything at all. The best is to stick to the setQuery method from QSqlQueryModel.
The refresh() method is the most important, without it, the model won't show anything at all. The best is to stick to the setQuery method from QSqlQueryModel.
<code>void ArtistsSqlModel::refresh()
<code>
void ArtistsSqlModel::refresh()
{
{
this->setQuery(SQL_SELECT);
    this->setQuery(SQL_SELECT);
}
}
</code>
</code>
Line 113: Line 114:


<code>
<code>
ArtistsSqlModel *artistsSqlModel = new ArtistsSqlModel( qApp);
ArtistsSqlModel *artistsSqlModel = new ArtistsSqlModel( qApp);
QmlApplicationViewer viewer;
QmlApplicationViewer viewer;
viewer.rootContext()->setContextProperty("artistsModel", artistsSqlModel);
viewer.setOrientation(QmlApplicationViewer::ScreenOrientationAuto);
viewer.setMainQmlFile(QLatin1String("qml/SQLListView/main.qml"));
viewer.showExpanded();


viewer.rootContext()->setContextProperty("artistsModel", artistsSqlModel);
viewer.setOrientation(QmlApplicationViewer::ScreenOrientationAuto);
viewer.setMainQmlFile(QLatin1String("qml/SQLListView/main.qml"));
viewer.showExpanded();
</code>
</code>


Line 125: Line 126:
Since the model was exposed in step 5, the model exists and is ready to be used in QML. Simply set the model of the ListView to the name give in step 5.
Since the model was exposed in step 5, the model exists and is ready to be used in QML. Simply set the model of the ListView to the name give in step 5.


<code>import QtQuick 1.1
<code>
import QtQuick 1.1


Rectangle {
Rectangle {
width: 500
    width: 500
height: 500
    height: 500
MouseArea {
    MouseArea {
anchors.fill: parent
        anchors.fill: parent
 
        Text {
Text {
            id: text1
id: text1
            anchors.verticalCenterOffset: 20
anchors.verticalCenterOffset: 20
            anchors.horizontalCenter: parent.horizontalCenter
anchors.horizontalCenter: parent.horizontalCenter
            text: qsTr("Testing")
text: qsTr("Testing")
            font.pixelSize: 12
font.pixelSize: 12
            }
}
        ListView {
 
            id: list_view1
ListView {
            x: 125
id: list_view1
            y: 100
x: 125
            width: 110
y: 100
            height: 160
width: 110
            delegate: ArtistItemDelegate {}
height: 160
            model: artistsModel
delegate: ArtistItemDelegate {}
        }
model: artistsModel
    }
}
}
}
}
</code>
</code>


Line 159: Line 158:


'''ArtistItemDelegate.qml'''
'''ArtistItemDelegate.qml'''
<code>import QtQuick 1.1
<code>
import QtQuick 1.1


Item {
Item {
id: delegate
    id: delegate
width: delegate.ListView.view.width;
    width: delegate.ListView.view.width;
height: 30
    height: 30
clip: true
    clip: true
anchors.margins: 4
    anchors.margins: 4
 
    Row {
Row {
        anchors.margins: 4
anchors.margins: 4
        anchors.fill: parent
anchors.fill: parent
        spacing: 4;
spacing: 4;
        Text {
 
            text: artist
Text {
            width: 150
text: artist
        }
width: 150
        Text {
}
            text: title
 
            width: 300;
Text {
        }
text: title
        Text {
width: 300;
            text: year
}
            width: 50;
 
        }
Text {
    }
text: year
}
width: 50;
</code>
}
}
}</code>




Line 201: Line 198:
'''sqlquerymodel.h'''
'''sqlquerymodel.h'''
<code>
<code>
#pragma once
#include <QSqlQueryModel>
#include <QSqlQueryModel>


class SqlQueryModel : public QSqlQueryModel
class SqlQueryModel : public QSqlQueryModel
{
{
Q_OBJECT
    Q_OBJECT
 
void generateRoleNames();


public:
public:
explicit SqlQueryModel(QObject *parent = 0);
    explicit SqlQueryModel(QObject *parent = 0);


void setQuery(const QString &query, const QSqlDatabase &db = QSqlDatabase());
    void setQuery(const QString &query, const QSqlDatabase &db = QSqlDatabase());
void setQuery(const QSqlQuery &query);
    void setQuery(const QSqlQuery &query);
QVariant data(const QModelIndex &index, int role) const;
    QVariant data(const QModelIndex &index, int role) const;
 
    QHash<int, QByteArray> roleNames() const { return m_roleNames; }
signals:
 
public slots:


private:
    void generateRoleNames();
    QHash<int, QByteArray> m_roleNames;
};
};
</code>
</code>
Line 225: Line 221:
'''sqlquerymodel.cpp'''
'''sqlquerymodel.cpp'''
<code>
<code>
#include "sqlquerymodel.h"
#include "SqlQueryModel.h"
#include <QSqlRecord>
#include <QSqlRecord>
#include <QSqlField>
#include <QSqlField>
#include <QDebug>
 
SqlQueryModel::SqlQueryModel(QObject *parent) :
SqlQueryModel::SqlQueryModel(QObject *parent) :
QSqlQueryModel(parent)
    QSqlQueryModel(parent)
{  
{
 
}
}
 
void SqlQueryModel::setQuery(const QString &query, const QSqlDatabase &db)
void SqlQueryModel::setQuery(const QString &query, const QSqlDatabase &db)
{
{
QSqlQueryModel::setQuery(query,db);
    QSqlQueryModel::setQuery(query, db);
generateRoleNames();
    generateRoleNames();
}
}
 
void SqlQueryModel::setQuery(const QSqlQuery & query)
void SqlQueryModel::setQuery(const QSqlQuery & query)
{
{
QSqlQueryModel::setQuery(query);
    QSqlQueryModel::setQuery(query);
generateRoleNames();
    generateRoleNames();
}
}
 
void SqlQueryModel::generateRoleNames()
void SqlQueryModel::generateRoleNames()
{
{
QHash<int, QByteArray> roleNames;
    m_roleNames.clear();
for( int i = 0; i < record().count(); i+'') {
    for( int i = 0; i < record().count(); i ++) {
roleNames[Qt::UserRole'' i + 1] = record().fieldName(i).toAscii();
        m_roleNames.insert(Qt::UserRole + i + 1, record().fieldName(i).toUtf8());
}
    }
setRoleNames(roleNames);
}
}
 
QVariant SqlQueryModel::data(const QModelIndex &index, int role) const
QVariant SqlQueryModel::data(const QModelIndex &index, int role) const
{
{
QVariant value = QSqlQueryModel::data(index, role);
    QVariant value;
if(role < Qt::UserRole)
 
{
    if(role < Qt::UserRole) {
value = QSqlQueryModel::data(index, role);
        value = QSqlQueryModel::data(index, role);
}
    }
else
    else {
{
        int columnIdx = role - Qt::UserRole - 1;
int columnIdx = role- Qt::UserRole - 1;
        QModelIndex modelIndex = this->index(index.row(), columnIdx);
QModelIndex modelIndex = this->index(index.row(), columnIdx);
        value = QSqlQueryModel::data(modelIndex, Qt::DisplayRole);
value = QSqlQueryModel::data(modelIndex, Qt::DisplayRole);
    }
}
    return value;
return value;
}
}
</code>
</code>
Line 276: Line 268:
And use it like this :
And use it like this :
<code>
<code>
SqlQueryModel '''model1 = new SqlQueryModel(0);
SqlQueryModel *model1 = new SqlQueryModel(0);
model1->setQuery("SELECT''' FROM table WHERE column='value'");
model1->setQuery("SELECT * FROM table WHERE column='value'");
SqlQueryModel '''model2 = new SqlQueryModel(0);
SqlQueryModel *model2 = new SqlQueryModel(0);
model2->setQuery("SELECT''' FROM anothertable WHERE anothercolumn='value'");
model2->setQuery("SELECT * FROM anothertable WHERE anothercolumn='value'");
QmlApplicationViewer viewer;
QmlApplicationViewer viewer;
viewer.rootContext()->setContextProperty("myFirstModel", model1);
viewer.rootContext()->setContextProperty("myFirstModel", model1);
viewer.rootContext()->setContextProperty("mySecondModel", model2);
viewer.rootContext()->setContextProperty("mySecondModel", model2);
</code>

Latest revision as of 13:54, 28 November 2016

This article may require cleanup to meet the Qt Wiki's quality standards. Reason: Auto-imported from ExpressionEngine.
Please improve this article if you can. Remove the {{cleanup}} tag and add this page to Updated pages list after it's clean.

En Ar Bg De El Es Fa Fi Fr Hi Hu It Ja Kn Ko Ms Nl Pl Pt Ru Sq Th Tr Uk Zh

Initial fully detailed approach

Introduction

The software I develop, Photo Parata, is a client server application that uses a sql backend. Most of the time the data Photo Parata displays requires some joins. Because of this, most of the time the models are derived from QSqlQueryModel, not QSqlTableModel. For QSqlRelationalTableModel, you can find an working but unexplained example at http://wiki.qt.io/QML_and_QSqlTableModel

In this how to, I will walk you through the steps of setting up a custom model for QML, derived from QSqlQueryModel.

I would like to thank Christophe Dumez for his blog How to use C++ list model in QML. It was this blog that allowed me to piece the following together.

Other useful sources were : Using_QStandardItemModel_in_QML (wiki page) and QSqlTableModel in QML (forum thread)

The data source for this example was lifted from one of the Sql examples that ships with Qt, examples\sql\masterdetail

Step 1: Create a C++ class that derives from QSqlQueryModel:

All the magic happens in the constructor and in the overloaded data() method.

class ArtistsSqlModel : public QSqlQueryModel
{
    Q_OBJECT
public:
    explicit ArtistsSqlModel(QObject *parent);
    void refresh();
    QVariant data(const QModelIndex &index, int role) const;

signals:

public slots:

private:
    const static char* COLUMN_NAMES[];
    const static char* SQL_SELECT;
};

Step 2: Implement two static constants

I always have two constant static variables in each of my models that derive from QSqlQueryModel, COLUMN_NAMES and SQL_SELECT. The order of the column names in COLUMN_NAMES must match the order they are listed in the SELECT statement

const char* ArtistsSqlModel::COLUMN_NAMES[] = {
    "artist",
    "title",
    "year",
    NULL
};
const char* ArtistsSqlModel::SQL_SELECT =
    "SELECT artists.artist, albums.title, albums.year"
    " FROM albums"
    " JOIN artists ON albums.artistid = artists.id";

Step 3: Set the roleNames in the constructor

This is where all the magic really happens. The QML will reference the different columns by the role names set on the model.

ArtistsSqlModel::ArtistsSqlModel(QObject *parent) :
    QSqlQueryModel(parent)
{
    int idx = 0;
    QHash<int, QByteArray> roleNames;
    while( COLUMN_NAMES[idx]) {
        roleNames[Qt::UserRole + idx + 1] = COLUMN_NAMES[idx];
        idx++;
    }
    setRoleNames(roleNames);
    refresh();
}

Step 4: implement the data() method and the refresh() method:

As long as the role that is requested is not a user role, return the default. But if the role is a user role, return the correct column:

QVariant ArtistsSqlModel::data(const QModelIndex &index, int role) const
{
    QVariant value = QSqlQueryModel::data(index, role);
    if(role < Qt::UserRole)
    {
        value = QSqlQueryModel::data(index, role);
    }
    else
    {
        int columnIdx = role - Qt::UserRole - 1;
        QModelIndex modelIndex = this->index(index.row(), columnIdx);
        value = QSqlQueryModel::data(modelIndex, Qt::DisplayRole);
    }
    return value;
}

The refresh() method is the most important, without it, the model won't show anything at all. The best is to stick to the setQuery method from QSqlQueryModel.

void ArtistsSqlModel::refresh()
{
    this->setQuery(SQL_SELECT);
}

Step 5: Allow QML to see the model:

Create an instance of the model (make note that the constructor of the model did query the DB the first time). Then set it as a property on the viewer’s context, in this case I called it artistModel:

ArtistsSqlModel *artistsSqlModel = new ArtistsSqlModel( qApp);
QmlApplicationViewer viewer;

viewer.rootContext()->setContextProperty("artistsModel", artistsSqlModel);
viewer.setOrientation(QmlApplicationViewer::ScreenOrientationAuto);
viewer.setMainQmlFile(QLatin1String("qml/SQLListView/main.qml"));
viewer.showExpanded();

Step 6: Create the QML list

Since the model was exposed in step 5, the model exists and is ready to be used in QML. Simply set the model of the ListView to the name give in step 5.

import QtQuick 1.1

Rectangle {
    width: 500
    height: 500
    MouseArea {
        anchors.fill: parent
        Text {
            id: text1
            anchors.verticalCenterOffset: 20
            anchors.horizontalCenter: parent.horizontalCenter
            text: qsTr("Testing")
            font.pixelSize: 12
            }
        ListView {
            id: list_view1
            x: 125
            y: 100
            width: 110
            height: 160
            delegate: ArtistItemDelegate {}
            model: artistsModel
        }
    }
}

Step 7: Create the QML delegate used by the list

And finally implementation of the delegate. Notice here how the names set in the roleModel are used as the values to bind to the text property of the Text objects:

ArtistItemDelegate.qml

import QtQuick 1.1

Item {
    id: delegate
    width: delegate.ListView.view.width;
    height: 30
    clip: true
    anchors.margins: 4
    Row {
        anchors.margins: 4
        anchors.fill: parent
        spacing: 4;
        Text {
            text: artist
            width: 150
        }
        Text {
            text: title
            width: 300;
        }
        Text {
            text: year
            width: 50;
        }
    }
}


Source code is no longer available on my website, so feel free to contact me for the complete source code, I will be happy to share!


A more generic approach

Based on the initial wiki article I came up with a more generic approach that allow to use the same class for all your models instead of creating a derived class for each model.

Here it is :

sqlquerymodel.h

#pragma once
#include <QSqlQueryModel>

class SqlQueryModel : public QSqlQueryModel
{
    Q_OBJECT

public:
    explicit SqlQueryModel(QObject *parent = 0);

    void setQuery(const QString &query, const QSqlDatabase &db = QSqlDatabase());
    void setQuery(const QSqlQuery &query);
    QVariant data(const QModelIndex &index, int role) const;
    QHash<int, QByteArray> roleNames() const {	return m_roleNames;	}

private:
    void generateRoleNames();
    QHash<int, QByteArray> m_roleNames;
};

sqlquerymodel.cpp

#include "SqlQueryModel.h"
#include <QSqlRecord>
#include <QSqlField>
 
SqlQueryModel::SqlQueryModel(QObject *parent) :
    QSqlQueryModel(parent)
{
}
 
void SqlQueryModel::setQuery(const QString &query, const QSqlDatabase &db)
{
    QSqlQueryModel::setQuery(query, db);
    generateRoleNames();
}
 
void SqlQueryModel::setQuery(const QSqlQuery & query)
{
    QSqlQueryModel::setQuery(query);
    generateRoleNames();
}
 
void SqlQueryModel::generateRoleNames()
{
    m_roleNames.clear();
    for( int i = 0; i < record().count(); i ++) {
        m_roleNames.insert(Qt::UserRole + i + 1, record().fieldName(i).toUtf8());
    }
}
 
QVariant SqlQueryModel::data(const QModelIndex &index, int role) const
{
    QVariant value;

    if(role < Qt::UserRole) {
        value = QSqlQueryModel::data(index, role);
    }
    else {
        int columnIdx = role - Qt::UserRole - 1;
        QModelIndex modelIndex = this->index(index.row(), columnIdx);
        value = QSqlQueryModel::data(modelIndex, Qt::DisplayRole);
    }
    return value;
}

And use it like this :

SqlQueryModel *model1 = new SqlQueryModel(0);
model1->setQuery("SELECT * FROM table WHERE column='value'");
SqlQueryModel *model2 = new SqlQueryModel(0);
model2->setQuery("SELECT * FROM anothertable WHERE anothercolumn='value'");
QmlApplicationViewer viewer;
viewer.rootContext()->setContextProperty("myFirstModel", model1);
viewer.rootContext()->setContextProperty("mySecondModel", model2);