FAQ Qt

Quelques questions fréquemment posées sur Qt …

Ce document est toujours en cours de rédaction.
Dernière modification : 17/05/2021 (rev. 6)

Vous pouvez consulter la FAQ Qt (fr) très complète du site developpez.com.

Qt

Qu’est-ce que Qt ?

Qt est une bibliothèque logicielle orientée objet développée en C++. La bibliothèque logicielle fournie par Qt regroupe un ensemble de classes. Qt offre notamment des composants (widgets) permettant de concevoir des interfaces graphiques (GUI).

Remarque : Bien qu’écrite en C++, Qt peut être utilisée dans d’autres environnements comme Python par exemple.

Qu’est-ce qu’un framework ?

Littéralement un “cadre de développement”. Qt peut être considéré comme un framework car il fournit un ensemble de classes C++ (formant une bibliothèque logicielle) ET une manière (un “cadre”) de les utiliser (notamment le mécanisme signal/slot propre à Qt).

Qu’est-ce qu’une API ? Qu’est-ce qu’une bibliothèque logicielle ?

C’est une interface de programmation d’application (API = Application Programming Interface) qui est un ensemble normalisé de classes et/ou de fonctions utilisables par un logiciel. L’API (et donc ses services) est offerte par une bibliothèque logicielle.

Une bibliothèque logicielle est une collection de fonctions prêtes à être utilisées par des programmes. Le plus souvent, les fonctions sont déjà “compilées” et la bibliothèque contient donc du code objet (du code “machine”). Globalement, les bibliothèques se distinguent des programmes exécutables par le simple fait qu’elles ne contiennent pas de point d’entrée main(). Le mot « librairie » est souvent utilisé à tort. Il ne faut donc pas le faire !

Les bibliothèques sont manipulées par l’éditeur de lien (pour fabriquer un exécutable) et le système d’exploitation (pendant l’exécution du programme).

Une bibliothèque est dite statique si elle est destinée à être copiée dans le programme qui l’utilise pour former un seul et unique exécutable. L’exécutable obtenu est plus volumineux mais “autonome”. Il ne pourra pas être mis à jour (seulement en le refabriquant).

Une bibliothèque est dite partagée si elle est destinée à être associée au programme au moment où il est exécuté. On parle aussi de “bibliothèque à lien dynamique” ou plus simplement de “bibliothèque dynamique”. L’exécutable obtenu est moins volumineux mais il est dépendant de la présence des bibliothèques au moment de son exécution. Il pourra être mis à jour en faisant simplement évoluer les bibliothèques. Les bibliothèques peuvent être partagées entre plusieurs exécutables en même temps.

Pour identifier les bibliothèques dynamiques d’un exécutable, on peut utiliser :

Remarque : les fichiers d’extension .h (ou .hh ou .hpp) ne sont pas à proprement parler des bibliothèques. Ce sont des fichiers d’en-têtes (header) contenant exclusivement des déclarations. En C/C++, les déclarations sont donc regroupées dans des fichiers d’en-têtes (header) et ne génèrent aucun code objet (code “machine”). Les déclarations énoncent seulement ce qui va exister en précisant les noms et les types.

Quelle est l’extension de fichier pour les bibliothèques logicielles dynamiques sous © Windows ? sous GNU/Linux ?

Les extensions des fichiers de bibliothèques logicielles dynamiques :

  • Sous © Windows : .dll (dynamic link library)
  • Sous GNU/Linux : .so (shared object)

Remarque : les extensions des fichiers de bibliothèques statiques sont généralement .a ou .lib.

Lors de la fabrication de l’exécutable, c’est la phase d’édition de liens (linker) qui a la responsabilité de lier les bibliothèques. Il y aura donc soit une édition de liens dynamique (choix par défaut) soit une édition de liens statique. Avant, on utilisait l’utilitaire ld. Maintenant, l’édition de liens est intégrée au compilateur avec des outils comme gcc/g++.

Qu’est-ce qu’un module Qt ? Que contient-il ?

Un module Qt est un service logiciel (une “fonctionnalité” de la bibliothèque Qt) fourni sous la forme d’un ensemble de classes. Le module doit être “activé” dans la variable QT du fichier .pro d’un projet Qt pour pouvoir l’utiliser.

La bibliothèque logicielle Qt est donc modulaire. Généralement, le module Qt est fourni par une seule bibliothèque partagée (.dll ou .so) :

  • libQt5Core.so pour le module core, libQt5Gui.so pour le module gui et libQt5Widgets.so pour le module widgets sous GNU/Linux
  • Qt5Core.dll pour le module core, Qt5Gui.dll pour le module gui et Qt5Widgets.dll pour le module widgets © Windows

Dans le cas de conception d’interfaces graphiques (GUI) avec Qt5, les modules core, gui et widgets sont obligatoires.

Par exemple, le module widgets contient les classes QWidget, QLabel, QPushButton, QComboBox, … On peut remarquer que toutes les classes Qt sont préfixées d’un Q. Elles héritent aussi toutes de la classe mère QObject qui définit la notion d’un “objet” spécifique à Qt.

Les modules Qt sont nombreux (cela fait la richesse de cette API), on peut citer : sql (accès aux base de données), network (réseau), charts (graphiques), etc …

Qu’est-ce que Qt Creator ?

Qt Creator est un environnement de développement intégré (EDI). Il a été créé avec le framework Qt. Il est censé faciliter la conception de programmes Qt.

Qu’est-ce qu’un environnement de développement intégré (EDI) ?

Un environnement de développement intégré (EDI ou IDE pour Integrated Development Environment) est un ensemble d’outils intégrés dans un même logiciel et qui permettent de développer des programmes.

Il comporte généralement (et au minimum) :

  • un éditeur de texte destiné à la programmation,
  • des accès rapides au compilateur, l’éditeur de liens pour fabriquer puis exécuter les programmes
  • un débogueur
  • une documentation intégrée sur les APIs

En gagnant en expertise, le développeur recherchera et utilisera d’autres outils intégrés à un EDI pour le rendre plus efficace dans son travail. On peut citer sans aucun doute au moins un outil de travail collaboratif comme subversion (svn) ou git.

Pourquoi ne faut-il pas confondre Qt et Qt Creator ?

Qt est une bibliothèque logicielle. Qt Creator est un environnement de développement intégré (EDI).

Et cela n’a strictement rien à voir !

Il est très important de comprendre que Qt Creator n’est pas indispensable pour créer des programmes Qt. Un simple éditeur de texte (comme vim), un compilateur C++ (comme g++), un utilitaire (comme make) et bien évidemment les bibliothèques et outils Qt (qmake, moc, uic, …) suffisent pour fabriquer un programme Qt !

Qu’est-ce que la programmation événementielle ?

La programmation événementielle est une programmation basée sur les événements. Elle est utilisée principalement dans les environnements graphiques (GUI). C’est notamment le cas dans les systèmes d’exploitation avec un environnement graphique (comme © Windows par exemple).

La programmation événementielle est architecturée autour d’une boucle principale fournie et divisée en deux sections : la première section attend et détecte les événements, la seconde les gère.

Pour chaque événement à gérer (un clic de souris, un appui sur une touche, …), il faut lui associer une action à réaliser (le code d’une fonction ou méthode) : c’est le gestionnaire d’événement (handler).

Ensuite, à chaque fois que l’événement sera détecté par la boucle d’événements, le gestionnaire d’événement (handler) sera alors exécuté.

Remarque : dans Qt, la boucle d’événement est réalisée par l’appel bloquant a.exec() dans le fichier main.cpp. Si on le remplace par un return 0, la fenêtre s’affiche (show()) mais se ferme immédiatement et le programme se termine !

Quel est le nom donné par Qt à un événement ?

Un signal. Dans Qt, un signal se déclare comme une méthode (une fonction membre) d’une classe. Évidemment, un signal ne se définit pas. Un signal n’est donc pas réellement une méthode et n’a pas de “code” associé. On rappelle qu’un signal est un événement (un clic sur un bouton par exemple).

Lire : Signal/Slot

Qu’est-ce qu’un slot dans Qt ?

Un slot est un gestionnaire d’événement (ou de signal Qt). Concrètement, un slot est une méthode (une fonction membre) d’une classe. Il représente le code qui sera exécuté lorsque le signal (auquel il est connecté) sera émis.

Pour connecter (associer) un signal à un slot, on utilise l’appel connect().

Pour émettre “manuellement” un signal, on utilise emit.

Qt fournit “automatiquement” des signaux et des slots “prêts à l’emploi”. Il est bien évident que l’on peut créer ses propres signaux et ses propres slots.

Lire : Signal/Slot

Quel est le rôle du fichier .pro dans Qt ?

Un projet Qt est défini par un fichier d’extension .pro. Ce fichier est très important car il décrit le contenu d’un projet Qt par l’intermédiaire de variables.

Au minimum, il contient :

# la liste des modules Qt utilisés
QT      += core gui widgets
# la liste des fichiers sources (de définitions)
SOURCES += main.cpp xxx.cpp
# la liste des fichiers d'en-têtes (de déclarations)
HEADERS += xxx.h

Il existe d’autres variables permettant par exemple de lister des dépendances, des paramètres passés au compilateur, des ressources, etc.

Ce fichier .pro est utilisé par l’utilitaire qmake fourni par Qt.

Remarque : Qt Creator génère probablement un fichier .pro.user qui est spécifique à la configuration personnelle de l’utilisateur. Ce fichier n’a aucune “valeur” dans un développement collaboratif. Il est d’autre part spécifique à la version de Qt Creator utilisée et à la plateforme (© Windows, GNU/Linux, MacOs, …).

Qu’est-ce que l’utilitaire qmake ? Qui le fournit ?

qmake est un utilitaire fourni par Qt.

qmake prend en entrée un fichier de projet Qt .pro et génère un fichier de règles (Makefile par exemple) permettant de fabriquer un exécutable spécifique à la plateforme (© Windows, GNU/Linux, MacOs, iOS, Android, …).

Attention : à chaque fois que le fichier .pro est modifié, l’utilitaire qmake doit être exécuté pour que le fichier de règles de fabrication soit mis à jour. Idem lorsque l’on change de plateforme.

Remarque : qmake est aussi capable de générer (option -project) initialement un fichier de projet Qt .pro.

Quel est le rôle d’un fichier Makefile ?

Un fichier Makefile contient la description des opérations nécessaires pour générer des fichiers (une application, une bibliothèque, …). Pour faire simple, il contient les règles à appliquer lorsque les dépendances ne sont plus respectées (par exemple recompiler un code source qui a été modifié).

Une règle est une suite d’instructions qui seront exécutées pour construire une cible si la cible n’existe pas ou si des dépendances sont plus récentes.

Remarque : De nos jours, les fichiers Makefile sont de plus en plus rarement générés à la main par le développeur mais construit à partir d’outils automatiques tels qu’autoconf, cmake ou qmake qui facilitent la génération de Makefile complexes et spécifiquement adaptés à l’environnement dans lequel les actions de production sont censées se réaliser.

Quel est le nom de l’utilitaire qui utilise les fichiers Makefile ? A quoi sert-il ?

Un fichier Makefile sera lu et exécuté par l’utilitaire make.

make est un utilitaire de type “moteur de production” : il sert à appeler des commandes créant des fichiers. Make exécute les commandes seulement si elles sont nécessaires.

Est-ce que Qt utilise les fichiers Makefile ?

Oui.

Est-ce que le contenu d’un fichier Makefile est le même sous © Windows et sous GNU/Linux ? Dans Qt, qui génère le fichier Makefile ?

Non ! Les fichiers Makefile sont différents et spécifiques à la plateforme utilisée. D’où l’intérêt d’utiliser un utilitaire comme qmake qui sera capable de générer les Makefile spécifiques à la plateforme.

