Dernière mise à jour le 20 juillet 2017

Qt : FAQ et erreurs courantes

Cette page se veut un complément de la FAQ Qt sur Developpez.com.

Je suis ouvert à toute critique, demande d'éclaircissement, ou suggestion visant à améliorer/compléter les sujets actuels ou à ajouter de nouveaux éléments.

Divers

qobject_cast : ou comment retrouver sa classe dérivée ?

Certaines fonctions membres des classes Qt telles que QTabWidget::currentWidget(), QTableView:::indexWidget(), QTableWidget::cellWidget(), QMdiArea::activeSubWindow() ou QObject::sender() dans un slot vous renvoient respectivement le type QWidget* pour les trois premières, QMdiSubWindow* et QObject* correspondant à la classe de base, là où vous attendriez votre propre classe qui en hérite (directement ou indirectement). Vous avez alors besoin de downcaster le pointeur retourné afin de récupérer votre instance de classe et utiliser les services qu'elle fournit.

Qt possède pour ce faire la fonction libre qobject_cast dont l'utilisation est la même que ses homologues :

ou auto C++11VotreClasse * pointeur = qobject_cast<VotreClasse *>(/* … */);

N'oubliez pas de vérifier si la conversion a réussi ou non :

if (pointeur) {
    // Vous pouvez utiliser le pointeur
} else {
    // Erreur dans la conversion, peut-être :
    //  - un mauvais type dérivé demandé ?
    //  - le pointeur retourné par la fonction est déjà nul ?
}

Ou selon le cas avec une assertion :

Q_ASSERT(pointeur);
// Utilisation

Exemples :

auto monWidget = qobject_cast<MaClasseWidget *>( tabWidget.currentWidget() );
auto monWidget = qobject_cast<MaClasseWidget *>( tableView.indexWidget( tableView.indexAt({i,j}) ) );
auto monWidget = qobject_cast<MaClasseWidget *>( tableWidget.cellWidget(i,j) );
auto maSubWindow = qobject_cast<MaSubWindow *>( mdiArea.activeSubWindow() );
auto monObjet = qobject_cast<MaClasse *>( sender() );

[Erreur] Membres de classe vs variables locales

Cette erreur est souvent commise par les débutants qui suivent le cours d'OpenClassrooms.

Au lieu d'utiliser les variables membres monBouton et monLayout dans le constructeur de votre classe, vous déclarez de nouvelles variables indépendantes des membres. Ces nouvelles variables locales prennent alors le pas sur les membres de la classe tout au long du constructeur. Ainsi, à la fin du constructeur :

  • les éléments graphiques gérés par ces variables locales sont correctement intégrés à votre interface (connexions, affichage…) ;
  • mais les membres de la classe n'ont pas été initialisés et pointent vers des adresses totalement aléatoires ;
  • les variables locales sont détruites, il ne reste que les variables membres.

Et dès que vous tenterez dans la fonction_membre d'accéder à vos objets graphiques à l'aide de ces mêmes membres non initialisés, ce sera le plantage quasi certain.

