Partage
  • Partager sur Facebook
  • Partager sur Twitter

Changement d'image en Qt

Sujet résolu
23 juin 2018 à 23:19:51

Bonjour à tous,

J'aimerais créer une fenêtre avec une image prés chargée, puis, lorsque l'on appui sur un bouton, qu'une autre image s'affiche par dessus, avant de revenir à l'image initiale lorsque le bouton est relâché. J'ai plusieurs problèmes:
-je ne parviens pas à visualiser les images lorsque je travail depuis la fenêtre Qt. J'ai pourtant ajouté mes images en 'ressources' et les ai mise dans un dossier indiqué dans le programme.
-lorsque je lance l'exécutable en double cliquant dessus, j'ai bien mon image et mon bouton qui s'affiche, mais ce dernier ne génère aucune action.

S'il vous plait, avant de me rediriger vers le cours de Matéo ou un forum, avec une phrase lapidaire, comprenez que j'ai déjà pas mal farfouillé sur le web. Toute indication reste la bienvenue. :D

Et voici le code:

header de la classe:

#ifndef DEF_MAFENETRE
#define DEF_MAFENETRE

#include <QBoxLayout>
#include <QApplication>
#include <QWidget>
#include <QPushButton>
#include <QLabel>

class MaFenetre : public QWidget // On hérite de QWidget (IMPORTANT)
{
    Q_OBJECT
    public:
    MaFenetre();

    private slots:
    void action(QLabel*);

    private:
    QPushButton *m_bouton;

};



#endif

 corps de la classe:

#include "fenetre.h"

MaFenetre::MaFenetre() : QWidget()
{

    setFixedSize(300, 150);
    QVBoxLayout *layout = new QVBoxLayout;
    QLabel *image = new QLabel(this);
    image->setPixmap(QPixmap("./image/image1.png"));
    m_bouton = new QPushButton("Taper!", this);
    m_bouton->setFont(QFont("Comic Sans MS", 14));
    m_bouton->setCursor(Qt::PointingHandCursor);
    m_bouton->move(120, 50);
    layout->addWidget(image);
    layout->addWidget(m_bouton);

    // Connexion du clic du bouton à la fermeture de l'application
    QObject::connect(m_bouton, SIGNAL(clicked()), qApp,SLOT(action(*image)));
}

void MaFenetre::action(QLabel* image){
    image->setPixmap(QPixmap("./image/image2.png"));
}

 main:

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


int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    MaFenetre fenetre;
    fenetre.show();

    return app.exec();
}



Merci d'avance. :D



  • Partager sur Facebook
  • Partager sur Twitter
25 juin 2018 à 14:47:46

Salut,

Quelle version de QT utilises-tu ?

Le système signal/slot est un peu "dépassé". Le problème étant que la vérification s’effectue à l'exécution et  non pas à la compilation (erreurs silencieuses !!)..Ici tu as un problème sur la ligne suivante.

QObject::connect(m_bouton, SIGNAL(clicked()), qApp,SLOT(action(*image)));