Par exemple :

  • la convention pour les chemins de fichiers et de répertoires est différente : © Windows utilise \ et GNU/Linux et MacOs le /.
  • les extensions de fichiers sont différentes : .o sous GNU/Linux et .obj sous © Windows, .so sous GNU/Linux et .dll sous © Windows, etc …
  • les chemins où sont installés les ressources sont différents
  • etc …

Qu’est-ce qu’un widget dans Qt ?

Avec Qt, les éléments (ou composants) graphiques prédéfinis sont appelés des widgets (pour windows gadgets).

Les widgets sont les éléments principaux de la création d’interface graphique utilisateur (GUI) avec Qt.

Les widgets peuvent afficher des données et des informations sur un état, recevoir des actions de l’utilisateur et agir comme un conteneur pour d’autres widgets qui doivent être regroupés. Les widgets classiques sont les boutons (QPushButton) , les listes déroulantes (QComboBox) etc …

Quelques caractéristiques de base des widgets :

  • ils sont créés “cachés” (hide)
  • ils sont capable de se “peindre” (paint) donc d’être “visible”
  • ils sont capable de recevoir les événements souris, clavier
  • ils sont tous rectangulaires
  • ils sont initialisés par défaut en coordonnées 0,0
  • ils sont ordonnés suivant l’axe z (la profondeur)
  • ils peuvent avoir un widget parent et des widgets enfants

Qu’est-ce qu’un layout dans Qt ? Pourquoi faut-il obligatoirement utiliser des layouts ?

Un layout est un système de disposition pour l’organisation et le positionnement automatique des widgets. Ce gestionnaire de placement permet l’agencement facile et le bon usage de l’espace disponible. Un layout est invisible.

Il permet un positionnement relatif et ainsi d’éviter un positionnement absolu (généralement très déconseillé car cela pose des problèmes de résolution d’écran, de redimensionnement, …). Les layouts peuvent s’imbriquer les uns dans les autres.

Les layouts sont spécialisés. Dans Qt, les plus utilisés sont :

  • QHBoxLayout : placement automatique en horizontal
  • QVBoxLayout : placement automatique en vertical
  • QGridLayout : placement en grille

Qu’est-ce que Qt Designer ?

Qt Designer est un logiciel qui permet de créer des interfaces graphiques Qt dans un environnement convivial. L’utilisateur, par glisser-déposer, place les composants d’interface graphique et y règle leurs propriétés facilement. Les fichiers d’interface graphique sont formatés en XML et portent l’extension .ui. Lors de la compilation, le fichier d’interface graphique .ui est converti en classe C++ par l’utilitaire uic, fourni par Qt.

Remarque : Qt Designer n’est évidemment pas indispensable pour produire des interfaces graphiques Qt.

Qu’est-ce qu’un kit de développement ?

Un kit de développement logiciel (SDK = Software Development Kit ou devkit) est un ensemble d’outils logiciels destinés aux développeurs, facilitant le développement d’un logiciel sur une plateforme donnée (par exemple, iOS, Android, GNU/Linux, OS X, Microsoft Windows).

Dans Qt, un kit de développement doit être présent et configuré. Il regroupe au minimum qmake, make (ou cmake) et gcc/g++/gdb.

Par exemple dans Qt Creator, la notion de kit de développement est la suivante :

Remarque : dans Qt Creator, un kit peut être auto-détecté ou configuré manuellement.

C’est la détection et la présence de qmake qui permet d’obtenir les informations sur la configuration de Qt (chemins, versions, etc …) à partir d’un fichier qt.conf :

Evidemment, plusieurs outils (et kits) peuvent cohabiter :

Sous Ubuntu, la configuration de Qt Creator est sotckée dans le fichier ~/.config/QtProject/QtCreator.ini. Il n’est pas inutile d’en faire une sauvegarde !

Et la configuration de Qt (installée par exemple à partir des paquets de la distribution ) est stokée dans le fichier /usr/lib/x86_64-linux-gnu/qt5/qt.conf :

$ cat /usr/lib/x86_64-linux-gnu/qt5/qt.conf
[Paths]
Prefix=/usr
ArchData=lib/x86_64-linux-gnu/qt5
Binaries=lib/qt5/bin
Data=share/qt5
Documentation=share/qt5/doc
Examples=lib/x86_64-linux-gnu/qt5/examples
Headers=include/x86_64-linux-gnu/qt5
HostBinaries=lib/qt5/bin
HostData=lib/x86_64-linux-gnu/qt5
HostLibraries=lib/x86_64-linux-gnu
Imports=lib/x86_64-linux-gnu/qt5/imports
Libraries=lib/x86_64-linux-gnu
LibraryExecutables=lib/x86_64-linux-gnu/qt5/libexec
Plugins=lib/x86_64-linux-gnu/qt5/plugins
Qml2Imports=lib/x86_64-linux-gnu/qt5/qml
Settings=/etc/xdg
Translations=share/qt5/translations

Remarque : le fichier qt.conf est lu par qmake.

QString et les chaînes de caractères

Comment convertir un nombre en QString ?

On utilise la méthode statique QString::number() pour convertir des nombres.

QString dix = QString::number(10); // en entier

QString a = QString::number(10, 16); // en hexadécimal

double d = 0.5;
QString unDemi = QString::number(d); // un double

QString unTiers = QString::number(1./3., 'f', 2); // avec une précision

Comment convertir un QString en nombre ?

La classe QString fournit des méthodes pour convertir une chaîne de caractères en nombre : toInt(), toLong(), toFloat(), toDouble(), …

QString dix = "10";
int i = dix.toInt();

QString unDemi = "0.5";
double d = unDemi.toDouble();

De plus ces méthodes possèdent des paramètres optionnels :

  • un pointeur vers un booléen permettra de vérifier si la conversion a été possible ou pas ;
  • la base pour les types entiers.
QString str = "FF";
bool ok;
int n = str.toInt(&ok, 16);
qDebug() << n << ok; // 255 true

Remarque : lire Comment afficher des messages de débogage ? pour voir l’utilisation de qDebug().

Comment formater une chaîne de caractères ?

La classe QString fournit la méthode arg() pour formater une chaîne de caractères.

int age = 20;
QString str = QString("J'ai %1 ans").arg(age); // "J'ai 20 ans"

int age = 20;
double taille = 1.99;
QString str = QString("J'ai %1 ans et je mesure %2.").arg(age).arg(taille); // "J'ai 20 ans et je mesure 1.99."

int n = 10;
QString str = QString("0x") + QString("%1").arg(n, 2, 16, QChar('0')).toUpper(); // en hexadécimal : "0x0A"

Remarque : La méthode arg() possède énormément de versions différentes. Documentation : doc.qt.io/qt-4.8/qstring.html

Comment convertir un QString en nombre hexadécimal et réciproquement ?

Pour obtenir une chaîne de caractères en hexadécimal :

int dix = 10; // en entier

QString a = QString::number(dix, 16); // a en hexadécimal

À partir d’une chaîne de caractères en hexadécimal :

QString str = "0A"; // une valeur en hexadécimal

bool ok;
int dix = str.toInt(&ok, 16);
qDebug() << n << ok; // 10 true

Remarque : lire Comment afficher des messages de débogage ? pour voir l’utilisation de qDebug().

Comment encoder les caractères avec QString ?

Par défaut, QString encode les chaînes de caractères en ISO 8859-1 (latin-1) remplaçant le standard ASCII mais il ne couvre pas complètement le français. En France, nous utilisons l’encodage ISO 8859-15 (latin-9) qui pallie ces problèmes. Sous Unix/Linux, l’encodage par défaut est l’UTF-8.

Il est possible d’encoder directement une chaîne de caractères en utilisant les méthodes statiques QString::fromAscii(), QString::fromLatin1(), QString::fromUtf8() et QString::fromLocal8Bit().

Si vous rencontrez des problèmes d’encodage (par exemple les accents), utilisez la méthode qui correspond :

QString str = QString::fromUtf8("Une chaîne de caractères");

La classe QTextCodec fournit des conversions entre les encodages de texte.

Il est possible de choisir son encodage de manière générale avec la méthode statique QTextCodec::setCodecForCStrings() :

QTextCodec::setCodecForCStrings(QTextCodec::codecForName("UTF-8"));

Comment effectuer un traitement avec un QString ?

La classe QString contient de nombreuses méthodes pour manipuler des chaînes de caractère. Sa documentation : doc.qt.io/qt-4.8/qstring.html.

Quelques méthodes intéressantes pour faire un traitement sur une trame par exemple :

  • startsWith() et endsWith() pour vérifier les délimiteurs de début de trame (par exemple le dollar $) et de fin de trame (par exemple la séquence “\r\n”)
  • contains() ou count() pour détecter (ou compter) la présence de certains caractères (le plus souvent des délimiteurs comme la virgule ,, le point-virgule ; ou l’étoile *)
  • section() ou split() pour le découpage à partir de délimiteurs (comme la virgule ,, le point-virgule ; …) ou mid() pour une extraction sans délimiteur
  • replace() pour le remplacement et remove() pour la suppression de caractères
  • indexOf() pour obtenir la position d’un caractère

Vérifier le format d’une trame NMEA0183 GGA :

QString phrase = "$GPGGA,064036.289,4836.5375,N,00740.9373,E,1,04,3.2,200.2,M,,,,0000*0E";
// Faire des essais :
//QString phrase = "";
//QString phrase = "GPGGA,064036.289,4836.5375,N,00740.9373,E,1,04,3.2,200.2,M,,,,0000*0E";
//QString phrase = "$GPAAM,A,A,0.10,N,WPTNME*32";
//QString phrase = "$GPGGA,064036.289,4836.5375,N,00740.9373,E,1,04,3.2,200.2,M,,,,0000";

QString checksum;
const QString debutTrame = "$";
const QString typeTrame = "GPGGA";
const QString debutChecksum = "*";

// phrase vide ?
if(phrase.length() != 0)
{    
    // est-ce une phrase NMEA 0183 ?
    if(phrase.startsWith(debutTrame))
    {    
        // est-ce la bonne phrase ?
        if(phrase.startsWith(debutTrame + typeTrame))
        {
            // y-a-t-il un checksum ?
            if(phrase.contains(debutChecksum))
            {
                checksum = phrase.section(debutChecksum, 1, 1);
                qDebug() << "checksum extrait : 0x" << checksum;
            }
            else
                qDebug() << "Attention : il n'y a pas de checksum dans cette phrase !";
        }
        else
            qDebug() << "Erreur : ce n'est pas une trame GGA !";
    }
    else
        qDebug() << "Erreur : ce n'est pas une trame NMEA 0183 !";
}
else
    qDebug() << "Erreur : phrase vide !";

Remarque : lire Comment afficher des messages de débogage ? pour voir l’utilisation de qDebug().

Récupérer l’horodatage d’une trame NMEA0183 GGA :

QString phrase = "$GPGGA,064036.289,4836.5375,N,00740.9373,E,1,04,3.2,200.2,M,,,,0000*0E";    
QString horodatage;
unsigned int heure, minute;
double seconde;

// découpe la trame avec le délimiteur ',' et récupère le deuxième champ
horodatage = phrase.section(',', 1, 1); 
// découpe une chaine à partir d'une position et un nombre de caractères
heure = horodatage.mid(0, 2).toInt();
minute = horodatage.mid(2, 2).toInt();
seconde = horodatage.mid(4, 2).toDouble();

Comment convertir un QString en chaîne C (char *) et réciproquement ?

La classe QString ne fournit pas de méthode directe pour obtenir une chaîne de type char *. Il faut passer par les méthodes toAscii() (seulement en Qt4), toLatin1(), toUtf8(), toLocal8Bit() qui retournent un objet de type QByteArray. La classe QByteArray fournit les méthodes constData() pour obtenir un const char * et data() pour un char *.