class Fenetre : public QWidget {
    QPushButton * monBouton;
    QVBoxLayout * monLayout;

public:
    Fenetre();
    void fonction_membre();
};
Fenetre::Fenetre() {
    Partie en trop qui sert à déclarer une nouvelle variable.
Supprimez-la, et vous utiliserez bien le membre de la classe.
QVBoxLayout *
monLayout = new QVBoxLayout{this}; // Nouvelle variable locale ! Idem.QPushButton * monBouton = new QPushButton{"Hello"}; // Nouvelle variable locale ! monLayout->addWidget(monBouton); } // Les pointeurs membres 'monBouton' et 'monLayout' eux, n'ont pas été initialisés !!! // ... mais on les utilise ici : void Fenetre::fonction_membre() { monBouton->setText("Hello World!"); // boom ! monLayout->addWidget( new QPushButton{"Bonjour le monde !", this} ); // boom ! }

Analogie toute simple : pour utiliser une variable int a; précédemment déclarée, vous ne rajoutez pas int devant mais utilisez a seul :

int a = 0;
int b = 10;
int a = 10 * b;
a = 10 * b;

C'est la même chose ici : QPushButton * monBouton et QVBoxLayout * monLayout ont déjà été déclarés dans le .h.

Comment parcourir les éléments d'un dossier ?

Pour ce faire, vous avez à votre disposition deux classes, que vous pouvez combiner ou non selon votre besoin :

Considérons l'arborescence suivante :

Image non disponible

avec « dossier » un répertoire quelconque situé sur votre ordinateur ayant pour cheminRappel : sous Windows n'oubliez pas de doubler le backslash \ qui sépare chacun des dossiers.
Ex. QString const path {"C:\\Users\\Jerome\\dossier"};
QString const path {"chemin/vers/dossier"};. Les différents codes ci-après vous donneront les sorties suivantesRappel : les dossiers « . » et « .. » représentent respectivement les dossiers courant et parent.
Ex. dossier/. et dossier/sous-dossier/.. représentent tous les deux dossier.
Note : pour alléger la lecture, le chemin menant à ce dossier principal est représenté ici par « … » dans la colonne de sortie.
 :

Description Code Sortie
Tous les éléments (chemin complet) directs du dossier.
QDirIterator it {path};
while (it.hasNext()) {
    qDebug() << it.next();
}
"…/dossier/."
"…/dossier/.."
"…/dossier/fichier.txt"
"…/dossier/image-1.png"
"…/dossier/image-2.png"
"…/dossier/image-3.png"
"…/dossier/sous-dossier"
"…/dossier/sous-dossier-1"
"…/dossier/sous-dossier-2"
"…/dossier/sous-dossier-3"
Tous les éléments (noms seuls) directs du dossier.
QDirIterator it {path};
while (it.hasNext()) {
    it.next();  // Voir avertissement de fin 
    qDebug() << it.fileName();
}

Ou

QDir dir {path};
for (QString const & item : dir.entryList()) {
    qDebug() << item;
}
"."
".."
"fichier.txt"
"image-1.png"
"image-2.png"
"image-3.png"
"sous-dossier"
"sous-dossier-1"
"sous-dossier-2"
"sous-dossier-3"
Tous les éléments directs du dossier, mais aussi ceux des sous-dossiers, sous-sous-dossiers, etc.
QDirIterator it {path, QDirIterator::Subdirectories};
while (it.hasNext()) {
    qDebug() << it.next();
}
"…/dossier/."
"…/dossier/.."
"…/dossier/fichier.txt"
"…/dossier/image-1.png"
"…/dossier/image-2.png"
"…/dossier/image-3.png"
"…/dossier/sous-dossier"
"…/dossier/sous-dossier/."
"…/dossier/sous-dossier/.."
"…/dossier/sous-dossier/sous-sous-dossier"
"…/dossier/sous-dossier/sous-sous-dossier/."
"…/dossier/sous-dossier/sous-sous-dossier/.."
"…/dossier/sous-dossier/sous-sous-dossier/fichier.txt"
"…/dossier/sous-dossier-1"
"…/dossier/sous-dossier-1/."
"…/dossier/sous-dossier-1/.."
"…/dossier/sous-dossier-1/fichier1.txt"
"…/dossier/sous-dossier-1/fichier2.txt"
"…/dossier/sous-dossier-2"
"…/dossier/sous-dossier-2/."
"…/dossier/sous-dossier-2/.."
"…/dossier/sous-dossier-2/fichier1.txt"
"…/dossier/sous-dossier-2/fichier2.txt"
"…/dossier/sous-dossier-3"
"…/dossier/sous-dossier-3/."
"…/dossier/sous-dossier-3/.."
"…/dossier/sous-dossier-3/fichier1.txt"
"…/dossier/sous-dossier-3/fichier2.txt"
Seuls les dossiers sont listés.
QDirIterator it {path, QDir::Dirs,
                       QDirIterator::Subdirectories};
while (it.hasNext()) {
    qDebug() << it.next();
}
"…/dossier/."
"…/dossier/.."
"…/dossier/sous-dossier"
"…/dossier/sous-dossier/."
"…/dossier/sous-dossier/.."
"…/dossier/sous-dossier/sous-sous-dossier"
"…/dossier/sous-dossier/sous-sous-dossier/."
"…/dossier/sous-dossier/sous-sous-dossier/.."
"…/dossier/sous-dossier-1"
"…/dossier/sous-dossier-1/."
"…/dossier/sous-dossier-1/.."
"…/dossier/sous-dossier-2"
"…/dossier/sous-dossier-2/."
"…/dossier/sous-dossier-2/.."
"…/dossier/sous-dossier-3"
"…/dossier/sous-dossier-3/."
"…/dossier/sous-dossier-3/.."
Les dossiers courants « . » et parents « .. » sont filtrés.
QDirIterator it {path, QDir::Dirs | QDir::NoDotAndDotDot,
                       QDirIterator::Subdirectories};
while (it.hasNext()) {
    qDebug() << it.next();
}
"…/dossier/sous-dossier"
"…/dossier/sous-dossier/sous-sous-dossier"
"…/dossier/sous-dossier-1"
"…/dossier/sous-dossier-2"
"…/dossier/sous-dossier-3"
Seuls les fichiers sont listés.
QDirIterator it {path, QDir::Files, 
                       QDirIterator::Subdirectories};
while (it.hasNext()) {
    qDebug() << it.next();
}
"…/dossier/fichier.txt"
"…/dossier/image-1.png"
"…/dossier/image-2.png"
"…/dossier/image-3.png"
"…/dossier/sous-dossier/sous-sous-dossier/fichier.txt"
"…/dossier/sous-dossier-1/fichier1.txt"
"…/dossier/sous-dossier-1/fichier2.txt"
"…/dossier/sous-dossier-2/fichier1.txt"
"…/dossier/sous-dossier-2/fichier2.txt"
"…/dossier/sous-dossier-3/fichier1.txt"
"…/dossier/sous-dossier-3/fichier2.txt"
Les fichiers sont filtrés par leur nom.
QDirIterator it {path, {"fichier*.txt"}, 
                       QDir::Files,
                       QDirIterator::Subdirectories};
while (it.hasNext()) {
    qDebug() << it.next();
}
"…/dossier/fichier.txt"
"…/dossier/sous-dossier/sous-sous-dossier/fichier.txt"
"…/dossier/sous-dossier-1/fichier1.txt"
"…/dossier/sous-dossier-1/fichier2.txt"
"…/dossier/sous-dossier-2/fichier1.txt"
"…/dossier/sous-dossier-2/fichier2.txt"
"…/dossier/sous-dossier-3/fichier1.txt"
"…/dossier/sous-dossier-3/fichier2.txt"
Les fichiers sont filtrés par leur nom et triés selon un leur taille.
auto imagesList = QDir{path}.entryInfoList(
                               {"image*.png"},
                               QDir::Files,
                               QDir::Size | QDir::Reversed);
for (QFileInfo const & item : imagesList) {
    qDebug() << item.absoluteFilePath() << item.size();
}
"…/dossier/image-2.png" 6241
"…/dossier/image-1.png" 6249
"…/dossier/image-3.png" 6257

Vous pourrez trouver dans la documentation QDir::SortFlag, QDir::Filter et QDirIterator::IteratorFlag d'autres filtres disponibles venant compléter QDir::Size/Reversed, QDir::Files/Dirs et QDirIterator::Subdirectories utilisés ici.

Lorque vous utilisez QDirIterator, prenez soin d'exécuter en premier lieu la fonction membre QDirIterator::next() afin de vous placer sur le bon élément, avant d'éventuellement utiliser une autre fonction de la classe QDirIterator permettant de récupérer des informations supplémentaires sur cet élément.

Un QDirIterator venant d'être construit ne désigne aucun élément ; il faut d'abord exécuter it.next() pour qu'il se positionne sur le tout premier élément (sous réserve que it.hasNext() soit true et que cet élément existe bien, ou cela n'aura aucun effet).