En effet, ton signal et ton slot doivent avoir strictement les mêmes paramètres (or ici l'un n'a pas de paramètre et l'autre un Qlabel*).

EDIT: en réalité, ton signal peut avoir des paramètres en plus de ceux du SLOT à condition qu'ils soient à la fin. Le compilateur les ignoreras. 

-
Edité par Didy7 25 juin 2018 à 14:53:06

  • Partager sur Facebook
  • Partager sur Twitter
26 juin 2018 à 8:58:24

Bonjour,

Je pense être en 3.0.4.

Donc, si je veux faire fonctionner mon code, il suffit de rajouter un Qlabel * en paramètre du SIGNAL?

D'accord, et la méthode la plus moderne quel est elle? Je suis au milieu du cours de C++ de Matéo, mais il ne parle que du couple SIGNAL/SLOT pour le moment.

Merci pour tout.


Bonjour,

J'ai essayé de modifier la ligne fautive de plusieurs façon.

J'ai tout d'abord essayé de passer l'argument Qlabel* au signal, de la façon suivante:

QObject::connect(m_bouton,SIGNAL(*image,clicked()),qApp,SLOT(action(*image)));

 L'erreur renvoyé était que 'SIGNAL' n'attendait qu'un seul paramètre.

 J'ai donc mis une variable image à ma classe fenêtre, pour ne plus avoir à passer l'argument en paramètre. J'obtient alors:

QObject::connect(m_bouton,SIGNAL(clicked()),qApp,SLOT(action()));

 Là j'obtient le bug initial (i.e, ce slot n'existe pas, et lorsque j'appui sur mon bouton, il n'y aucun changements.

J'ai donc essayé cette dernière chose:

QObject::connect(m_bouton,SIGNAL(action()),qApp,SLOT(action()));

 avec une mise à jour de la fonction "action"

voidMaFenetre::action(){
m_bouton->clicked();
image->setPixmap(QPixmap("./image/image2.png"));
}

 Mais ceci ne fonctionne pas non plus.
Je poste mon code final ici.

header

#ifndef DEF_MAFENETRE
#define DEF_MAFENETRE

#include <QBoxLayout>
#include <QApplication>
#include <QWidget>
#include <QPushButton>
#include <QLabel>

class MaFenetre : public QWidget // On hérite de QWidget (IMPORTANT)
{
    Q_OBJECT
    public:
    MaFenetre();

    private slots:
    void action();

    private:
    QPushButton *m_bouton;
    QLabel *image;
};



#endif

fenêtre.cpp

#include "fenetre.h"

MaFenetre::MaFenetre() : QWidget()
{

    setFixedSize(300, 150);
    QVBoxLayout *layout = new QVBoxLayout;
    image = new QLabel(this);
    image->setPixmap(QPixmap("./image/image1.png"));
    m_bouton = new QPushButton("Taper!", this);
    m_bouton->setFont(QFont("Comic Sans MS", 14));
    m_bouton->setCursor(Qt::PointingHandCursor);
    m_bouton->move(120, 50);
    layout->addWidget(image);
    layout->addWidget(m_bouton);

    // Connexion du clic du bouton à la fermeture de l'application
    QObject::connect(m_bouton, SIGNAL(action()), qApp,SLOT(action()));
}

void MaFenetre::action(){
    m_bouton->clicked();
    image->setPixmap(QPixmap("./image/image2.png"));
}

main.cpp

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


int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    MaFenetre fenetre;
    fenetre.show();

    return app.exec();
}



-
Edité par MatthiasPac 26 juin 2018 à 9:21:37

  • Partager sur Facebook
  • Partager sur Twitter
26 juin 2018 à 16:51:21

Salut,

Tu as deux façons de résoudre ton problème.

1) Soit (ton premier message) en partant sur le passage en paramètre.
2) Soit (dernier code, plus simple) en ayant ton QLabel en membre, le passage en paramètre devient inutile.

2) Commençons par le plus facile. Le soucis qu'il te reste est au niveau du connect :

QObject::connect(m_bouton, SIGNAL(action()), qApp,SLOT(action()));

ou plutôt LES soucis qui sont au nombre de deux :

Premièrement SIGNAL(action()) : toi tu veux intercepter l'évènement du clic sur le bouton, tu dois donc utiliser le bon signal correspondant fourni par ton QPushButton *m_bouton;.

Quels signaux propose la classe QPushButton ?

Ceux de la classe QAbstractButton (dont elle hérite, + 5 de QWidget et QObject dont on se moque ici). Vois-tu un quelconque action dedans ? Non, tu n'as que clicked, pressed, released et toggled.

C'est donc clicked, dont la description correspond bien au clic sur le bouton, que nous choisissons.

Deuxièmement qApp :

Quel est l'objet qui doit exécuter le SLOT(action()) ?

Qu'est-ce que qApp ? qApp est une variable globale, pointeur qui pointe vers le QApplication que tu crées dans le main().
action() est un slot membre de ta classe MaFenetre. Pas de QApplication.
Au niveau de ton constructeur, n'aurais-tu pas quelque chose qui désigne un pointeur vers l'objet courant de ta classe MaFenetre ? Oui : this.

On se retrouve donc avec ceci (QObject:: n'est pas nécessaire étant donné que nous en héritons)

connect(m_bouton, SIGNAL(clicked()), this, SLOT(action()));

1) Revenons maintenant à ton point de départ et le passage du QLabel en paramètre. (Nous corrigeons bien sûr au passage la même erreur sur l'emploi de qApp à la place de this).

La première chose à savoir avec l'emploi des macros SIGNAL/SLOT c'est qu'avec, tu NE PEUX PAS passer des paramètres comme tu aurais aimé le faire. Les paramètres du slot ne peuvent arriver que depuis le signal émis (emit leSignal(valeurs_émises); -> leSlot(valeurs_reçues)). Et ici tu n'as évidemment pas la main sur le code de la classe QPushButton pour y ajouter ton QLabel sur la ligne qui émet clicked.

Ces macros ne prennent que les noms des signaux/slot avec les types (et seulement les types, PAS les noms des paramètres. Ex. action(QLabel*)).

Toute transgression à l'une de ces règles te vaudra un message "No such signal/slot" dans l'onglet de sortie, et une connexion qui ne se fera pas.

De l'époque de l'utilisation de ces macros, tu aurais pu t'en sortir en utilisant la classe QSignalMapper (dont l'utilisation est assez lourde et contraignante sur le choix des types passés), mais depuis Qt 5 et C++11 (qui date de 2011 soit 7 ans déjà !) nous avons bien mieux : les fonctions lambda. Et ces macros sont remplacées par des pointeurs sur fonctions membres.