QString str("Hello wordl!");
printf(str.toUtf8().constData());

Par contre, il existe un constructeur QString(const char * str) pour convertir une chaîne C (char *) en QString.

On peut aussi utiliser la fonction qPrintable() qui reçoit en paramètre une référence sur un QString et qui retourne un const char * : const char * qPrintable(const QString & str). C’est l’équivalent de str.toLocal8Bit().constData().

Comment transformer un QString en std::string et réciproquement ?

Pour transformer un std::string en QString, il faut utiliser la méthode statique QString::fromStdString().

Pour l’opération inverse, on utilisera la méthode toStdString().

Comment récupérer la date et l’heure courante sous forme de chaînes de caractères ?

On utilise un objet de la classe QDateTime (ou QDate ou QTime) que l’on initialise avec la méthode statique QDateTime::currentDateTime() (ou QDate::currentDate() ou QTime::currentTime()). On obtient une chaîne de caractères formatée en appelant la méthode toString(format) :

QDateTime maintenant = QDateTime::currentDateTime();
QString date = maintenant.toString("dd/MM/yyyy");
QString heure = maintenant.toString("hh:mm:ss");

Il existe plusieurs paramètres de format.

Remarque : il existe une fonction inverse fromString() qui permet d’initialiser un objet QDateTime (ou QDate ou QTime) à partir d’une chaîne de caractères formatée.

L’affichage de données sous forme de liste

Comment utiliser un QListView ?

On peut utiliser le pattern Model-View de Qt pour associer des données (le modèle) à un affichage (une vue) sous forme de list.

Lire qt-modelview.pdf et tp-qt-5-calepin.pdf.

La classe QListView fournit une implémentation par défaut d’un modèle/vue sous la forme d’une vue en liste qui affiche les éléments contenus dans un modèle.

Il existe d’autres types de vue : sous forme de table (QTableView) et sous forme d’arbre (QTreeView).

QListView *listeViewDonnees;
QStringListModel *modele;
QStringList donnees;

donnees << "donnee 1" << "donnee 2" << "donnee 3";

listeViewDonnees = new QListView(this);
modele = new QStringListModel(this);
modele->setStringList(donnees);
listeViewDonnees->setModel(modele);

L’affichage de données sous forme de tableau (table)

Comment utiliser un QTableView ?

On peut utiliser le pattern Model-View de Qt pour associer des données (le modèle) à un affichage (une vue) sous forme de table.

Lire qt-modelview.pdf et tp-qt-5-calepin.pdf.

La classe QTableView fournit une implémentation par défaut d’un modèle/vue sous la forme d’une vue en table qui affiche les éléments contenus dans un modèle.

La personnalisation d’un QTableView se fait en utilisant les nombreuses méthodes offertes par cette classe : setColumnWidth(), setMinimumWidth(), setMaximumWidth(), setSelectionMode(), setSelectionBehavior(), setSortingEnabled(), sortByColumn(), setHorizontalScrollMode(), setVerticalScrollMode(), …

La classe QTableView possède un entête vertical et un entête horizontal qui sont accessibles par les méthodes : verticalHeader() et horizontalHeader(). Elles retournent un QHeaderView qui possède de nombreuses méthodes de personnalisation : hide(), setFixedWidth(), setFixedHeight(), setDefaultSectionSize(), setResizeMode(), setStretchLastSection(), …

Il existe d’autres types de vue : sous forme de liste (QListView) et sous forme d’arbre (QTreeView).

Si vous ne voulez pas utiliser votre propre modèle, vous pouvez utiliser la classe QTableWidget qui fournit une vue de table basée avec un modèle par défaut. Les éléments des cellules d’un QTableWidget sont des objets QTableWidgetItem.

// Des données (simplement pour initialiser le modèle)
QStringList listePays;
listePays << "France" << "Angleterre" << "Espagne" << "Italie" << "Allemagne";

// Un modèle initialisé avec des données
QStringListModel *modele = new QStringListModel(listePays);

// Une vue Table (un widget)
vueTable = new QTableView(this);

// Association de la vue Table et du modèle
vueTable->setModel(modele);

On va maintenant utiliser un autre modèle pour la vue Table :

// Des données
QStringList listePays;
listePays << "France" << "Angleterre" << "Espagne" << "Italie" << "Allemagne";
QStringList capitalesPays;
capitalesPays << "Paris" << "Londres" << "Madrid" << "Rome" << "Berlin";

// Un autre modèle initialisé avec des données
autreModele = new QStandardItemModel(listePays.size(), 2);
for (int ligne = 0; ligne < listePays.size(); ligne++)
{
     QStandardItem *item = new QStandardItem(listePays.at(ligne));
     autreModele->setItem(ligne, 0, item);
}
for (int ligne = 0; ligne < capitalesPays.size(); ligne++)
{
     QStandardItem *item = new QStandardItem(capitalesPays.at(ligne));
     autreModele->setItem(ligne, 1, item);
}

// Une vue Table (un widget)
vueTable = new QTableView(this);

// Association de la vue Table et du modèle
vueTable->setModel(autreModele);

// Les éléments de la vue Table ne seront pas "éditables"
vueTable->setEditTriggers(QAbstractItemView::NoEditTriggers);

Comment récupérer des éléments dans un QTableView ?

Si on veut récupérer une donnée par un simple clic sur la vue de la liste :

connect(vueListe, SIGNAL(clicked(QModelIndex)), this, SLOT(selectionner(QModelIndex)));

Et dans le slot void selectionner(QModelIndex index) :

qDebug() << index.data().toString();

Comment insérer des éléments dans un QTableView ?

// Ajout d'une nouvelle ligne
int numeroLigne = modele->rowCount();
modele->insertRow(numeroLigne);

// Ajout de la donnée pour cette nouvelle ligne
QModelIndex index = modele->index(numeroLigne); // l'index de cette ligne
modele->setData(index, QString("Belgique"));

Ou :

// Ajout d'un élément
QStandardItem *item = new QStandardItem(QString("..."));
modele->setItem(numeroLigne, numeroColonne, item);

Comment supprimer des éléments dans un QTableView ?

// Récupération du nombre de lignes
int nbLignes = modele->rowCount();
if(nbLignes > 0)
{
    // Suppression d'une ligne : la dernière
    modele->removeRows(nbLignes-1, 1);
}

Comment personnaliser l’affichage d’un QTableView ?

On peut personnaliser l’affichage de la vue Table :

// Masquer les numéros de ligne
vueTable->verticalHeader()->setHidden(true);
// Redimensionner automatiquement la colonne pour occuper l'espace disponible
vueTable->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); // voir aussi QHeaderView::ResizeToContents

Et on peut ajouter un en-tête aux colonnes :

QStringList nomColonnes; // nom des colonnes
nomColonnes << "Pays" << "Capitale";
autreModele->setHorizontalHeaderLabels(nomColonnes);

Comment personnaliser l’affichage d’un élément dans un QTableView ?

On récupère un QStandardItem depuis le modèle :

QStandardItem *item = modele->item(numeroLigne, numeroColonne);

QFont texteLabel;
texteLabel.setPointSize(18);
item->setFont(texteLabel);
item->setBackground(QColor(Qt::green));
item->setForeground(QColor(Qt::red));
item->setTextAlignment(Qt::AlignHCenter);

Comment créer un QTableWidget ?

// un QTableWidget avec un nombre de lignes et de colonnes
tableWidget = new QTableWidget(4, 3, this);

// Ou un QTableWidget
tableWidget = new QTableWidget(this);
// on fixe le nombre de lignes
tableWidget->setRowCount(4);
// on fixe le nombre de colonnes
tableWidget->setColumnCount(3);

// Ou on peut insérer directement une ligne
tableWidget->insertRow(numeroLigne);

Comment insérer des éléments dans un QTableWidget ?

Un QString :

QTableWidgetItem *element = new QTableWidgetItem(QString("Une donnée"));
tableWidget->insertRow(l);
tableWidget->setItem(l, c, element); // l : ligne (row) et c : colonne

Un widget :

comboBox = new QComboBox(this);
comboBox->addItem("Item 1");
comboBox->addItem("Item 2");
// on peut ajouter des propriétés : l (ligne) et c (colonne)
comboBox->setProperty("l", l);
comboBox->setProperty("c", c);
// on peut connecter un slot
connect(comboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(slot(int)));
tableWidget->setCellWidget(l, c, cb);

QPushButton *bouton = new QPushButton("Valider", this);
bouton->setProperty("l", l);
bouton->setProperty("c", c);
connect(bouton, SIGNAL(clicked()), this, SLOT(slot2()));
tableWidget->setCellWidget(l, c, bouton);

Par exemple, un slot :

void MaClasse::slot2()
{
    // on récupère la ligne et la colonne du bouton
    int r = sender()->property("l").toInt();
    int c = sender()->property("c").toInt();
    qDebug() << Q_FUNC_INFO << l << c;
    // ...
}

Comment gérer le mécanisme signal/slot des objets avec QSignalMapper ?

On insère des QCheckBox dans un QTableWidget :

QCheckBox *checkBox = new QCheckBox(this);
twItineraires->setCellWidget(l, c, checkBox);
// etc ...

Une alternative à la méthode sender() (vu ci-dessous) est l’utilisation d’un QSignalMapper. Cette classe permet d’associer un signal sans paramètre d’un objet à un autre signal pourvu d’un paramètre défini.

// Pour la gestion des checkbox dans les tables
mapperSelectionne = new QSignalMapper(this);

On connecte le signal déclencheur souhaité de chaque objet (ici clicked()) avec le slot map() du QSignalMapper :

connect(checkBox, SIGNAL(clicked()), mapperSelectionne, SLOT(map()));

On indique ensuite au QSignalMapper avec la méthode setMapping() le signal qui sera émis :

// le deuxième paramètre est ici de type QWidget*, 
// mais on peut aussi le faire avec des int, QObject* ou QString
mapperSelectionne->setMapping(checkBox, checkBox);

À chaque appel au slot map(), le QSignalMapper identifie l’objet qui déclenche le slot (avec sender()) et émet le signal mapped() défini avec setMapping() :

connect(mapperSelectionne, SIGNAL(mapped(QWidget *)), this, SLOT(activer(QWidget *)));

Il ne reste plus qu’à définir le slot activer(QWidget *). À la place d’un slot, on peut indiquer un signal qui sera alors émis.

Comment paramétrer le comportement d’un élément d’un QTableWidget ?

Avec la méthode setFlags() :

QTableWidgetItem *element = new QTableWidgetItem(QString("Une donnée"));
tableWidget->insertRow(l);
tableWidget->setItem(l, c, element); // l : ligne (row) et c : colonne

// Aucune propriété
item->setFlags(Qt::NoItemFlags);

// Sélectionnable
item->setFlags(Qt::ItemIsSelectable);

// Éditable
item->setFlags(Qt::ItemIsEditable);

// Activé
item->setFlags(Qt::ItemIsEnabled);

// Ou plusieurs
item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsEditable);

Comment récupérer un élément d’un QTableWidget ?

On peut détecter le changement d’une cellule

connect(tableWidget, SIGNAL(cellChanged(int,int)), this, SLOT(slot(int,int)));

Puis dans le slot, on peut récupérer le contenu de la cellule :

void MaClasse::slot(int l, int c)
{
    QTableWidgetItem *item;

    item = tableWidget->item(l, c);
    qDebug() << item->data(0).toString();
}

Comment modifier un élément d’un QTableWidget ?

QTableWidgetItem *item;