Notez qu'une ligne telle que qDebug() << it.next() << " - " << it.filePath(); ne garantit pas l'ordre d'exécution des deux instructions.

Comment ouvrir une nouvelle fenêtre ? / Pourquoi rien ne s'affiche ?

Rappel

Lorsque vous déclarez une variable dite « locale » sous la forme QWidget widget; au sein d'une fonction ou d'un quelconque bloc { … }, celle-ci est détruite dès que l'on en sort. Ainsi le widget suivant :

void fonction() {
    QWidget widget;
    // ...
    widget.show();
} // widget détruit ici

sera détruit avant même qu'il ait le temps d'être affiché. Et il en va de même pour tout objet déclaré de la sorte qui aura éventuellement été inséré dedans, le widget détruisant ses enfants avec lui.

Vous vous posez peut-être la question de la fonction main() où vous avez sûrement utilisé de telles variables locales sans aucun problème…

Ceci est dû au fait que la fonction QApplication::exec() (le return app.exec(); situé à la fin) est bloquante : elle retourne sa valeur seulement lorsque vous fermez votre (dernier) widget. Vous restez donc dans la fonction main() tout le long de l'exécution.

Ouverture d'une boîte de dialogue modale

L'affichage d'une boîte de dialogue modale s'effectuant à l'aide de sa fonction membre exec() (tout aussi bloquante), ceci entre parfaitement dans le cas d'utilisation d'une variable locale. Vous avez juste à la définir en lui passant d'éventuels paramètres par le biais de son constructeur, appeler sa fonction membre exec(), et récupérer les données entrées par l'utilisateur avec une ou plusieurs fonction(s) membre(s) que vous aurez ajouté dans votre classe héritant de QDialog :