connect(m_bouton, &QPushButton::clicked, this, [this,image]() { action(image); });

La fonction lambda [this,image]() { action(image); } va te permettre de capturer ce dont tu as besoin ([ici]). La fonction sera ensuite appelée lorsque le signal sera émit.

La première solution peut également s'écrire ainsi :

connect(m_bouton, &QPushButton::clicked, this, &MaFenetre::action);

Qu'apporte cette nouvelle écriture ?

  • Des erreurs à la compilation et non plus pendant l'exécution. Là, tu ne peux plus manquer le message d'erreur ^^
  • La possibilité de conversion implicite entre les paramètres du signal et du slot.
  • La possibilité d'utiliser les fonctions lambda, bien pratiques :D

Alors jette dès maintenant ces macros et opte pour Les signaux et slots dans Qt 5 ;)

-
Edité par Squall31 26 juin 2018 à 17:28:37

  • Partager sur Facebook
  • Partager sur Twitter
27 juin 2018 à 9:08:24

Bonjour,

Merci beaucoup pour cette réponse. J'ai tout très bien compris.
Je vais utiliser la solution 2), car j'aimerais bien comprendre les lambda fonction et savoir ce que je fais plutôt que de recopier. (Ce qui tentant étant donné la qualité de ce poste. :D)

Par contre, quand je lance mon programme depuis Qt, les images ne se chargent toujours pas: il m'est nécessaire de lancer l'exe dans le répertoire debug pour que tout fonctionne. Une idée?

Merci encore. ^^ 
  • Partager sur Facebook
  • Partager sur Twitter
27 juin 2018 à 18:08:23

Le dossier à partir duquel est lancée ton application n'est pas le même lorsque tu passes par ton IDE, réfère-toi à ce que retourne QDir::currentPath().

-
Edité par Squall31 27 juin 2018 à 18:09:55

  • Partager sur Facebook
  • Partager sur Twitter
28 juin 2018 à 14:08:21

Bonjour

J'ai utilisé QDir comme tu me l'as dis, de la façon suivante:
chm=QDir::currentPath();
image->setPixmap(QPixmap(chm+"/rcs/image1.png"));
Mais le problème persiste. (Les images ne chargent pas via l'ide, mais sont présente lorsque je clique sur l’exécutable).
J'au aussi des problèmes avec l'utilisation du son, mais je vais faire un topic pour cela.

Merci pour tout. :D
  • Partager sur Facebook
  • Partager sur Twitter
28 juin 2018 à 17:23:50

J'aurais-dû rajouter : regarde-le pour commencer, et place tes fichiers à lire selon ce que tu vois.

QDir::currentPath() te retourne le dossier à partir duquel l'exécutable est lancé. Ce dossier est le même que celui de ton exécutable SI tu le lances en double-cliquant dessus, mais PAS si tu le lances depuis ton IDE (où c'est généralement par défaut le dossier racine du projet ou dossier debug/release parent de l'exécutable pour Qt Creator) ou encore en ligne de commande depuis un tout autre dossier.

Sur ton IDE, tu peux régler ce dossier dans les options. Dans Qt Creator, sélectionne "Projets" dans les outils sur la gauche, puis "Run" juste à sa droite et modifie le "Working directory" dans la partie Exécuter.

Par le code, pour obtenir le dossier qui contient ton exécutable à coup sûr, tu as QCoreApplication::applicationDirPath(). Toutefois, les éléments fixes telle que ton image sont plutôt à placer dans un fichier de ressources .qrc où tu n'as plus à te soucier du chemin.

  • Partager sur Facebook
  • Partager sur Twitter
29 juin 2018 à 9:53:18

Okay, y'a tout qui marche c'est génial.

Merci beaucoup. :D

  • Partager sur Facebook
  • Partager sur Twitter