item = tableWidget->item(l, c);
item->setData(0, QString("Coucou !");

Comment personnaliser l’affichage d’un élément dans un QTableWidget ?

Ses couleurs d’arrière et d’avant plan :

QTableWidgetItem *item;

item = tableWidget->item(l, c);

QColor couleur1(Qt::blue);
QColor couleur2(Qt::yellow);
item->setBackgroundColor(couleur1);
item->setForeground(couleur2);

Sa police de caractères :

QTableWidgetItem *item;

item = tableWidget->item(l, c);

QFont font("Liberation Sans", 20, QFont::Normal);
font.setPointSize(22);
font.setBold(true);
font.setItalic(true);
font.setUnderline(true);
item->setFont(font);

Son alignement dans la cellule :

tableWidget->item(l, c)->setTextAlignment(Qt::AlignHCenter|Qt::AlignVCenter);

Comment se déplacer sur un élément ?

// Sur un élément
QTableWidgetItem *item;
item = tableWidget->item(l, c);
tableWidget->scrollToItem(item);

// sur une cellule
tableWidget->setCurrentCell(l, c);

Comment effacer le contenu d’un QTableWidget ?

int nb = tableWidget->rowCount();
// on efface les lignes du tableau une par une
for(int i = 0; i < nb; i++)
{    
    tableWidget->removeRow(0);
}

Ou :

// on efface les QTableWidgetItem contenus dans le tableau
tableWidget->clear();
// on efface toutes les lignes du tableau
tableWidget->setRowCount(0);

Comment paramétrer l’affichage d’un QTableWidget ?

Il existe de nombreuses méthode de paramétrage d’un QTableWidget :

QTableWidget *tableWidget = new QTableWidget(this);

QStringList header; // nom des colonnes
header << "Nom" << "Prénom"; // ...

// On fixe le nombre de colonnes
tableWidget->setColumnCount(header.size());
// On applique les noms des colonnes
tableWidget->setHorizontalHeaderLabels(header);

// on cache les numéros de ligne
tableWidget->verticalHeader()->setHidden(true);

QHeaderView * headerView = tableWidget->horizontalHeader();
// on redimensionne automatiquement la colonne pour occuper l'espace disponible
// Qt 5 :
headerView->setSectionResizeMode(QHeaderView::Stretch);
// Qt 4 :
headerView->setResizeMode(QHeaderView::Stretch);

Comment redimensionner un QTableWidget ?

A adapter :

void IHMItineraire::resizeTW(QTableWidget *tw)
{
    int w = 0, h = 0;

    // redimensionner les colonnes en fonction du contenu
    tw->resizeColumnsToContents();
    // redimensionner les lignes en fonction du contenu
    //tw->resizeRowsToContents();

    // les barres de défilements
    tw->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    tw->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);

    // calcul largeur (w) et hauteur (h)
    w += tw->contentsMargins().left() + tw->contentsMargins().right();
    h += tw->contentsMargins().top() + tw->contentsMargins().bottom();
    w += tw->verticalHeader()->width();
    h += tw->horizontalHeader()->height();
    for (int i=0; i<tw->columnCount(); ++i)
        w += tw->columnWidth(i);
    for (int i=0; i<tw->rowCount(); ++i)
        h += tw->rowHeight(i);

    // fixe largeur (w) et hauteur (h)
    tw->setMinimumWidth(w);
    tw->setMaximumWidth(w);
    //tw->setMinimumHeight(h);
    //tw->setMaximumHeight(h);
    
    //qDebug() << "w =" << w << "h =" << h;
}

Application

Comment afficher la version de Qt ?

qDebug() << qVersion();

Remarque : lire Comment afficher des messages de débogage ? pour voir l’utilisation de qDebug().

Comment afficher mon application en plein écran ?

Il existe plusieurs méthodes :

  • en appelant la méthode showFullScreen()

  • en utilisant la méthode desktop() de la classe QApplication puis en appelant resize()

const int width = qApp->desktop()->availableGeometry(this).width(); // ou : qApp->desktop()->width()
const int height = qApp->desktop()->availableGeometry(this).height(); // ou : qApp->desktop()->height()
resize(width, height);

Comment afficher mon application au centre de l’écran ?

setGeometry(QStyle::alignedRect(Qt::LeftToRight, Qt::AlignCenter, size(), qApp->desktop()->availableGeometry()));

Sinon, il faudra utiliser la méthode move() et faire quelques calculs …

Comment afficher un écran de démarrage ?

On utilisera la classe QSplashScreen pour afficher un écran de démarrage.

#include <QApplication>
#include "mainwindow.h"

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;

    // Le splash
    QSplashScreen splash;
    // En avant-plan et sans décoration (titre ...)
    splash.setWindowFlags(Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint);
    // L'image de l'écran de démarrage
    splash.setPixmap(QPixmap("splash.png"));

    // On affiche
    splash.show();

    // On peut afficher un message sur l'image
    splash.showMessage(QString::fromUtf8("Lancement de l'application ..."), Qt::AlignHCenter | Qt::AlignTop, Qt::black);
    
    // On ferme le splash après un certain temps (ici 2 s)
    QTimer::singleShot(2000, &splash, SLOT(close()));
    // On affiche l'application
    QTimer::singleShot(2000, &w, SLOT(show()));
   
    return a.exec();
}

Il est aussi possible de synchroniser les 2 affichages :

#include <QApplication>
#include "mainwindow.h"

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;

    // Le splash
    QSplashScreen splash;
    
    QStringList chargement;
    chargement << "" << QString::fromUtf8("Lancement de l'application ...") << QString::fromUtf8("Chargement des paramètres ...") << QString::fromUtf8("Chargement des styles ...") << QString::fromUtf8("Chargement des widgets ...");

    // En avant-plan et sans décoration (titre ...)
    splash.setWindowFlags(Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint);
    splash.setPixmap(QPixmap("/home/tv/Documents/cours/BTS-Avignon/divers/logos/splash.png"));

    splash.show();

    for(int i = 0;i < chargement.size();i++)
    {
        // On peut afficher un message sur l'image
        splash.showMessage(chargement.at(i), Qt::AlignHCenter | Qt::AlignTop, Qt::black);
        
        // on fait quelque chose
        usleep(500000);
        
        a.processEvents();
    }

    // On affiche l'application
    w.show();

    // On ferme le splash lorsque l'e MainWindo'application sera affichée
    splash.finish(&w);
    
    return a.exec();
}

Comment mettre en français les boîtes fournies par Qt ?

Il faut charger le fichier de traduction adapté dans la fonction main() de votre application :

#include <QApplication>
#include <QTranslator>
#include <QLocale>
#include <QLibraryInfo>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QString locale = QLocale::system().name().section(’_’, 0, 0);
    QTranslator translator;
    translator.load(QString("qt_") + locale, QLibraryInfo::location(QLibraryInfo::TranslationsPath));
    a.installTranslator(&translator);
    
    // ...
    
    return a.exec();
}

Comment supprimer les widgets contenus dans un layout ?

QLayoutItem *itemL;
while ((itemL = mainLayout->takeAt(0)) != 0)
{
    QWidget * widget = widget = itemL->widget();
    mainLayout->removeItem(itemL);
    if(widget != 0)
    {
        delete widget;
    }
}

Comment appliquer une feuille de style Qt ?

Qt permet la personnalistion des widgets par l’utilisation de feuilles de style. Les feuilles de style Qt et les règles syntaxiques sont presque identiques à celles du CSS de l’HTML. Si vous connaissez déjà le CSS, vous pouvez rapidement les mettre en oeuvre pour Qt.

Lire la documentation Style Sheets Qt5 ou Qt4.

Modification d’un attribut de style pour un widget (ici un QLineEdit) :

// Pour l'application
qApp->setStyleSheet("QLineEdit { background-color: yellow }");

// Pour le widget
nameEdit->setStyleSheet("background-color: yellow");

On peut regrouper l’ensemble des paramètres dans un fichier externe (.qss par exemple) :

QLineEdit { 
    background-color: yellow;
}

QLineEdit:focus {
    border-width: 2px;
    padding: 0px;
}

Et l’appliquer au démarrage de l’application :

int main( int argc, char **argv )
{
    QApplication a( argc, argv );
    
    QFile file("default.qss");
    if(file.open(QFile::ReadOnly))
    {
        QString styleSheet = QLatin1String(file.readAll());
        a.setStyleSheet(styleSheet);
    }

    // ...
    
    return a.exec();
}

Ou dans un fichier ressource intégré à l’application :

int main( int argc, char **argv )
{
    QApplication a( argc, argv );
    
    QFile file(":/stylesheet/default.qss");
    if(file.open(QFile::ReadOnly))
    {
        QString styleSheet = QLatin1String(file.readAll());
        a.setStyleSheet(styleSheet);
    }

    // ...
    
    return a.exec();
}

Le fichier ressource .qrc :

<RCC>
    <qresource prefix="/stylesheet">
        <file>default.qss</file>
    </qresource>
</RCC>

Comment assurer l’internationalisation d’une application Qt ?

La procédure est décrite dans ce document : qt-internationalisation.pdf.

Comment afficher une page web ?

WebKit est un moteur web open source capable de charger le contenu d’une page web et d’en faire le rendu. Le module QtWebkit est l’intégration du moteur WebKit pour Qt. Il faut l’activer dans son fichier de projet .pro :

QT += webkit

Dans ce module, Qt fournit de nombreuses classes dont la classe QWebView :

QWebView *webView = new QWebView(this);

// sans les barres de défilement (au choix)
webView->page()->mainFrame()->setScrollBarPolicy(Qt::Horizontal, Qt::ScrollBarAlwaysOff);
webView->page()->mainFrame()->setScrollBarPolicy(Qt::Vertical, Qt::ScrollBarAlwaysOff);

// avec les barres de défilement (au choix)
webView->page()->mainFrame()->setScrollBarPolicy(Qt::Horizontal, Qt::ScrollBarAlwaysOn);
webView->page()->mainFrame()->setScrollBarPolicy(Qt::Vertical, Qt::ScrollBarAlwaysOn);

// Lire un paramètre
//qDebug() << webView->page()->settings()->testAttribute( QWebSettings::JavascriptEnabled );

// Modifier des paramètres
QWebSettings::globalSettings()->setAttribute(QWebSettings::PluginsEnabled,true );
QWebSettings::globalSettings()->setAttribute(QWebSettings::JavaEnabled,true );
QWebSettings::globalSettings()->setAttribute(QWebSettings::JavascriptEnabled,true );
QWebSettings::globalSettings()->setAttribute(QWebSettings::AutoLoadImages,true);

// un slot :
//connect(webView, SIGNAL(urlChanged(QUrl)), this, SLOT(webview_changed(const QUrl &)));

// L'url de la page
QUrl URL = QString("http://www.google.fr");

// une authentification
//URL.setUserName(username);
//URL.setPassword(password);

// modifier le zoom
//webView->setZoomFactor(1.0);

// on charge la page
webView->load(URL);

Divers

Comment afficher des messages de débogage ?

Qt comprend des macros globales pour écrire des messages d’avertissement et de débogage :

  • qDebug() pour écrire des sorties de débogage personnalisées.
  • qInfo() pour les messages d’information.
  • qWarning() pour signaler des avertissements et des erreurs récupérables dans votre application.
  • qCritical() pour écrire des messages d’erreur critiques et des erreurs de système de rapports.
  • qFatal() pour écrire des messages d’erreur fatale peu avant la sortie.

Si vous incluez le fichier d’en-tête <QtDebug>, la macro qDebug() peut également être utilisée comme un flux de sortie (comme cout par exemple).

Des espaces et un saut de ligne sont automatiquement ajoutés :