void fonction() {
    MyDialog dialog{/* éventuels paramètres */};
    if (dialog.exec() == QDialog::Accepted) {
        // Récupération des données rentrées…
        // Par exemple avec une fonction membre getData() que vous auriez créé :
        auto data = dialog.getData();
    }
}

Je vois beaucoup de gens qui utilisent à la place de MyDialog dialog{/* ... */}; un pointeur alloué comme ceci :

MyDialog * dialog = new MyDialog{this};

et habitués avec Qt à ne pas avoir à faire de delete, négligent de faire un delete dialog; à la fin.

C'est incorrect, et ceci malgré la présence du this ! (Et je ne parle même pas du cas où il n'y est pas…)

Le parent (dans la majorité des cas votre fenêtre principale) ne détruit ses enfants que lorsqu'il est lui-même détruit. Il faudra alors attendre que l'utilisateur ferme l'application pour effectivement détruire les instances. Je dis bien « les » car autant de fois ce code sera exécuté, autant de nouvelles instances de MyDialog seront créées, utiliseront inutilement des ressources, et attendront patiemment d'être détruites.

L'utilisation de pointeur n'est ici pas justifiée. L'existence des instances des MyDialog n'a de sens qu'au sein de la fonction. Limitez donc naturellement leur durée de vie à l'intérieur de cette dernière en utilisant une variable « locale » qui est automatiquement détruite au }.

Autre ouverture d'une fenêtre non modale

Une fenêtre non modale devra au contraire rester ouverte une fois l'exécution de la fonction qui l'a créée terminée. Pour ce faire, vous devez utiliser un pointeur et l'opérateur new qui permettra d'étendre sa durée de vie jusqu'au delete correspondant :

void fonction() {
    QWidget * widget = new QWidget{/*...*/};
    // ...
    widget->show();
}

Se pose alors la question de l'emplacement de ce delete… qui va être plus ou moins lié au comportement que vous souhaitez.

  • La nouvelle fenêtre est-elle complètement indépendante de la première ou se ferme-t-elle en même temps que celle-ci ?
  • Si cette fonction est exécutée plusieurs fois, est-ce qu'une nouvelle fenêtre est ouverte à chaque fois ou est-ce que je garde la même ?
  • Que se passe-t-il si la seconde fenêtre est fermée ? Si la première est fermée ? Comment gérer l'éventuel échange de données entre les deux en conséquence ?

Heureusement, Qt vous offre des outils permettant de gérer ces cas :

  • le mécanisme parent/enfant : les enfants sont automatiquement libérés par le parent lorsque ce dernier est détruit ;
  • l'attribut Qt::WA_DeleteOnClose qui permet au widget de s'autodelete lorsqu'il est fermé par l'utilisateur ;
  • les pointeurs intelligents QScopedPointer/std::unique_ptr qui, détruits, vont automatiquement delete le pointeur qu'ils gèrent ;
  • les pointeurs intelligents QSharedPointer/std::shared_ptr qui permettent de partager la responsabilité du pointeur entre plusieurs instances ; le dernier détruit étant chargé de delete le pointeur ;
  • QPointer qui permet de vérifer si un objet (dérivant exclusivement de QObject) a été détruit ou non.

Comment gérer les clics et mouvements de la souris ?

Pour ce faire, la classe QWidget définit les fonctions membres virtuelles protégées suivantes :

