Version PDF de ce document : qt-android-base-donnees-sqlite.pdf
L’application à développer devra utiliser la technique dite embedded SQL : les instructions en langage SQL seront incorporées dans le code source d’un programme écrit dans un autre langage (ici le C++ sous Qt pour Android).
Lire : Activité bases de données
SQLite est un moteur de base de données relationnelle accessible par le langage SQL et intégrée dans chaque appareil Android.
Remarques :
Si l’application crée une base de données, celle-ci est par défaut enregistrée dans le répertoire : /data/APP_NAME/databases/DATABASE_NAME
.
Il est possible ensuite de récupérer la base de données créée à partir de l’émulateur adb
:
$ adb -d shell "run-as com.example.tv.APP_NAME cat /data/com.example.tv.APP_NAME/databases/DATABASE_NAME" > bd.sqlite
On peut ensuite l’ouvrir avec le plugin SQLite Manager de Firefox ou avec l’application sqlitebrowser
ou sqliteman
sous Linux.
Il existe aussi un exemple pour une base de données MySQL.
SQLite est une bibliothèque écrite en C qui propose un moteur de base de données relationnelle accessible par le langage SQL. Contrairement aux serveurs de bases de données traditionnels, comme MySQL ou PostgreSQL, sa particularité est de ne pas reproduire le schéma habituel client-serveur mais d’être directement intégrée aux programmes. L’intégralité de la base de données (déclarations, tables, index et données) est stockée dans un fichier indépendant de la plateforme. SQLite est le moteur de base de données le plus distribué au monde, grâce à son utilisation dans de nombreux logiciels grand public comme Firefox, Skype, Google Gears, dans certains produits d’Apple, d’Adobe et de McAfee et dans les bibliothèques standards de nombreux langages comme PHP ou Python. De par son extrême légèreté (moins de 300 Kio), il est également très populaire sur les systèmes embarqués, notamment sur la plupart des smartphones modernes : l’iPhone ainsi que les systèmes d’exploitation mobiles Symbian et Android l’utilisent comme base de données embarquée.
Qt fournit de nombreuses classes pour la gestion des base de données. Il faudra activer le module dans son fichier de projet .pro
pour pouvoir accéder aux classes :
...
QT += sql
...
On utilisera alors la classe QSqlDatabase
qui permet la connexion à une base de données.
Lien : La classe QSqlDatabase Qt5 (en)
Et ensuite la classe QSqlQuery
pour exécuter des requêtes SQL :
Lien : La classe QSqlQuery Qt5 (en)
On peut assurer le déploiement de la base de données liée à l’application dans son fichier de projet .pro
:
...
deployment.files += mabase.sqlite
deployment.path = /assets/db
INSTALLS += deployment
...
# copie la base de données dans le dossier build
CONFIG += file_copies
COPIES += bd
bd.files = mabase.sqlite
bd.path = $$OUT_PWD/
bd.base = $$PWD/
Pour l’exemple, on va créer une base de données mabase.sqlite
comprenant 2 tables :
releves
qui contiendra la description des relevésmesures
qui contiendra les mesures horodatées de températures pour chaque relevépragma foreign_keys = on;
CREATE TABLE releves (
idReleve INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL ,
description VARCHAR(255) NULL
);
CREATE TABLE mesures (
idMesure INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL ,
idReleve INTEGER NOT NULL ,
horodatage DATETIME,
temperature DOUBLE NULL ,
CONSTRAINT fk_mesures_1 FOREIGN KEY (idReleve) REFERENCES releves (idReleve) ON DELETE CASCADE
);
Pour les tests, on va insérer quelques mesures :
INSERT INTO releves(idReleve,description) VALUES(1,'Relevé de la serre 1');
INSERT INTO mesures(idReleve,horodatage,temperature) VALUES(1,'2017-04-01 08:00:00',35.23);
INSERT INTO mesures(idReleve,horodatage,temperature) VALUES(1,'2017-04-01 08:30:00',35.1);
INSERT INTO mesures(idReleve,horodatage,temperature) VALUES(1,'2017-04-01 09:00:00',34.45);
INSERT INTO mesures(idReleve,horodatage,temperature) VALUES(1,'2017-04-01 09:30:00',35.02);
INSERT INTO mesures(idReleve,horodatage,temperature) VALUES(1,'2017-04-01 10:00:00',35.53);
INSERT INTO mesures(idReleve,horodatage,temperature) VALUES(1,'2017-04-01 10:30:00',35.24);
INSERT INTO mesures(idReleve,horodatage,temperature) VALUES(1,'2017-04-01 11:00:00',35.25);
INSERT INTO mesures(idReleve,horodatage,temperature) VALUES(1,'2017-04-01 11:30:00',35.7);
INSERT INTO mesures(idReleve,horodatage,temperature) VALUES(1,'2017-04-01 12:00:00',35.61);
INSERT INTO mesures(idReleve,horodatage,temperature) VALUES(1,'2017-04-01 12:30:00',35.65);
INSERT INTO mesures(idReleve,horodatage,temperature) VALUES(1,'2017-04-01 13:00:00',35.75);
INSERT INTO mesures(idReleve,horodatage,temperature) VALUES(1,'2017-04-01 13:30:00',36.03);
INSERT INTO mesures(idReleve,horodatage,temperature) VALUES(1,'2017-04-01 14:00:00',36.1);
INSERT INTO mesures(idReleve,horodatage,temperature) VALUES(1,'2017-04-01 14:30:00',36.05);
INSERT INTO mesures(idReleve,horodatage,temperature) VALUES(1,'2017-04-01 15:00:00',36.33);
INSERT INTO mesures(idReleve,horodatage,temperature) VALUES(1,'2017-04-01 15:30:00',36.5);
INSERT INTO releves(idReleve,description) VALUES(2,'Relevé de la serre 2');
INSERT INTO mesures(idReleve,horodatage,temperature) VALUES(2,'2017-04-01 08:00:00',35.1);
INSERT INTO mesures(idReleve,horodatage,temperature) VALUES(2,'2017-04-01 08:30:00',35.15);
INSERT INTO mesures(idReleve,horodatage,temperature) VALUES(2,'2017-04-01 09:00:00',35.25);
INSERT INTO mesures(idReleve,horodatage,temperature) VALUES(2,'2017-04-01 09:30:00',35.05);
INSERT INTO mesures(idReleve,horodatage,temperature) VALUES(2,'2017-04-01 10:00:00',35.35);
INSERT INTO mesures(idReleve,horodatage,temperature) VALUES(2,'2017-04-01 10:30:00',35.24);
INSERT INTO mesures(idReleve,horodatage,temperature) VALUES(2,'2017-04-01 11:00:00',35.65);
INSERT INTO mesures(idReleve,horodatage,temperature) VALUES(2,'2017-04-01 11:30:00',35.7);
INSERT INTO mesures(idReleve,horodatage,temperature) VALUES(2,'2017-04-01 12:00:00',35.71);
INSERT INTO mesures(idReleve,horodatage,temperature) VALUES(2,'2017-04-01 12:30:00',35.75);
INSERT INTO mesures(idReleve,horodatage,temperature) VALUES(2,'2017-04-01 13:00:00',35.65);
INSERT INTO mesures(idReleve,horodatage,temperature) VALUES(2,'2017-04-01 13:30:00',35.93);
INSERT INTO mesures(idReleve,horodatage,temperature) VALUES(2,'2017-04-01 14:00:00',36.1);
INSERT INTO mesures(idReleve,horodatage,temperature) VALUES(2,'2017-04-01 14:30:00',36.15);
INSERT INTO mesures(idReleve,horodatage,temperature) VALUES(2,'2017-04-01 15:00:00',36.4);
INSERT INTO mesures(idReleve,horodatage,temperature) VALUES(2,'2017-04-01 15:30:00',36.35);
Et quelques requêtes SQL que l’on utilisera ensuite dans le code :
// la liste des relevés
SELECT description FROM releves ORDER BY releves.description ASC
// Les 5 dernières mesures du relevé 1
SELECT * FROM (SELECT mesures.horodatage, mesures.temperature FROM mesures INNER JOIN releves ON releves.idReleve = mesures.idReleve WHERE releves.description = 'Relevé de la serre 1' ORDER BY mesures.horodatage DESC LIMIT 5) tmp ORDER BY horodatage ASC LIMIT 5
// La moyenne des 5 dernières mesures du relevé 1
SELECT AVG(temperature) FROM (SELECT mesures.horodatage, mesures.temperature FROM mesures INNER JOIN releves ON releves.idReleve = mesures.idReleve WHERE releves.description = 'Relevé de la serre 1' ORDER BY mesures.horodatage DESC LIMIT 5) tmp ORDER BY horodatage ASC LIMIT 5
On crée un projet Qt QML Quick :
Au final, le fichier .pro
sera le suivant :
QT += quick quickcontrols2 sql
CONFIG += c++11
HEADERS += \
releve.h \
mesure.h
SOURCES += main.cpp \
releve.cpp \
mesure.cpp
RESOURCES += qml.qrc \
icons/MonApplicationBD/index.theme \
$$files(icons/*.png, true)
unix:!macx:
{
android:
{
DISTFILES += \
android-sources/AndroidManifest.xml
ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android-sources
# déploie la base de données avec l'apk
deployment.files += mabase.sqlite
deployment.path = /assets/db
INSTALLS += deployment
}
!android:
{
# copie la base de données dans le dossier build
CONFIG += file_copies
COPIES += bd
bd.files = mabase.sqlite
bd.path = $$OUT_PWD/
bd.base = $$PWD/
}
}
On a ajouté une classe Releve
qui aura la charge d’ouvir la base de données mabase.sqlite
, d’effectuer les 3 requêtes SQL et de fournir les résultats à l’IHM.
La classe Releve
hérite de QObject
afin de bénificier des mécanismes Qt :
#ifndef RELEVE_H
#define RELEVE_H
#include <QObject>
class Releve : public QObject
{
Q_OBJECT
public:
explicit Releve(QObject *parent = nullptr);
private:
signals:
public slots:
};
#endif // RELEVE_H
On instanciera un objet Releve
que l’on rendra accessible à partir de l’IHM décrite dans qmain.qml
. Cela sera fait dans le fichier main.cpp
avec setContextProperty()
:
#include <QGuiApplication>
#include <QIcon>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "releve.h"
int main(int argc, char *argv[])
{
QGuiApplication::setApplicationName("MonApplicationBD");
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QIcon::setThemeName("MonApplicationBD");
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("Releve", new Releve());
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
Dans qmain.qml
, on accèdera à notre objet avec l’identifiant Releve
.
L’interaction entre la classe C++ Releve
et l’IHM main.qml
sera basée sur 3 mécanismes :
Q_PROPERTY
en C++Q_INVOKABLE
en C++Connections
en QMLOn ajoutera 4 propriétés :
erreurConnexion
: pour gérer l’erreur d’ouverture de la base de donnéeslisteReleves
: pour récupérer la liste des relevés de la table releves
mesures
: les mesures d’un relevé sous la forme d’une liste de Mesure
moyenne
: la moyenne de l’ensemble des mesures
Les 3 dernières propriétés ne sont accessibles qu’en lecture auxquelles on associera un accesseur pour READ
.
Q_PROPERTY(bool erreurConnexion MEMBER erreurConnexion NOTIFY erreurChanged)
Q_PROPERTY(QStringList listeReleves READ getReleves NOTIFY listeRelevesChanged)
Q_PROPERTY(QVariant mesures READ getMesures NOTIFY mesuresUpdated)
Q_PROPERTY(QString moyenne READ getMoyenne NOTIFY moyenneUpdated)
On pourra par exemple accéder à la propriété moyenne
directement en QML comme ceci :
labelMoyenne.text = "Moyenne : " + Releve.moyenne + " °C";
Côté C++, il faudra définir l’accesseur getMoyenne()
qui permettra de lire (READ) la propriété du même nom :
QString Releve::getMoyenne()
{
return moyenne;
}
On créera 3 méthodes publiques appelables depuis QML :
lireReleve()
et lireMoyenneReleve()
qui recevront en paramètres le nom du relevé et le nombre des dernières mesures désirées (0 signifiera toutes les mesures du relevé)executerRequete()
qui recevra en paramètre une requête SQL de type INSERT
, UPDATE
ou DELETE
Q_INVOKABLE bool lireReleve(QString releve, int nb=0);
Q_INVOKABLE void lireMoyenneReleve(QString releve, int nb=0);
Q_INVOKABLE bool executerRequete(QString requete);
Côté QML, on pourra apeller une de ces méthodes de la manière suivante :
Button {
id: purgerReleve
enabled: true;
text: "Purger le relevé"
onClicked: {
Releve.executerRequete("DELETE FROM releves WHERE releves.description = '" + choixReleve.currentText + "'");
//...
}
}
On aura aussi besoin des sigaux suivants :
signals:
void erreurChanged();
void listeRelevesChanged();
void mesuresUpdated();
void mesuresErreur();
void moyenneUpdated();
...
En QML, il est possible de connecter un slot à un signal de target
en le préfixant avec on
comme ceci :
Connections {
target: Releve
onMoyenneUpdated: {
labelMoyenne.text = "Moyenne : " + Releve.moyenne + " °C";
labelMoyenne.color = "#0000FF"
labelMoyenne.visible = true
}
onMesuresErreur: {
labelMoyenne.text = "Aucune mesure pour ce relevé !";
labelMoyenne.color = "#FF0000"
labelMoyenne.visible = true
listeMesures.visible = false
}
}
On ajoute les membres privés suivants :
private:
QSqlDatabase db;
bool erreurConnexion;
QStringList releves;
QList<QObject*> mesures;
QString moyenne;
...
Au final, la déclaration de la classe Releve
est la suivante :
#ifndef RELEVE_H
#define RELEVE_H
#include <QObject>
#include <QString>
#include <QtSql/QtSql>
#include <QSqlDatabase>
#define NOM_BD QString("mabase.sqlite")
class Releve : public QObject
{
Q_OBJECT
Q_PROPERTY(bool erreurConnexion MEMBER erreurConnexion NOTIFY erreurChanged)
Q_PROPERTY(QStringList listeReleves READ getReleves NOTIFY listeRelevesChanged)
Q_PROPERTY(QVariant mesures READ getMesures NOTIFY mesuresUpdated)
Q_PROPERTY(QString moyenne READ getMoyenne NOTIFY moyenneUpdated)
public:
explicit Releve(QObject *parent = nullptr);
virtual ~Releve();
Q_INVOKABLE bool lireReleve(QString releve, int nb=0);
Q_INVOKABLE void lireMoyenneReleve(QString releve, int nb=0);
Q_INVOKABLE bool executerRequete(QString requete);
QStringList getReleves();
QVariant getMesures();
QString getMoyenne();
private:
QSqlDatabase db;
bool erreurConnexion;
QStringList releves;
QList<QObject*> mesures;
QString moyenne;
bool recuperer(QString requete, QStringList &donnees);
bool recuperer(QString requete, QVector<QStringList> &donnees);
bool recuperer(QString requete, QString &donnee);
bool copier(QFile &sfile, QFile &dfile);
bool remplacer(QFile &sfile, QFile &dfile);
bool estBDPresente(QString BD);
signals:
void erreurChanged();
void listeRelevesChanged();
void mesuresUpdated();
void mesuresErreur();
void moyenneUpdated();
public slots:
};
#endif // RELEVE_H
Dans le constructeur de la classe Releve
, on assurera l’ouverture de la base de données SQLite.
On précisera tout d’abord avec la méthode statique addDatabase()
le type QSQLITE
.
Sous Android, il faudra préalablement copier la base de données depuis l’apk (“assets:/db”) à la racine de l’application puis lui donner les droits.
Remarque : si l’application Android est déjà installée, la base de données ne sera pas “re-copier”. Si vous souhaitez réinstaller la base de données, vous devez soit désinstaller l’application Android soit appeler la méthode remplacer()
qui supprimera d’abord le fichier mabase.sqlite
avant de le copier.
Ensuite, on fixera le nom de la base de données avec setDatabaseName()
et on l’ouvrira avec open()
.
Releve::Releve(QObject *parent) : QObject(parent), erreurConnexion(false)
{
db = QSqlDatabase::addDatabase("QSQLITE");
QFile sfile(QString("assets:/db") + QString("/" + NOM_BD));
QFile dfile(QString("./" + NOM_BD));
copier(sfile, dfile);
// ou :
//remplacer(sfile, dfile);
if(estBDPresente(QString("./") + NOM_BD))
{
db.setDatabaseName(QString("./") + NOM_BD);
db.open();
erreurConnexion = false;
QSqlQuery r;
r.exec("pragma foreign_keys = on;");
}
else
{
erreurConnexion = true;
}
emit erreurChanged();
}
bool Releve::copier(QFile &sfile, QFile &dfile)
{
if (sfile.exists())
{
return sfile.copy(dfile.fileName());
}
return false;
}
bool Releve::remplacer(QFile &sfile, QFile &dfile)
{
bool retour;
// supprime le fichier destination
if (sfile.exists())
{
if (dfile.exists())
{
retour = dfile.remove();
if(!retour)
{
return false;
}
}
}
return copier(sfile, dfile);
}
bool Releve::estBDPresente(QString BD)
{
QFile fichier(BD);
return fichier.exists();
}
Ici, le signal erreurChanged()
est utile pour prévenir l’utilisateur si un problème d’ouverture a eu lieu. Dans ce cas, on affichera une boîte de dialoge avec un message d’erreur :
...
onErreurChanged: {
if (Releve.erreurConnexion)
{
messageErreur.text = qsTr("Problème d'ouverture de la base de données !")
erreurDialog.open()
}
}
...
Dialog {
id: erreurDialog
x: (parent.width - width) / 2
y: (parent.height - height) / 2
standardButtons: Dialog.Close
title: "Erreur"
Label {
id: messageErreur
text: ""
}
}
Pour exécuter des requêtes SQL, on aura besoin d’un objet QSqlQuery
. Il faudra distinguer les requêtes SQL SELECT
, qui retournent des résultats, des requêtes INSERT
, UPDATE
ou DELETE
qui ne produisent pas de résultats en retour.
On passera par des méthodes privées :
executerRequete()
qui recevra en paramètre une requête SQL de type INSERT
, UPDATE
ou DELETE
recuperer()
qui recevra en paramètre une requête SQL de type SELECT
et un type pour stocker les données sélectionnées par la requête. On surchargera la méthode pour les types suivants : QString
, QStringList
et QVector<QStringList>
.bool Releve::executerRequete(QString requete)
{
QSqlQuery r;
if(db.isOpen())
{
bool retour = r.exec(requete);
return retour;
}
return false;
}
bool Releve::recuperer(QString requete, QStringList &donnees)
{
QSqlQuery r;
bool retour;
if(db.isOpen())
{
retour = r.exec(requete);
if(retour)
{
// pour chaque enregistrement
while ( r.next() )
{
// on stocke l'enregistrement dans le QStringList
donnees << r.value(0).toString();
}
return true;
}
else
{
return false;
}
}
else
return false;
}
bool Releve::recuperer(QString requete, QVector<QStringList> &donnees)
{
QSqlQuery r;
bool retour;
QStringList data;
if(db.isOpen())
{
retour = r.exec(requete);
if(retour)
{
// pour chaque enregistrement
while ( r.next() )
{
// on récupère sous forme de QString la valeur de tous les champs sélectionnés
// et on les stocke dans une liste de QString
for(int i=0;i<r.record().count();i++)
data << r.value(i).toString();
// on stocke l'enregistrement dans le QVector
donnees.push_back(data);
// on efface la liste de QString pour le prochain enregistrement
data.clear();
}
return true;
}
else
{
return false;
}
}
else
return false;
}
bool Releve::recuperer(QString requete, QString &donnee)
{
QSqlQuery r;
bool retour;
if(db.isOpen())
{
retour = r.exec(requete);
if(retour)
{
// on se positionne sur l'enregistrement
r.first();
// on vérifie l'état de l'enregistrement retourné
if(!r.isValid())
{
return false;
}
// on récupère sous forme de QString la valeur du champ
if(r.isNull(0))
{
return false;
}
donnee = r.value(0).toString();
return true;
}
else
{
return false;
}
}
else
return false;
}
L’utilisation des méthodes recuperer()
se fera à partir des méthodes publiques suivantes :
getReleves()
qui récupera la liste des relevés
lireReleve()
qui recevra en paramètres le nom du relevé et le nombre des dernières mesures désirées (0 signifiera toutes les mesures du relevé) et qui fabriquera la liste des Mesure
lireMoyenneReleve()
qui recevront en paramètres le nom du relevé et le nombre des dernières mesures à prendre en compte dans le calcul de la moyenne (0 signifiera toutes les mesures du relevé) et qui affectera l’attribut moyenne
QStringList Releve::getReleves()
{
if(!erreurConnexion)
{
releves.clear();
recuperer("SELECT description FROM releves ORDER BY releves.description ASC", releves);
}
return releves;
}
bool Releve::lireReleve(QString releve, int nb)
{
if(erreurConnexion)
return false;
QVector<QStringList> relevesMesures;
QString requete;
if(nb > 0)
{
// les nb dernières mesures
requete = "SELECT * FROM (SELECT mesures.horodatage, mesures.temperature FROM mesures INNER JOIN releves ON releves.idReleve = mesures.idReleve WHERE releves.description = '" + releve + "' ORDER BY mesures.horodatage DESC LIMIT " + QString::number(nb) + ") tmp ORDER BY horodatage ASC LIMIT " + QString::number(nb);
}
else
{
// toutes les mesures du relevé
requete = "SELECT mesures.horodatage,mesures.temperature FROM mesures INNER JOIN releves ON releves.idReleve = mesures.idReleve WHERE releves.description = '" + releve + "' ORDER BY mesures.horodatage ASC";
}
qDeleteAll(mesures);
mesures.clear();
if(recuperer(requete, relevesMesures))
{
for(int i=0;i<relevesMesures.count();i++)
{
Mesure *m = new Mesure(QDateTime::fromString(relevesMesures.at(i).at(0), "yyyy-MM-dd HH:mm:ss"), relevesMesures.at(i).at(1).toDouble(), this);
mesures.append(m);
}
if(mesures.count() > 0)
{
emit mesuresUpdated();
return true;
}
else
{
emit mesuresErreur();
}
}
else
{
qDebug() << Q_FUNC_INFO;
emit mesuresErreur();
}
return false;
}
void Releve::lireMoyenneReleve(QString releve, int nb)
{
if(erreurConnexion)
return;
QString requete;
if(nb > 0)
{
// la moyenne des nb dernières mesures
requete = "SELECT AVG(temperature) FROM (SELECT mesures.horodatage, mesures.temperature FROM mesures INNER JOIN releves ON releves.idReleve = mesures.idReleve WHERE releves.description = '" + releve + "' ORDER BY mesures.horodatage DESC LIMIT " + QString::number(nb) + ") tmp ORDER BY horodatage ASC LIMIT " + QString::number(nb);
}
else
{
// la moyenne de toutes les mesures du relevé
requete = "SELECT AVG(mesures.temperature) FROM mesures INNER JOIN releves ON releves.idReleve = mesures.idReleve WHERE releves.description = '" + releve + "' ORDER BY mesures.horodatage ASC";
}
if(recuperer(requete, moyenne))
{
emit moyenneUpdated();
}
}
L’accès au relevé de Mesure
se fera par l’accesseur de la propriété listeReleves
:
QVariant Releve::getMesures()
{
return QVariant::fromValue(mesures);
}
On créera une nouvelle classe Mesure
pour stocker une mesure qui est caractérisée par :
double
)QDateTime
que l’on retournera sous la forme d’un QString
)#ifndef MESURE_H
#define MESURE_H
#include <QObject>
#include <QDateTime>
class Mesure : public QObject
{
Q_OBJECT
Q_PROPERTY(double temperature READ getTemperature NOTIFY mesure)
Q_PROPERTY(QString horodatage READ getHorodatage NOTIFY mesure)
public:
explicit Mesure(QDateTime horodatage, double temperature, QObject *parent = nullptr);
double getTemperature() const;
QString getHorodatage() const;
private:
QDateTime horodatage;
double temperature;
signals:
void mesure();
public slots:
};
#endif // MESURE_H
L’interface utilisateur sera décrite en QML avec Qt Quick Controls 2.
On utilisera un AppWindow
dans main.qml
:
import QtQuick 2.11
import QtQuick.Window 2.3
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
ApplicationWindow {
id: window
title: qsTr("Mon ApplicationBD")
width: Screen.desktopAvailableWidth
height: Screen.desktopAvailableHeight
property bool erreur: Releve.erreurConnexion
visible: true
onErreurChanged: {
if (Releve.erreurConnexion)
{
messageErreur.text = qsTr("Problème d'ouverture de la base de données !")
erreurDialog.open()
}
}
header: ToolBar {
RowLayout {
spacing: 20
anchors.fill: parent
Label {
id: titre
text: qsTr("Mon ApplicationBD")
font.pixelSize: 20
elide: Label.ElideRight
horizontalAlignment: Qt.AlignHCenter
verticalAlignment: Qt.AlignVCenter
Layout.fillWidth: true
}
ToolButton {
id: toolButton2
icon.name: "menu"
onClicked: menu.open()
}
}
}
Menu {
id: menu
x: parent.width - width
transformOrigin: Menu.TopRight
MenuItem {
id: about
text: "À propos"
onTriggered: {
aPropos.open()
}
}
}
Dialog {
id: aPropos
modal: true
focus: true
title: "À propos"
x: (window.width - width) / 2
y: window.height / 6
width: Math.min(window.width, window.height) / 3 * 2
contentHeight: message.height
Label {
id: message
width: aPropos.availableWidth
text: "Exemple d'utilisation d'une Base de données SQLite en Qt Quick Controls 2."
wrapMode: Label.Wrap
font.pixelSize: 12
}
}
Dialog {
id: erreurDialog
x: (parent.width - width) / 2
y: (parent.height - height) / 2
standardButtons: Dialog.Close
title: "Erreur"
Label {
id: messageErreur
text: ""
}
}
footer: Label {
width: parent.width
horizontalAlignment: Qt.AlignRight
padding: 10
text: qsTr("© http://tvaira.free.fr")
font.pixelSize: 14
font.italic: true
}
Releves
{
id: pageReleve
}
}
L’affichage du relevé se fera dans Releve.qml
. On listera les noms de relevés dans un ComboBox
et on ajoutera la possibilité de choisir le nombre de dernières mesures que l’on souhaite.
On complètera la GUI avec trois boutons :
On positionnera les éléments avec Column
et Row
.
import QtQuick 2.9
import QtQuick.Window 2.2
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
Page {
width: parent.width
padding: 10
Connections {
target: Releve
onMoyenneUpdated: {
labelMoyenne.text = "Moyenne : " + Releve.moyenne + " °C";
labelMoyenne.color = "#0000FF"
labelMoyenne.visible = true
}
onMesuresErreur: {
labelMoyenne.text = "Aucune mesure pour ce relevé !";
labelMoyenne.color = "#FF0000"
labelMoyenne.visible = true
listeMesures.visible = false
}
}
Column {
id: colonnePage
width: parent.width
spacing: 15
Row {
Label {
id: introduction
wrapMode: Label.Wrap
text: "Relevés de mesures de températures dans les serres"
}
}
Row {
ComboBox {
id: choixReleve
width: introduction.width
model: Releve.listeReleves
enabled: true;
}
}
Row {
spacing: 20
Label {
wrapMode: Label.Wrap
text: "Nb dernières mesures :"
anchors.verticalCenter: parent.verticalCenter
}
SpinBox {
id: limiteNbMesures
enabled: choixReleve.count > 0 ? true : false;
value: 5
editable: true
}
}
Row {
spacing: 20
Button {
id: annuleReleve
enabled: choixReleve.count > 0 ? true : false;
text: "Annuler"
onClicked: {
listeMesures.visible = false
labelMoyenne.visible = false
}
}
Button {
id: voirReleve
enabled: choixReleve.count > 0 ? true : false;
text: "Voir le relevé"
onClicked: {
if(Releve.lireReleve(choixReleve.currentText, limiteNbMesures.value))
{
listeMesures.visible = true
Releve.lireMoyenneReleve(choixReleve.currentText, limiteNbMesures.value);
}
}
}
Button {
id: purgerReleve
enabled: choixReleve.count > 0 ? true : false;
text: "Purger le relevé"
onClicked: {
Releve.executerRequete("DELETE FROM releves WHERE releves.description = '" + choixReleve.currentText + "'");
listeMesures.visible = false
labelMoyenne.visible = false
}
}
}
Row {
Mesures
{
id: listeMesures
visible: false
width: colonnePage.width //onglets.width
height: Screen.desktopAvailableHeight * 0.45
color: "#cfcfcf"
}
}
Row {
Label {
id: labelMoyenne
visible: false
anchors.margins: 5
wrapMode: Label.Wrap
text: ""
}
}
}
}
L’affichage des mesures se fera dans Mesures.qml
. On utilisera un ListView
.
ListView
permet une vue en liste des éléments fournis par un model
, ici notre relevé accessible par la propriété mesures
. L’affichage des éléments de la liste sera pris en charge par un delegate
. Le délégué fournit un modèle définissant chaque élément instancié par la ListView
. Le type ItemDelegate
utilisé ici est l’élément de vue standard.
La ListView
étant défilable dans la vue en fonction du nombre de mesures, on ajoutera un ScrollIndicator
.
import QtQuick 2.9
import QtQuick.Window 2.2
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import QtQuick.Controls.Material 2.1
Rectangle {
ListView {
id: listeMesures
anchors.fill: parent
spacing: 10
anchors.margins: 5
model: Releve.mesures
delegate:
ItemDelegate {
width: parent.width
height: colonne.implicitHeight
Column {
id: colonne
padding: 5
Text { text: model.modelData.horodatage; }
Text { text: '<b>Température : ' + model.modelData.temperature + ' °C</b>'; font.italic: true }
}
}
focus: true
clip: true
ScrollIndicator.vertical: ScrollIndicator { }
}
}
On obtient :
Lien : MonApplicationBD.zip
Qt fournit le module Qt Charts pour dessiner des graphiques.
Voir : Qt pour Android : dessiner des graphiques
Il est aussi possible d’utiliser une base de données MySQL.