qDebug() << "QLabel" << pLabel << "at position" << pLabel->pos(); // QLabel QLabel(0x1a58dd0) at position QPoint(0,0) 
qDebug().nospace() << "QLabel" << pLabel << "at position" << pLabel->pos(); // QLabelQLabel(0x1631720) at position QPoint(0,0)

On peut l’utiliser aussi comme la fonction C printf() :

int a = 10;
double pi = 3.141592;
qDebug("a = %d et pi = %.3f", a, pi); // a = 10 et pi = 3.142

Remarque : Sous Windows, le message est envoyé à la console, s’il s’agit d’une application console sinon, il est envoyé au débogueur. Il faut donc ajouter console à la variable QT de votre fichier .pro si vous voulez l’affichage dans la console.

qDebug(), qInfo(), et qWarning() sont des outils de débogage. Ils ne font rien si QT_NO_DEBUG_OUTPUT, QT_NO_INFO_OUTPUT ou QT_NO_WARNING_OUTPUT ont été respectivement définis lors de la compilation, par exemple pour une version release.

Si vous ne voulez plus les affichage de débogage, mettre dans le fichier .pro :

DEFINES += QT_NO_DEBUG_OUTPUT

L’opérateur de flux de qDebug() gère la plupart des types standards et Qt. Par contre, il vous faudra le surcharger pour vos propres types :

Exemple pour type Coordonnees possédant deux membres x et y :

QDebug operator<<(QDebug dbg, const Coordonnees &c)
{
    QDebugStateSaver saver(dbg);
    dbg.nospace() << "(" << c.x() << ", " << c.y() << ")";

    return dbg;
}

Comment générer un nombre aléatoirement ?

Il faut tout d’abord initialiser le générateur de nombres pseudo-aléatoire :

qsrand(QTime::currentTime().msec());

Puis, on utilise qrand() : on peut générer un nombre entre 0 et 9 par exemple :

// un nombre entre 0 et RAND_MAX
int nombre1 = qrand();

// un nombre entre 0 et 9
int nombre2 = qrand() % 10;

Ou encore mieux utiliser une fonction qui retourne un nombre compris entre une valeur min et une valeur max :

int generateur(int min, int max)
{
    return static_cast<int>(min + (static_cast<float>(qrand()) / RAND_MAX * (max - min)));
}

float generateur(float min, float max)
{
    return static_cast<float>(min + static_cast<float>(qrand()) / (static_cast<float>(RAND_MAX/(max - min))));
}

Comment comparer correctement des réels ?

À cause de l’approximation des nombres réels, tester l’égalité de deux nombres réels avec == n’est pas possible.

Il faudra donc utiliser la fonction qFuzzyCompare() qui retourne vrai si deux éléments sont égaux. Il existe aussi qFuzzyIsNull() qui retourne vrai si le nombre réel est nul.

Voir d’autres fonctions intéressantes.

Comment effectuer des actions périodiques avec un minuteur ?

Qt fournit un minuteur ou temporisateur avec la classe QTimer. Cette classe est relativement simple à utiliser grâce aux méthodes suivantes :

  • int interval() const : permet de connaître l’intervalle de temps (en ms) entre chaque déclenchement du minuteur
  • void setInterval(int msec) : permet de fixer l’intervalle en millisecondes entre deux déclenchements successifs du minuteur
  • bool isActive() const : permet de déterminer si le minuteur est activé ou pas
  • void start() : démarre le minuteur
  • void stop() : arrête le minuteur
// on met à zéro un compteur représentant le nombre de secondes
int compteur = 0;

// on crée un minuteur
QTimer *timer = new QTimer(this);
        
// on connecte le signal d'expiration timeout() de la minuterie à un slot
connect(timer, SIGNAL(timeout()), this, SLOT(compter()));

// on choisit un intervalle en ms
timer->setInterval(1000);
        
// on démarre le minuteur
timer->start();  
// timer->start(1000);

L’action périodique (le slot) :

// Méthode appelée toutes les secondes par le QTimer
void MaClasse::compter()
{
    // on incrémente le compteur de secondes
    compteur++;
    qDebug() << compteur;
}

Comment effectuer une action temporisée avec un minuteur ?

Qt fournit un minuteur ou temporisateur avec la classe QTimer. Cette classe est relativement simple à utiliser grâce aux méthodes suivantes :

  • bool isSingleShot() const : permet de déterminer si le timer est en mode de déclenchement unique (temporisateur) ou pas (minuteur)
  • void setSingleShot(bool singleShot) : permet de mettre le minuteur en mode déclenchement unique (temporisateur)

On peut aussi utiliser la méthode statique QTimer::singleShot() :

// on ferme l'application après un certain temps (ici 1s)
QTimer::singleShot(1000, qApp, SLOT(quit()));

Compilation

Comment assurer une comptabilité Qt4 / Qt5 ?

Qt 5.0 est sorti le 19 décembre 2012. Bien que marquant des changements majeurs sur bien des points, le passage à Qt5 casse au minimum la compatibilité au niveau des sources. Lire : Transition_from_Qt_4.x_to_Qt5.

En Qt 5, il faut intégrer la module QtWidgets qui est un module séparé dans le fichier .pro :

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

Ensuite, il faut corriger les erreurs de compilation comme ‘error: QPushButton: No such file or directory’.

#include <QtWidgets/QPushButton> /* pour Qt5 */

Pour cela, on peut tester QT_VERSION et assurer une comptabilité Qt4 / Qt5 :

#include <QtGlobal> /* déclare QT_VERSION */
#if QT_VERSION >= 0x050000
    #include <QtWidgets> /* les widgets de Qt5 */
#else
    #include <QtGui> /* les widgets de Qt4 */
#endif

Est-il possible de fabriquer une application pour Android sous Qt ?

Oui ! Voir : Qt pour Android

Erreur d’édition des liens undefined reference to ‘vtable for xxx’ ?

Pour bénéficier des mécanismes de Qt (les signaux et les slots par exemple) dans vos propres classes, il faudra que celles-ci héritent de QObject (ou d’une classe fille de QObject) et intègrent la macro Q_OBJECT dans leur déclaration :

class MaClasse : public QObject
{
    Q_OBJECT
    
public:
    MaClasse( QObject *parent=0 ) : QObject(parent) {}
    
private:

signals:
    
public slots:
};

Cette classe ne pourra pas être compilée directement par un compilateur C++. Elle doit d’abord être traitée par l’outil moc de Qt. L’utilitaire qmake détectera si vos classes ont besoin du moc et génèrera le fichier Makefile qui contiendra les règles de fabrication.

Si vous obtenez l’erreur d’édition des liens undefined reference to 'vtable for xxx', vous devez :

  • Tout nettoyer (make clean)
  • Exécutez qmake
  • Tout recompiler

Comment récupérer et utiliser l’objet qui a déclenché un slot ?

Dans un slot, la méthode sender() retourne l’adresse de l’objet qui a déclenché ce slot (en émettant un signal).

Par exemple, deux clics de boutons connectés à un même slot :

QPushButton *bouton1 = new QPushButton("Bouton 1");
QObject::connect(btn, SIGNAL(clicked()), this, SLOT(monSlot()));
QPushButton *bouton2 = new QPushButton("Bouton 2");
QObject::connect(btn, SIGNAL(clicked()), this, SLOT(monSlot()));

Dans le slot, il est possible de récupérer l’adresse de l’objet bouton qui a déclenché le slot :

MaClasse::monSlot()
{
    QPushButton *bouton = qobject_cast<QPushButton*>(sender());
}

Gestion des données

Comment lire des paramètres dans un fichier .ini ?

Pour cela, on utilise la classe QSettings :

// Le nom du fichier INI : nom-executable.ini
QString fichierINI = qApp->applicationName() + ".ini";

QSettings parametres(fichierINI, QSettings::IniFormat);

// Lecture des paramètres de configuration
QString adresseIP = parametres.value("connexion/adresse","127.0.0.1").toString();
int port = parametres.value("connexion/port", "5000").toInt();

qDebug() << QString("adresse ip : %1").arg(adresseIP);
qDebug() << QString("port : %1").arg(port);

Comment écrire des paramètres dans un fichier .ini ?

Pour cela, on utilise la classe QSettings :

// Le nom du fichier INI : nom-executable.ini
QString fichierINI = qApp->applicationName() + ".ini";

QSettings parametres(fichierINI, QSettings::IniFormat);

// Les paramètres à enregistrer
QString adresseIP = "127.0.0.1";
int port = 5000;

// Écriture des paramètres de configuration (version 1)
//parametres.beginGroup("connexion"); // cf. endGroup()
//parametres.setValue("adresse", adresseIP);
//parametres.setValue("port", port);
//parametres.endGroup();

// Écriture des paramètres de configuration (version 1)
parametres.setValue("connexion/adresse", adresseIP);
parametres.setValue("connexion/port", port);

Comment créer un fichier XML ?

XML signifie eXtensible Markup Language (Langage de balisage extensible). Le langage est standardisé par la spécification W3C XML 1.0 du 10/02/98.

Un document XML bien formé signifie que le texte XML obéit aux règles syntaxiques de XML. Le document considéré comme valide (facultatif) signifie que le texte XML est bien formé et répond à une structure définie par une DTD (Definition Type Document).

Un document XML est composé d’éléments désignés par des balises et structuré sous la forme d’un arbre avec un et un seul élément racine (root).

Exemple de fichier XML :

<?xml version="1.0" encoding="UTF-8"?>
<interfaces>
  <interface id="100">
    <peripherique>/dev/ttyUSB0</peripherique>
  </interface>
</interfaces>

Qt fournit un module QtXml qu’il faut activer dans son fichier de projet .pro :

QT += xml

Dans ce module, Qt fournit de nombreuses classes qui permettent de gérer des documents XML :

On utilise la classe QFile pour la gestion de fichier. On utilise un objet DOM (Document Objet Model) de la classe QDomDocument pour agir sur le document XML :

// Le fichier XML
QFile fichierXML("test.xml");