void enterEvent(QEvent *event) exécutée lorsque la souris entre dans la zone du widget.
void leaveEvent(QEvent *event) exécutée lorsque la souris sort de la zone du widget.
void mouseMoveEvent(QMouseEvent *event) exécutée lorsque vous déplacez la souris au sein de la zone du widget (seulement lorsqu'un bouton de la souris est enfoncé, à moins d'appeler setMouseTracking(true)).
void mousePressEvent(QMouseEvent *event) exécutée lorsque vous pressez un bouton de la souris lorsque vous êtes dans la zone du widget ou lorsque le widget a appelé grabMouse().
void mouseReleaseEvent(QMouseEvent *event) exécutée lorsque vous relâchez un bouton précédemment pressé de la souris, même si elle ne survole plus le widget depuis l'évènement mousePress (sauf si un popup surgit : il dérobe alors immédiatement les événements de la souris).
void mouseDoubleClickEvent(QMouseEvent *event) exécutée lors d'un double clic dans la zone du widget (notez que les événements mousePress et mouseRelease du premier clic sont également exécutés avant cet évènement-ci).
void wheelEvent(QWheelEvent *event) exécutée lorsque l'utilisateur tourne la molette de la souris quand le widget détient le focus.
http://blog.qt.io/blog/2006/05/27/mouse-event-propagation/
QMouseEvent
QEvent
QWheelEvent
Exemple : Scribble Example
détails à ajouter : http://doc.qt.io/qt-5/qwidget.html#events ; https://qt.developpez.com/doc/4.6/qwidget/#%C3%89venements
idée de montrer des membres suivant le pressé/relâcher et la position + refresh() du paint

Signaux/slots

Comment passer des paramètres à mon slot lors du connect() ?

Lorsque vous connectez un signal à un slot, les paramètres d'entrée du slot sont obtenus à partir de ceux émis par le signal. Par exemple depuis le signal QLineEdit::textChanged(const QString &text) vers un slot MonWidget::handleChange(const QString &text)

connect(monLineEdit, &QLineEdit::textChanged,
        this,        &MonWidget::handleChange);
// ou ancienne forme :
connect(monLineEdit, SIGNAL(textChanged(const QString &)),
        this,        SLOT(handleChange(const QString &)));

Lorsque le signal est émis avec la ligne emit textChanged(new_text); au sein de la classe QLineEdit, notre slot handleChange() est exécuté avec en entrée la valeur de new_text.

Autant il est possible de connecter un signal avec un nombre de paramètres supérieur ou égal au slot : le slot recevant les paramètres correspondants, autant l'inverse n'est pas possible. Si par exemple vous voulez suite à l'appui sur un certain bouton (signal QPushButton::clicked() sans paramètre) exécuter ce même slot MonWidget::handleChange(const QString &text) possédant un paramètre, le signal sera incapable de fournir ce paramètre et vous obtiendrez une erreur (à la compilation pour la première forme « error: static assertion failed: Signal and slot arguments are not compatible. » ou durant l'exécution pour l'ancienne « QObject::connect: Incompatible sender/receiver arguments »).

Lorsque vous connaissez à l'avance le paramètre à passer au moment où vous effectuez la connexion, ceci ne se fait pas en remplaçant la partie const QString & par votre string :

connect(monLineEdit, SIGNAL(textChanged(const QString &)),
        this,        SLOT(handleChange("Texte")));

ou

connect(monLineEdit, SIGNAL(textChanged(const QString &)),
        label,       SLOT(setText("Texte")));

remplacer ainsi un type par une variable ou expression n'a pas grand sens.

Il vous faut :

  • soit utiliser la classe QSignalMapper (C++03) comme indiqué dans ce sujet ou ce cours, ou plus simple une fonction lambda (depuis Qt 5, C++11) :

    C++03 Qt 5, C++11
    QSignalMapper * signalMapper = new QSignalMapper(this);
    connect(button,       SIGNAL(clicked()), 
            signalMapper, SLOT(map()));
    
    connect(signalMapper, SIGNAL(mapped(QString const &)), 
            this,         SLOT(handleChange(QString const &)));
    connect(signalMapper, SIGNAL(mapped(QString const &)), 
            label,        SLOT(setText(QString const &)));
    
    signalMapper->setMapping(button, "Texte");
    connect(button,   &QPushButton::clicked,
            this,     [this]() { handleChange("Texte"); });
    connect(button,   &QPushButton::clicked,
            label,    [this]() { label->setText("Texte"); });

    (Nous devons capturer this, pour sa fonction membre (implicitement this->)handleChange et son membre (implicitement this->)label afin de pouvoir les utiliser au sein de la fonction lambda.)

    Notez qu'il est préférable, notamment si les objets this et label peuvent être détruits avant le bouton, de rajouter le 3e paramètre de contexte ou d'utiliser par dessus la classe QPointer et avant de les utiliser dans la fonction lambda, de vérifier s'ils n'ont pas été détruits entre la capture et l'exécution (cf. la note de fin).

  • soit utiliser un slot sans paramètre dans lequel on retrouvera la ligne handleChange("Texte").

Lorsque vous capturez un pointeur nu ou this dans une fonction lambda, prêtez une attention particulière à la durée de vie de celui-ci par rapport au sender. Si l'objet pointé est détruit et qu'un nouveau signal est émit, l'utilisation de ce pointeur (dès lors devenu un dangling pointer) au sein de la fonction lambda entraîne un comportement indéfini. En effet, une connexion normale est automatiquement détruite si le sender ou le receiver sont détruits, mais lors d'une connexion avec une fonction lambda, seule la destruction du sender supprime la connexion.

Pour palier à cela, vous pouvez comme montré ci-dessus rajouter un contexte grâce à cette surcharge de la fonction connect() qui entraînera la suppression de la connexion lors de la destruction l'objet. Je vous conseille aussi fortement d'utiliser la classe QPointer qui permet d'observer à la manière d'un pointeur intelligent la durée de vie d'un objet dont la classe hérite de QObject.

Vous pouvez consulter à ce sujet l'item 31 d'Effective Modern C++ de Scott Meyers ainsi que le billet Use carefully lambda function with Qt's connections de Guillaume Belz.

Nouvelle syntaxe Qt 5/C++11, comment connecter des fonctions surchargées ?

Il vous faut effectuer un cast à l'aide de static_cast qui explicite la signature de la fonction sous la forme :

static_cast(void (Classe::*)(types des paramètres)(&Classe::monSlot))

Voici un exemple pour la fonction membre currentIndexChanged() de la classe QComboBox qui possède les deux surcharges suivantes :

QComboBox * comboBox = new QComboBox;

// utilisation de : void currentIndexChanged(int index)
connect(comboBox, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
        /* ... */);

// utilisation de : void currentIndexChanged(const QString & text)
connect(comboBox, static_cast<void (QComboBox::*)(const QString &)>(&QComboBox::currentIndexChanged),
        /* ... */);

Pour alléger la syntaxe, il existe depuis la version Qt 5.7 la variable template qOverload() (ainsi que qConstOverload() et qNonConstOverload() pour des surcharges constantes et non constantes) :

class Classe {
    void fonctionSurchargee();        // (1)
    void fonctionSurchargee(QString); // (2)
    void fonctionSurchargee(int, QString);       // (3)
    void fonctionSurchargee(int, QString) const; // (4)
};

Ces variables template nécessitant C++14, vous avez en C++11 les classes équivalentes :

C++14 C++11
qOverload<>(&Classe:fonctionSurchargee)                     // (1)
qOverload<QString>(&Classe:fonctionSurchargee)              // (2)
qNonConstOverload<int, QString>(&Classe:fonctionSurchargee) // (3)
qConstOverload<int, QString>(&Classe:fonctionSurchargee)    // (4)
QOverload<>::of(&Classe:fonctionSurchargee)                     // (1)
QOverload<QString>::of(&Classe:fonctionSurchargee)              // (2)
QNonConstOverload<int, QString>::of(&Classe:fonctionSurchargee) // (3)
QConstOverload<int, QString>::of(&Classe:fonctionSurchargee)    // (4)

L'exemple précédent devient alors :

// utilisation de : void currentIndexChanged(int index)
connect(comboBox, qOverload<int>(&QComboBox::currentIndexChanged),
        /* ... */);

// utilisation de : void currentIndexChanged(const QString & text)
connect(comboBox, qOverload<const QString &>(&QComboBox::currentIndexChanged),
        /* ... */);

Si vous tentez d'effectuer la connexion sans ce cast, vous vous heurterez à l'erreur :

no matching function for call to 'QObject::connect(QComboBox*& <unresolved overloaded function type> […]

Qt Creator

Comment utiliser une bibliothèque dans Qt Creator ?

Quel que soit le dossier où vous avez (compilé et) installé la bibliothèque en question, vous aurez à rajouter les lignes suivantes dans votre .pro :

INCLUDEPATH += chemin/vers/dossier/include

Cette ligne permet de rajouter ce dossier « include » aux dossiers que parcours votre compilateur pour trouver les fichiers d'entête .h qui sont #include. Si vous obtenez une erreur « No such file or directory », c'est cette ligne qu'il faut reconsidérer/ajouter.

LIBS += -Lchemin/vers/dossier/lib

(-L suivi sans espace du chemin du dossier.)