if(!(fichierXML.open(QIODevice::ReadWrite | QIODevice::Truncate)))
{
    QMessageBox::critical(0,"Erreur","Erreur ouverture fichier XML !");
    return false;
}
else
{
    QDomDocument documentXML;

    documentXML.setContent(&fichierXML, false);
    QDomNode xmlNode = documentXML.createProcessingInstruction("xml","version=\"1.0\" encoding=\"UTF-8\"");
    documentXML.insertBefore(xmlNode, documentXML.firstChild());

    // crée l'élément racine
    QDomElement root = documentXML.createElement("interfaces");
    documentXML.appendChild(root);

    // crée et ajoute un élément
    QDomElement elementInterface = documentXML.createElement("interface");
    // ajoute un attribut à cete élément
    elementInterface.setAttribute("id", 100);
    root.appendChild(elementInterface);

    // crée et ajoute un sous-élément
    QDomElement elementPeripherique = documentXML.createElement("peripherique");
    elementInterface.appendChild(elementPeripherique);
    QDomText text = documentXML.createTextNode("/dev/ttyUSB0);
    elementPeripherique.appendChild(text);

    // écrit le fichier XML
    QTextStream out(&fichierXML);
    documentXML.save(out, 2);

    fichierXML.close();
}

Comment lire un fichier XML ?

XML signifie eXtensible Markup Language (Langage de balisage extensible). Le langage est standardisé par la spécification W3C XML 1.0 du 10/02/98.

Un document XML bien formé signifie que le texte XML obéit aux règles syntaxiques de XML. Le document considéré comme valide (facultatif) signifie que le texte XML est bien formé et répond à une structure définie par une DTD (Definition Type Document).

Un document XML est composé d’éléments désignés par des balises et structuré sous la forme d’un arbre avec un et un seul élément racine (root).

Exemple de fichier XML :

<?xml version="1.0" encoding="UTF-8"?>
<interfaces>
  <interface id="100">
    <peripherique>/dev/ttyUSB0</peripherique>
  </interface>
</interfaces>

Qt fournit un module QtXml qu’il faut activer dans son fichier de projet .pro :

QT += xml

Dans ce module, Qt fournit de nombreuses classes qui permettent de gérer des documents XML :

On utilise la classe QFile pour la gestion de fichier. On utilise un objet DOM (Document Objet Model) de la classe QDomDocument pour agir sur le document XML :

// Le fichier XML
QFile fichierXML("test.xml");

if(!(fichierXML.open(QIODevice::ReadOnly)))
{
    QMessageBox::critical(0,"Erreur","Le fichier XML n'a pas pu être ouvert !");
    return false;
}
else
{
    QDomDocument documentXML;

    documentXML.setContent(&fichierXML, false);
    
    // lit l'élément racine
    QDomElement racine = documentXML.documentElement(); // <interfaces>
    if(racine.isNull())
    {
        qDebug() << "Erreur racine !";
        return;
    }

    // récupère le premier élément
    QDomNode noeudInterface = racine.firstChild();
    if(noeudInterface.isNull())
    {
        qDebug() << "Erreur racine vide !";
        return;
    }

    while(!noeudInterface.isNull())
    {
        QDomElement elementInterface = noeudInterface.toElement(); // <interface>
        if(elementInterface.isNull())
        {
            qDebug() << "Erreur element !";
            break;
        }

        QDomNode noeudPeripherique = elementInterface.firstChild();
        if(!noeudPeripherique.isNull())
        {
            QDomElement elementPeripherique = noeudPeripherique.toElement(); // <peripherique>
            qDebug() << elementInterface.attribute("id").toInt(); // l'id
            qDebug() << interface.peripherique = elementPeripherique.text(); // le périphérique
        }

        // au suivant
        noeudInterface = noeudInterface.nextSibling();
    }

    fichierXML.close();
}

Comment gérer des données au format JSON ?

Lire : JSON [PDF]

Comment écrire des données dans un fichier CSV ?

Le format CSV (Comma-separated values) est un format informatique ouvert représentant des données tabulaires sous forme de valeurs séparées par un délimiteur (initialement des virgules). Les séparateurs ne sont pas standardisés (virgules, points-virgules, etc…) rend ce format peu pratique pour une utilisation autre que des échanges de données ponctuels. Ce format est toutefois très populaire parce qu’il est très facile à générer.

Les fichiers CSV sont des fichiers texte : ils peuvent donc être manipulés avec un éditeur de texte. Mais les fichiers CSV sont essentiellement utilisés par des logiciels de type tableur (Microsoft Excel, LibreOffice/OpenOffice calc, …).

Chaque ligne du texte correspond à une ligne du tableau et les virgules correspondent aux séparations entre les colonnes. Les portions de texte séparées par une virgule correspondent ainsi aux contenus des cellules du tableau.

Une ligne est une suite ordonnée de caractères terminée par un caractère de fin de ligne (CRLF), la dernière ligne pouvant en être exemptée.

Exemple de fichier CSV :

Sexe;Prénom;Année de naissance
M;Alphonse;1932

On utilise la classe QFile pour la gestion de fichier. Il n’existe pas de classe Qt native pour gérer le format CSV.

// Le fichier CSV
QFile *fichierCSV = new QFile("datas.csv");

// Ouverture du fichier en mode texte et en écriture seule
if (fichierCSV->open(QFile::WriteOnly | QIODevice::Text))
{
    // Ecriture de l'en-tête
    QTextStream entete(fichierCSV);
    
    entete << QString::fromUtf8("Sexe;Prénom;Année de naissance") << endl;
    
    // Ecriture des données
    QTextStream datas(fichierCSV);

    datas << "\"" << "M" << "\""; // on protège la data avec des "
    datas << ";";
    datas << "\"" << "Alphonse" << "\""; // on protège la data avec des "
    datas << ";";
    datas << "\"" << "1932" << "\""; // on protège la data avec des "
    datas << ";";
    datas << endl;

    // On ferme le fichier
    fichierCSV->close();

    delete fichierCSV;
}
else
{
    QMessageBox::critical(0,"Erreur !",("Impossible d'ouvrir le fichier datas.csv"));
    delete fichierCSV;
}

Remarque : voir aussi la classe qcsv

Comment lire des données dans un fichier CSV ?

Le format CSV (Comma-separated values) est un format informatique ouvert représentant des données tabulaires sous forme de valeurs séparées par un délimiteur (initialement des virgules). Les séparateurs ne sont pas standardisés (virgules, points-virgules, etc…) rend ce format peu pratique pour une utilisation autre que des échanges de données ponctuels. Ce format est toutefois très populaire parce qu’il est très facile à générer.

Les fichiers CSV sont des fichiers texte : ils peuvent donc être manipulés avec un éditeur de texte. Mais les fichiers CSV sont essentiellement utilisés par des logiciels de type tableur (Microsoft Excel, LibreOffice/OpenOffice calc, …).

Chaque ligne du texte correspond à une ligne du tableau et les virgules correspondent aux séparations entre les colonnes. Les portions de texte séparées par une virgule correspondent ainsi aux contenus des cellules du tableau.

Une ligne est une suite ordonnée de caractères terminée par un caractère de fin de ligne (CRLF), la dernière ligne pouvant en être exemptée.

Exemple de fichier CSV :

Sexe;Prénom;Année de naissance
M;Alphonse;1932

On utilise la classe QFile pour la gestion de fichier. Il n’existe pas de classe Qt native pour gérer le format CSV.

// Le fichier CSV
QFile *fichierCSV = new QFile("datas.csv");

// Ouverture du fichier en mode texte et en écriture seule
if (fichierCSV->open(QFile::ReadOnly | QIODevice::Text))
{
    // Lecture des données
    QTextStream datas(fichierCSV);
    QString ligne;

    while(!datas.atEnd())
    {
        ligne = datas.readLine();
        qDebug() << ligne;
    }
    
    // On ferme le fichier
    fichierCSV->close();
    delete fichierCSV;
}
else
{
    QMessageBox::critical(0, "Erreur !", ("Impossible d'ouvrir le fichier datas.csv"));
    delete fichierCSV;
}

Remarque : voir aussi la classe qcsv

Comment remplacer le point (‘.’) par la virgule (‘,’) dans un nombre réel ?

QString data = QString::number(2.5);
// A la française ?
data.replace(QString("."), QString(","));

Comment se connecter à une base de données ?

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

Remarque : Il faudra aussi disposer d’un pilote de base de données comme pour MySQL (QMYSQL) ou SQLite (QSQLITE).

On utilisera la classe QSqlDatabase qui permet la connexion à une base de données.

Et ensuite la classe QSqlQuery pour exécuter des requêtes SQL :

Exemple pour base de données MySQL :

QSqlDatabase db = QSqlDatabase::addDatabase("QMYSQL");

db.setHostName("localhost");
db.setUserName("root");
db.setPassword("password");
db.setDatabaseName("test"); // mettre le nom de la base de données

if(db.open())
{
   qDebug() << "Connexion réussie à " << db.hostName().toStdString();
}
else
{
   qDebug() << db.lastError().text();
}

Exemple pour base de données SQLite :

QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");

db.setDatabaseName("test.sqlite"); // mettre le nom du fichier sqlite

if(db.open())
{
   qDebug() << "Connexion réussie à " << db.hostName().toStdString();
}
else
{
   qDebug() << db.lastError().text();
}

Comment effectuer une requête SQL SELECT sur une base de données ?

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

Remarque : Il faudra aussi disposer d’un pilote de base de données comme pour MySQL (QMYSQL) ou SQLite (QSQLITE).

On utilisera la classe QSqlDatabase qui permet la connexion à une base de données.

Et ensuite la classe QSqlQuery pour exécuter des requêtes SQL :

QSqlQuery query;
QString requete = "SELECT * FROM mesures";
bool retour = query.exec(requete);
if(retour)
{
  while (query.next())
  {  
     qDebug() << "enregistrement -> ";
     for(int i=0;i<query.record().count();i++)
        qDebug() << query.value(i).toString();
  } 
}
else  qDebug() << query.lastError().text();

Comment effectuer une requête SQL UPDATE, INSERT ou DELETE sur une base de données ?

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

Remarque : Il faudra aussi disposer d’un pilote de base de données comme pour MySQL (QMYSQL) ou SQLite (QSQLITE).

On utilisera la classe QSqlDatabase qui permet la connexion à une base de données.

Et ensuite la classe QSqlQuery pour exécuter des requêtes SQL :

Il existe plusieurs façons :

QSqlQuery query;
QString requete;

requete = "UPDATE Seuils SET niveauTemperature='23'";
if (!query.exec())
{
   qDebug() << r.lastError().text();
}

requete = "DELETE FROM mesures";
if (!query.exec())
{
   qDebug() << r.lastError().text();
}

// idem pour INSERT
QSqlQuery query;

// Utilisation des marqueurs '?'
// INSERT INTO `mesures` (`id`, `date`, `heure`, `temperature`) VALUES (...)
query.prepare("INSERT INTO mesures (id, date, heure, temperature) VALUES ('', ?, ?, ?)");

// id en auto-incrément
query.addBindValue("2009-09-10");
query.addBindValue("09:01:00");
query.addBindValue(35.12);

if (query.exec())
{
   qDebug() << "Insertion réussie";
}
else
{
   qDebug() << r.lastError().text();
}

// Utilisation des marqueurs nominatifs
query.prepare("INSERT INTO mesures (id, date, heure, temperature) VALUES (:id, :date, :heure, :temperature)");

query.bindValue(":id", ""); // auto-incrément
query.bindValue(":date", "2009-09-10");
query.bindValue(":heure", "09:01:00");
query.bindValue(":temperature", 34.92);
if (query.exec())
{
   qDebug() << "Insertion réussie";
}
else
{
   qDebug() << r.lastError().text();
}

Comment corriger l’erreur “driver not loaded” ?

En utilisant le module sql de Qt de la manière suivante :

QSqlDatabase db = QSqlDatabase::addDatabase("QMYSQL");

on obtient à l’exécution ce message d’erreur :

QSqlDatabase: QMYSQL driver not loaded
QSqlDatabase: available drivers: QSQLITE QMYSQL QMYSQL3 QODBC QODBC3 QPSQL QPSQL7

Est-ce que l’erreur provient de Qt ?

Non pas vraiment ! Le module sql et certaines de ses classes ne sont que des wrappers (des adaptateurs d’interface) vers l’API (SQLite, MySQL, etc …) de chaque système.

Ici, le message d’erreur indique que la bibliothèque logicielle (le fichier .dll sous © Windows ou .so sous GNU/Linux) MySQL est introuvable par Qt. Elle n’est donc (dans l’ordre) :

  • pas accessible : il faut configurer l’accès au fichier
  • pas présente : il faut l’installer sur le système
  • éventuellement pas la bonne version : il faut la remplacer

Remarque : l’accès aux bibliothèques logicielles par les exécutables est différent sous © Windows et sous GNU/Linux.

Pour faire (très) simple, © Windows “aime” bien trouver les .dll spécifiques dans le même répertoire que l’exécutable.

GNU/Linux regroupe généralement l’ensemble des bibliothèques partagées dans les répertoires suivants : /usr/lib mais aussi dans /lib et /usr/local/lib. Voir aussi l’utilitaire ldconfig et la variable LD_LIBRARY_PATH.

Remarque : MySQL est un système de gestion de bases de données relationnelles (SGBDR) client/serveur. Ici, c’est le module client (celui qui permet d’effectuer des requêtes SQL vers un serveur) qui fait défaut. Depuis mai 2009, son créateur Michael Widenius a créé MariaDB pour continuer son développement en tant que projet Open Source.

Sous Ubuntu, il faut d’abord s’assurer de l’installation du paquet libqt5sql5-mysql :

$ sudo apt install libqt5sql5-mysql

Ce paquet installe le driver (ici libqsqlmysql.so) côté Qt pour être utilisé par le module sql :

$ dpkg -L libqt5sql5-mysql
/.
/usr
/usr/lib
/usr/lib/x86_64-linux-gnu
/usr/lib/x86_64-linux-gnu/qt5
/usr/lib/x86_64-linux-gnu/qt5/plugins
/usr/lib/x86_64-linux-gnu/qt5/plugins/sqldrivers
/usr/lib/x86_64-linux-gnu/qt5/plugins/sqldrivers/libqsqlmysql.so
/usr/share
/usr/share/doc
/usr/share/doc/libqt5sql5-mysql
/usr/share/doc/libqt5sql5-mysql/copyright
/usr/share/doc/libqt5sql5-mysql/changelog.Debian.gz

Maintenant, il faut identifier la bibliothèque du client MySQL (ici libmysqlclient.so) :

$ ldd /usr/lib/x86_64-linux-gnu/qt5/plugins/sqldrivers/libqsqlmysql.so
    linux-vdso.so.1 (0x00007fff13ac2000)
    libQt5Sql.so.5 => /usr/lib/x86_64-linux-gnu/libQt5Sql.so.5 (0x00007f60abffd000)
    libQt5Core.so.5 => /usr/lib/x86_64-linux-gnu/libQt5Core.so.5 (0x00007f60ab8b2000)
    libmysqlclient.so.20 => /usr/lib/x86_64-linux-gnu/libmysqlclient.so.20 (0x00007f60ab2fa000)
    ...

C’est le paquet libmysqlclientXX (ici libmysqlclient20) qui l’a fournie :

$ apt-cache search libmysqlclient
default-libmysqlclient-dev - MySQL database development files (metapackage)
libmysqlclient-dev - Fichiers de développement de la base de données MySQL
libmysqlclient20 - Bibliothèque cliente de la base de données MySQL
libglpk40 - linear programming kit with integer (MIP) support
libcrypt-mysql-perl - Perl module to emulate the MySQL PASSWORD() function
libmariadbclient-dev-compat - MariaDB database development files (libmysqlclient compatibility)

$ apt-file search libmysqlclient.so.20
libmysqlclient20: /usr/lib/x86_64-linux-gnu/libmysqlclient.so.20
libmysqlclient20: /usr/lib/x86_64-linux-gnu/libmysqlclient.so.20.3.21
libmysqlclient20: /usr/lib/x86_64-linux-gnu/libmysqlclient.so.20.3.8

$ sudo apt install libmysqlclient20

Comment générer un fichier PDF ?

Vous pouvez utiliser :

La classe QPrinter est utilisée normalement pour imprimer. Ici, elle va nous permettre de générer un fichier PDF en fixant le format QPrinter::PdfFormat avec la méthode setOutputFormat() :

QString nomFichier = QFileDialog::getSaveFileName(0, QString::fromUtf8("Générer PDF"), QCoreApplication::applicationDirPath(), "*.pdf");

if (!nomFichier.isEmpty())
{
    if (QFileInfo(nomFichier).suffix().isEmpty())
        nomFichier.append(".pdf");

    QPrinter printer(QPrinter::HighResolution);
    printer.setOutputFormat(QPrinter::PdfFormat);
    printer.setOutputFileName(nomFichier);
    printer.setOrientation(QPrinter::Portrait);
    printer.setPaperSize(QPrinter::A4);
    printer.setPageSize(QPrinter::A4);
    printer.setPageMargins(15,15,15,15,QPrinter::Millimeter);
    
    qDebug() << "Page px :" << printer.pageRect() << printer.paperRect();
    qDebug() << "Page mm :" << printer.pageRect(QPrinter::Millimeter) << printer.paperRect(QPrinter::Millimeter);
    qreal left, top, right, bottom;
    printer.getPageMargins(&left, &top, &right, &bottom, QPrinter::DevicePixel);
    qDebug() << "Marges px :" << left << top << right << bottom;
    printer.getPageMargins(&left, &top, &right, &bottom, QPrinter::Millimeter);
    qDebug() << "Marges mm :" << left << top << right << bottom;
    
    ...
}

Pour générer un contenu dans le fichier PDF, il suffit d’utiliser un objet QPainter, de l’associer à l’objet QPrinter et de dessiner à l’intérieur :

    ...
    
    QPainter painter(&printer);
    
    // Pour écrire du texte
    painter.drawText(0, printer.pageRect().y()*2, QString::fromUtf8("Ligne 1"));
    
    // Une nouvelle page
    printer.newPage();
    painter.drawText(0, printer.pageRect().y()*3, QString::fromUtf8("Ligne 2"));
}