Cette ligne permet de rajouter ce dossier « lib » aux dossiers que parcours votre éditeur de liens (linker) pour trouver les fichiers de code compilé (.a/.so/.lib) de la bibliothèque. Si vous obtenez une erreur « cannot find -l<fichier> », c'est cette ligne qu'il faut reconsidérer/ajouter.

LIBS += -lfichier1 -lfichier2 … -lfichierN

(-l L minuscule, suivi sans espace du nom du fichier, sans extension, ni « lib » devant.)

Cette ligne permet de spécifier les fichiers de code compilé (.a/.so/.lib) de la bibliothèque à utiliser. Si vous obtenez des erreurs « undefined reference to » mentionnant des fonctions de la bibliothèque, c'est que vous avez oublié d'ajouter certains fichiers de la bibliothèque.

Pour cette dernière ligne, on peut citer quelques exemples de bibliothèques courantes :

# SDL2
LIBS += -lmingw32 -lSDL2main -lSDL2
# SFML
LIBS += -lsfml-graphics -lsfml-window -lsfml-system
# OpenCV
LIBS += -lopencv_core2410 -lopencv_highgui2410 -lopencv_imgproc2410

Remarque : on pourra retrouver plusieurs versions des fichiers de code compilé avec entre autres :

  • « debug » avec un « d » à la fin de leur nom ;
  • « release » sans ce « d » à la fin de leur nom ;
  • « static » avec un « s » à la fin de leur nom.

Vous pourrez alors adapter votre .pro en utilisant les sélecteurs suivants :

static {
    # Ajout des fichiers avec un « s »
} else {
    # Ajout des fichiers sans « s »
}

et

CONFIG(debug, debug|release) {
    # Ajout des fichiers avec un « d »
} else {
    # Ajout des fichiers sans « d »
}

Il vous suffira alors de sélectionner au début du .pro la configuration à utiliser avec :

CONFIG += static
# Ou
CONFIG += debug
# Ou
CONFIG += release

Enfin, sous Windows, n'oubliez pas de copiez les fichiers .dll dans le dossier d'exécution (à la racine du projet par défaut dans la plupart des EDI).

Comment compiler en C++11 (-std=c++11) ou C++14 (-std=c++14) dans Qt Creator ?

Il vous suffit de rajouter cette ligne dans votre .pro :

CONFIG += C++11
# ou
CONFIG += C++14

ou encore

QMAKE_CXXFLAGS += -std=c++11
# ou
QMAKE_CXXFLAGS += -std=c++14

puis d'exécuter qmake.

Comment rajouter les options de compilation -Wall, -Wextra… dans Qt Creator ?

Il vous suffit de rajouter cette ligne dans votre .pro :

QMAKE_CXXFLAGS += -Wall -Wextra

puis d'exécuter qmake.

Messages d'erreur à la compilation

'qApp' was not declared in this scope

qApp est une instance statique de la classe QApplication définie dans le fichier d'entête de cette même classe. Pour pouvoir utiliser cette variable, vous devez donc inclure ce fichier avec :

#include <QApplication>

Si malgré la présence de cette ligne, vous avez toujours une erreur, veuillez consulter le sujet Qxxx : No such file or directory.

QObject::connect: No such slot Classe::monSlot(…)

Ce message intervient lors de l'exécution de la ligne connect(…);. Il peut être dû à plusieurs raisons :

  • Une classe qui utilise les signaux et slots doit :

    • hériter de QObject, directement ou par le biais de l'une de ses classes dérivées (par ex. QWidget) ;
    • utiliser la macro Q_OBJECT ;
  • Un slot doit être déclaré dans la classe comme ceci à l'aide du mot-clé slots :
    (Non obligatoire si vous utilisez la nouvelle syntaxe apparue avec Qt 5 présentée en fin de sujet.)

    public slots: // public, protected ou private
        void monSlot(/* éventuels paramètres */);
  • Suite à une faute de frappe, il se peut que vous ayez mal orthographié le nom du slot. Vérifiez donc la bonne concordance entre sa déclaration dans le .h et son utilisation dans le connect(…); ;
  • Vous avez tout simplement oublié de le déclarer dans votre classe.

Commencez donc par vérifier ces éléments, et corrigez-les si besoin. (Notamment pour le rajout de la macro Q_OBJECT, prenez soin d'exécuter qmake juste après. Il se peut que vous ayez à nettoyer (clean) et recompiler votre projet [menu « Compiler » dans Qt Creator] pour que la modification prenne effet.)

Si l'erreur persiste, deux autres cas peuvent se présenter :

1) Mauvaise concordance receiver/slot