Impression

Comment imprimer sous Qt ?

Dans Qt, les imprimantes sont représentées par QPrinter, un périphérique de dessin qui fournit les fonctionnalités spécifiques à l’impression, telles que le support de pages multiples et l’impression recto verso. Cela implique que l’impression passe par l’utilisation d’un QPainter pour dessiner sur une série de pages de la même façon que vous dessineriez sur un widget personnalisé ou une image.

Pour créer puis configurer un objet QPrinter, on peut utiliser la boîte de dialogue QPrintDialog :

QPrinter printer;
QPrintDialog *dialog = new QPrintDialog(&printer, this);

dialog->setWindowTitle(tr("Imprimer"));
if (dialog->exec() == QDialog::Accepted)
{
    // ...
}

La classe QPrinter possède de nombreuses méthodes pour configurer l’impression :

QPrinter printer(QPrinter::HighResolution);
printer.setOrientation(QPrinter::Portrait);
printer.setPaperSize(QPrinter::A4);
printer.setPageSize(QPrinter::A4);
printer.setPageMargins(15, 15, 15, 15, QPrinter::Millimeter);
...

Lire : Impression avec Qt

Comment imprimer un fichier ?

  • En utilisant un QTextEdit :
QTextEdit *editor = new QTextEdit(this);
...

QPrinter printer;
QPrintDialog *dialog = new QPrintDialog(&printer, this);

dialog->setWindowTitle(tr("Imprimer"));
if (dialog->exec() == QDialog::Accepted)
{
    QFile *file = new QFile("donnees.dat");
    if (file->open(QFile::ReadOnly | QFile::Text))
    {
       editor->setPlainText(file->readAll());
       editor->print(&printer);
       file->close();
    }
    delete file;
}
  • En utilisant un QPainter :
QPrinter printer;
QPrintDialog *dialog = new QPrintDialog(&printer, this);
dialog->setWindowTitle(tr("Imprimer"));
if (dialog->exec() == QDialog::Accepted)
{
    file = new QFile("donnees.dat");
    if (file->open(QFile::ReadOnly | QFile::Text))
    {
       QString ligne;
       QPainter painter;
       painter.begin(&printer);

       QFont f;
       f.setPointSize(10);
       f.setBold(true);
       painter.setFont(f);

       ligne = file->readLine();
       painter.drawText(40, 120, ligne);
       
       //printer.newPage();
       
       painter.end();
       file->close();
    }
    delete file;
}

Pour l’exemple, le fichier donnees.dat peut être généré simplement comme ceci :

QString uneDonnee = "Hello Wordl!";

QFile *file = new QFile("donnees.dat");
if (file->open(QFile::WriteOnly | QIODevice::Text))
{
    QTextStream out(file);
    out << uneDonnee;
    out << endl;
    file->close();
}
else
{
    QMessageBox::critical(this, "Erreur", QString::fromUtf8("Erreur enregistrement !"), QMessageBox::Ok, 0);
}
delete file;

Comment imprimer le contenu d’un QTextEdit ?

QTextEdit *editor = new QTextEdit(this);
...

// L'impression
QPrinter printer;

QPrintDialog *dialog = new QPrintDialog(&printer, this);
dialog->setWindowTitle(tr("Imprimer"));

if (editor->textCursor().hasSelection())
    dialog->addEnabledOption(QAbstractPrintDialog::PrintSelection);

if (dialog->exec() == QDialog::Accepted)
{
    // Solution simple et directe
    editor->print(&printer);        
}

Comment imprimer un QWidget ?

Pour imprimer un widget, il faut utiliser la méthode QWidget::render().

Centre le widget sur la page et le redimensionne pour remplir la page :

QPrinter printer;
QPainter painter;
painter.begin(&printer);
double xscale = printer.pageRect().width()/double(myWidget->width());
double yscale = printer.pageRect().height()/double(myWidget->height());
double scale = qMin(xscale, yscale);
painter.translate(printer.paperRect().x() + printer.pageRect().width()/2,
                printer.paperRect().y() + printer.pageRect().height()/2);
painter.scale(scale, scale);
painter.translate(-width()/2, -height()/2);

myWidget->render(&painter);

Remarque : la résolution des imprimantes est probablement différente (souvent plus grande) que la résolution de l’écran.

Comment gérer les conversions pixels / millimètres ?

qreal ratioPx = printer->paperRect().topRight().x() / printer->paperRect(QPrinter::Millimeter).topRight().x(); // mm -> px
qreal ratioMm = printer->paperRect(QPrinter::Millimeter).topRight().x() / printer->paperRect().topRight().x(); // px -> mm

qreal PixelToMillimeter(int pixel)
{
    return (qreal)(pixel*ratioMm);
}

int MillimeterToPixel(qreal mm)
{
    return (int)(mm*ratioPx);
}

Comment encadrer un texte ?

/* x, y et marge en mm */
void ecrireLigneTexteEncadre(QPainter *painter, qreal x, qreal y, qreal marge, QString ligne)
{
    QRect rLigne;
    QFontMetrics fmTF = painter->fontMetrics();

    rLigne = fmTF.boundingRect(ligne);
    rLigne.adjust(MillimeterToPixel(x-marge), MillimeterToPixel(y-marge), MillimeterToPixel(x+marge), MillimeterToPixel(y+marge));
    painter->drawRect(rLigne);
    painter->drawText(MillimeterToPixel(x), MillimeterToPixel(y), ligne);
}

Comment souligner un texte ?

/* x, y et marge en mm */
void ecrireLigneTexteSouligne(QPainter *painter, qreal x, qreal y, qreal marge, QString ligne)
{
    QRect rLigne;
    QFontMetrics fmTF = painter->fontMetrics();

    rLigne = fmTF.boundingRect(ligne);
    rLigne.adjust(MillimeterToPixel(x-marge), MillimeterToPixel(y-marge), MillimeterToPixel(x+marge), MillimeterToPixel(y+marge));
    painter->drawLine(rLigne.bottomRight(), rLigne.bottomLeft());
    painter->drawText(MillimeterToPixel(x), MillimeterToPixel(y), ligne);
}

Communication

Comment gérer un port série sous Qt ?

Suivant votre version de Qt :

  • Qt 4 : la version 4 de Qt ne fournit pas de classes pour gérer un port série nativement. On va donc devoir utiliser une bibliothèque logicielle externe à Qt : la classe QextSerialPort disponible ici.
#include "qextserialport.h"

#define PORT_LINUX "/dev/ttyUSB0"
#define PORT_WINDOWS "COM1"

// instanciation du port en mode synchrone -> QextSerialPort::EventDriven
QextSerialPort *port = new QextSerialPort(QLatin1String(PORT_LINUX), QextSerialPort::EventDriven);
// Ou :
// instanciation du port en mode asynchrone -> QextSerialPort::Polling
QextSerialPort *port = new QextSerialPort(QLatin1String(PORT_LINUX), QextSerialPort::Polling);
    
// configuration
port->setBaudRate(BAUD9600);
port->setDataBits(DATA_8);
port->setParity(PAR_NONE);
port->setStopBits(STOP_1);
port->setFlowControl(FLOW_OFF);

// ouverture du port en lecture/écriture
port->open(QIODevice::ReadWrite);

/** @todo : réceptionner et/ou envoyer des données sur le port */

// fermeture du port
port->close();    
  • Qt 5 : le problème n’existe plus en Qt5 car on dispose alors de la classe QSerialPort ! Il faudra ajouter le module serialport à la variable QT dans le fichier .pro (QT += serialport)
#include <QSerialPort>

#define PORT_LINUX "/dev/ttyUSB0"
#define PORT_WINDOWS "COM1"


// instanciation du port 
QSerialPort *port = new QSerialPort(QLatin1String(PORT_LINUX));

// configuration
port->setBaudRate(QSerialPort::Baud9600);
port->setDataBits(QSerialPort::Data8);
port->setParity(QSerialPort::NoParity);
port->setStopBits(QSerialPort::OneStop);
port->setFlowControl(QSerialPort::NoFlowControl);

// ouverture
port->open(QIODevice::ReadWrite);

/** @todo : réceptionner et/ou envoyer des données sur le port */

// fermeture du port
port->close();

Les fonctions d’écriture sont les suivantes :

  • qint64 write(const char * data, qint64 maxSize)
  • qint64 write(const char * data)
  • qint64 write(const QByteArray & byteArray)

Pour aller plus loin, on dispose :

  • du signal bytesWritten(qint64 bytes) : ce signal est émis chaque fois bytes octets de données ont été écrits sur le périphérique.
  • de la méthode waitForBytesWritten(int msecs) : elle attend jusqu’à ce que le signal bytesWritten() ait été émis ou que msecs millisecondes soient passées. Il est déconseillé de l’utiliser à partir du thread GUI.
  • de la méthode setTimeout(long millisec) : elle définit les délais d’attente d’écriture (et aussi de lecture) pour le port en millisec millisecondes. C’est un délai d’expiration par caractère individuel et non pour l’opération entière.

Les fonctions de lecture sont très nombreuses :

  • qint64 read(char * data, qint64 maxSize)
  • QByteArray read(qint64 maxSize)
  • QByteArray readAll()
  • qint64 readLine(char * data, qint64 maxSize)
  • QByteArray readLine(qint64 maxSize = 0)

La classe QextSerialPort (Qt4) ou QSerialPort (Qt5) fournit le signal readyRead(). Ce signal est émis une fois que de nouvelles données sont disponibles pour la lecture à partir du périphérique.

Comment créer une socket UDP ?

Qt fournit un module QtNetwork qu’il faut activer dans son fichier de projet .pro :

QT += network

Dans ce module, Qt fournit de nombreuses classes dont la classe QUdpSocket :

Création d’une socket et son attachement sur le port 5000 des interfaces locales de la machine :

QUdpSocket udpSocket = new QUdpSocket(this);
   
// Attachement locale de la socket UDP :
udpSocket->bind(QHostAddress((QString)"0.0.0.0"), 5000);
udpSocket->bind(QHostAddress::LocalHost, 5000);

// ...    

// on ferme la socket
udpSocket->close();

Comment recevoir des datagrammes sur une socket UDP ?

Qt fournit un module QtNetwork qu’il faut activer dans son fichier de projet .pro :

QT += network

Dans ce module, Qt fournit de nombreuses classes dont la classe QUdpSocket :

On réalise la connexion du signal readyRead() au slot ReceptionnerDatagrammes() :

connect(udpSocket, SIGNAL(readyRead()), this, SLOT(ReceptionnerDatagrammes()));

La méthode ReceptionnerDatagrammes() assure donc la réception des datagrammes UDP. Pour cela, elle réalise un boucle d’attente sur la présence de données dans la socket.

void MaClasse::ReceptionnerDatagrammes()
{
   int nbOctets = 0;   
   int etat;
 
   // datagramme en attente d'être lu ?
   while (udpSocket->hasPendingDatagrams())
   {
      QByteArray donneesDatagramme;
      QHostAddress emetteurAdresse;
      quint16 emetteurPort;      

      // Fixe la taille du tableau au nombre d'octets reçus en attente
      donneesDatagramme.resize(udpSocket->pendingDatagramSize());
      
      // Lit le datagramme en attente
      //nbOctets = udpSocket->readDatagram(datagram.data(), datagram.size());
      nbOctets = udpSocket->readDatagram(donneesDatagramme.data(), donneesDatagramme.size(), &emetteurAdresse, &emetteurPort);      
      
      QString qs_emetteurAdresse = emetteurAdresse.toString();
      qDebug() << "<" << qs_emetteurAdresse.toStdString() << ":" << emetteurPort 
               << "> datagramme de " << nbOctets << " octet(s) reçu(s)";      
   }
}

Comment envoyer des datagrammes sur une socket UDP ?

Qt fournit un module QtNetwork qu’il faut activer dans son fichier de projet .pro :

QT += network

Dans ce module, Qt fournit de nombreuses classes dont la classe QUdpSocket :

int MaClasse::EnvoyerDatagramme()
{
  int nbOctets;
  QByteArray datagramme = "WIDD";
  
  //nbOctets = udpSocket->writeDatagram(datagramme.data(), datagramme.size(), QHostAddress::Broadcast, 5000);
  nbOctets = udpSocket->writeDatagram(datagramme.data(), datagramme.size(), QHostAddress((QString)"10.7.89.95"), 5000);
  
  return nbOctets;
}

Comment créer une socket TCP côté client ?

Qt fournit un module QtNetwork qu’il faut activer dans son fichier de projet .pro :

QT += network

Dans ce module, Qt fournit de nombreuses classes dont la classe QTcpSocket :

Création d’une socket TCP et sa connexion vers un serveur TCP :

QTcpSocket *socket = new QTcpSocket(this);

QHostAddress adresseServeur("127.0.0.1");
int portServeur = 5000;

//socket->connectToHost("127.0.0.1", portServeur);
socket->connectToHost(adresseServeur, portServeur);

if(!socket->isOpen())
{
    qDebug() << socket->errorString());
}
else
{
    // quelques signaux
    connect(socket, SIGNAL(connected()), this, SLOT(estConnecte()));
    connect(socket, SIGNAL(disconnected()), this, SLOT(estDeconnecte()));
    connect(socket, SIGNAL(readyRead()), this, SLOT(recevoir()));

    if(!socket->waitForConnected(5000))
    {
        qDebug() << socket->errorString());
    }
}

Quelques slots :

void MaClasse::estConnecte()
{
   QTcpSocket *serveur = qobject_cast<QTcpSocket *>(sender());
   if (serveur == 0) // aucun ?
        return;

   qDebug() << QString::fromUtf8("Connexion réussie au serveur (") + serveur->peerAddress().toString() + QString::fromUtf8(":") + QString::number(serveur->peerPort()) + QString::fromUtf8(")");
}

void MaClasse::estDeconnecte()
{
   QTcpSocket *serveur = qobject_cast<QTcpSocket *>(sender());
   if (serveur == 0) // aucun ?
      return;

   qDebug() << QString::fromUtf8("Déconnexion du serveur (") + serveur->peerAddress().toString() + QString::fromUtf8(":") + QString::number(serveur->peerPort()) + QString::fromUtf8(")");
}

Comment envoyer des données avec une socket TCP ?

Qt fournit un module QtNetwork qu’il faut activer dans son fichier de projet .pro :

QT += network

Dans ce module, Qt fournit de nombreuses classes dont la classe QTcpSocket :

Avec la méthode write() par exemple :

QString message = "Hello world !\n";

socket->write(qPrintable(message));

Comment recevoir des données avec une socket TCP ?

Qt fournit un module QtNetwork qu’il faut activer dans son fichier de projet .pro :

QT += network

Dans ce module, Qt fournit de nombreuses classes dont la classe QTcpSocket :

On réalise la connexion du signal readyRead() au slot recevoir() :

connect(socket, SIGNAL(readyRead()), this, SLOT(recevoir()));

Puis avec la méthode readAll() par exemple :

void MaClasse::recevoir()
{
   QTcpSocket *serveur = qobject_cast<QTcpSocket *>(sender());
   if (serveur == 0) // aucun ?
      return;

    QByteArray donneesRecues;
    donneesRecues = serveur->readAll();
    QString reponse(donneesRecues.constData());

    qDebug() << donneesRecues;
}

Comment créer une socket TCP côté serveur ?

Qt fournit un module QtNetwork qu’il faut activer dans son fichier de projet .pro :

QT += network

Dans ce module, Qt fournit de nombreuses classes dont la classe QTcpServer :

Création d’une socket TCP et sa connexion vers un serveur TCP :

QTcpServer *serveur = new QTcpServer(this);

// Démarrage du serveur sur toutes les IP locales disponibles et sur le port 5000
if (!serveur->listen(QHostAddress::Any, 5000)) 
{
    qDebug() QString::fromUtf8("Erreur démarrage serveur : ") + serveur->errorString();
}
else
{
    // slot pour gérer la connxion des clients
    connect(serveur, SIGNAL(newConnection()), this, SLOT(connecterClient()));
}

Le slot pour gérer la connexion des clients :

void MaClasse::connecterClient()
{
    QTcpSocket *nouveauClient = serveur->nextPendingConnection();

    qDebug() << QString::fromUtf8("<Dialogue> Connexion client (") + nouveauClient->peerAddress().toString() + QString::fromUtf8(":") + QString::number(nouveauClient->peerPort()) + QString::fromUtf8(")");

    // envisager de stocker ce nouveau client

    // quelques slots
    connect(nouveauClient, SIGNAL(readyRead()), this, SLOT(recevoir()));
    connect(nouveauClient, SIGNAL(disconnected()), this, SLOT(deconnecterClient()));
}

void MaClasse::deconnecterClient()
{
    // On détermine quel client se déconnecte
    QTcpSocket *client = qobject_cast<QTcpSocket *>(sender());
    if (client == 0) // aucun client ?
        return;

    dQebug() << QString::fromUtf8("<Dialogue> Déconnexion client (") + client->peerAddress().toString() + QString::fromUtf8(":") + QString::number(client->peerPort()) + QString::fromUtf8(")");

    // supprimer le client stocké précedemment

    client->deleteLater();
}

Qt en ligne de commande (mode CLI)

Quels sont les outils fournis par Qt ?

Les outils de base de Qt sont :

  • qmake : un programme qui gère le processus de création des projets de développement sur différentes plates-formes. Il automatise la génération des Makefiles pour n’importe quel projet logiciel, qu’il soit écrit avec Qt ou non.
  • moc (Meta Object Compiler) : un programme qui gère les spécificités de Qt en générant des fichiers 100% C++.
  • uic (User Interface Compiler) : un programme qui génére des classes C++ à partir de fichiers .ui en XML décrivant une interface graphique.

Remarque : g++ et make sont des outils indépendants qe Qt.

Comment créer un projet Qt en ligne de commande ?

Étape n°1 : création du répertoire et du fichier main.cpp

Étape n°2 : création d’une classe Widget (avec un QLabel et un QPushButton)

Étape n°3 : création et fabrication du projet Qt

On obtient :

Étape n°4 (bonus) : voyage au coeur d’un exécutable Qt

Étape n°5 : nettoyage du projet

Remarque : une fois le projet Qt “nettoyé”, on obtient les seuls fichiers à conserver pour ce projet Qt (par exemple pour un projet collaboratif avec svn ou git). Les autres fichiers (dont l’exécutable) sont des fichiers générés (ils n’ont AUCUNE VALEUR pour un développeur).

Retour au sommaire