Pour rappel la fonction connect(), dans son utilisation la plus classique, va connecter le signal d'une instance sender au slot d'une autre instance receiver :

connect(sender,   SIGNAL(monSignal(/* éventuels types */)),
        receiver, SLOT(monSlot(/* éventuels types */)) );
à chaque émission du signal : emit sender->monSignal(/* valeurs transmises */),
sera exécuté : receiver->monSlot(/* valeurs reçues */)

il faut donc que la classe de sender possède le signal monSignal(), et que la classe de receiver possède le slot monSlot().

Une erreur classique de débutant consiste à faire :

connect(button,   SIGNAL(clicked()),
        lineEdit, SLOT(monSlot()) );

monSlot() étant un slot de votre classe, alors que lineEdit est une instance de la classe QLineEdit : la classe QLineEdit ne possédant évidemment pas votre slot, vous obtenez cette erreur No such slot QLineEdit::monSlot(), car lineEdit->monSlot() n'existe pas, nous voulons this->monSlot().

C'est le pointeur vers l'instance de votre classe (à savoir this) qu'il vous faut fournir en 3e argument :

connect(button, SIGNAL(clicked()),
        this,   SLOT(monSlot()) );

Il vous est possible dans ce cas d'omettre ce 3e argument, this sera utilisé par défaut :

connect(button, SIGNAL(clicked()), SLOT(monSlot()) );

2) Tentative erronée de passer des arguments

Comme rappelé dans le 1) dans les commentaires du premier code, vous devez écrire entre parenthèses les types (et seulement les types) que prennent les signaux et slots. Vous ne pouvez pas rajouter vos propres arguments sur cette ligne, les seuls passés sont ceux transmis depuis le signal émit vers le slot. Le code suivant, qui a pour volonté de fixer le texte suite à l'appui sur le bouton, est par conséquent faux :

connect(button,   SIGNAL(clicked()),
        lineEdit, SLOT(setText("Texte")) );

et vous donne le message No such slot QLineEdit::setText("Texte").

Veuillez consulter le sujet Comment passer des paramètres à mon slot lors du connect() ? pour savoir comment procéder.

Depuis Qt 5, il est préférable d'utiliser la nouvelle syntaxe utilisant des pointeurs de fonction membre :

connect(button, &QPushButton::clicked, 
        this,   &MaClasse::monSlot);

Cette syntaxe présente les avantages de vérifier cette erreur à la compilation plutôt que durant l'exécution, et d'autoriser la conversion implicite des paramètres du signal vers ceux du slot.

Je vous conseille vivement la lecture de l'article suivant : Les signaux et slots dans Qt 5.4 écrit par Guillaume Belz.

Qxxx : No such file or directory

L'erreur est due au fait que vous avez oublié de rajouter le module auquel appartient cette classe dans votre .pro.

Commencez par aller voir (sur Internet ou F1 sur Qt Creator) la documentation de votre classe. (Vous pouvez retrouver la liste de toutes les classes Qt dans cette page.)

Prenons par exemple la classe QApplication : vous pouvez voir au début de la page la ligne suivante :

Image non disponible

signifiant que cette classe appartientLe passage à la version Qt 5 s'est effectué entre autres avec le déplacement de plusieurs classes (dont QApplication) depuis le module Qt Gui vers ce nouveau module Qt Widgets ; le portage sur les versions antérieures s'effectuera alors en rajoutant : greaterThan(QT_MAJOR_VERSION, 4): QT += widgets qui ne rajoutera le module Qt Widgets que pour les versions Qt 5 et plus.
Vous pouvez aussi avoir dans votre code des lignes telles que #include <QtGui/QWidget> qu'il faudra passer en #include <QtWidgets/QWidget>, ou mieux en #include <QWidget> sans le nom du module devant, qui fonctionnera quelle que soit la version.
au module Qt Widgets et que pour pouvoir l'utiliser, vous devez ajouter cette ligne QT += widgets dans votre fichier .pro.

Pensez à exécuter qmake suite à cette modification (dans Qt Creator : menu « Compiler » ➝ « Exécuter qmake »). Il se peut que vous ayez à nettoyer (clean) et recompiler votre projet (toujours dans le menu « Compiler » dans Qt Creator) pour que la modification prenne effet.