Signal/Slot

Le problème exposé dans ce sujet a été résolu.

Bonjour,

Je tiens tout d'abord à préciser comme c'est indiqué dans le titre que je débute sur QT.

Je dispose d'une liste de QComboBox (créée dans un slot en fonction d'une QSpinBox) et je voudrais pouvoir créer un slot qui me permettrait d'afficher où non une QLine edit en fonction de la valeur sélectionnée. Voici mon code :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
for(int i=tmp;i<nbLigne;i++)
{

     //Ajoute la liste des comboBox
     QComboBox *elementBox=new QComboBox();
     QStringList choix;
     choix << "a" << "b" << "c" ;
     elementBox->addItems(choix);
     listeBox.push_back(elementBox);
     layout->addWidget(listeBox.last(),i+2,3);

     //Ajoute la liste des champs
     QLineEdit *elementLigne= new QLineEdit();
     elementLigne->hide();
     listeLigne.push_back(ligneDentSecondaire);
     layout->addWidget(listeLigne.last(),i+2,4);

     //Et le signal
      QObject::connect(listeBox.at(i),SIGNAL(currentIndexChanged(int)),listeLigne.at(i),SLOT(?));

On m'a déjà parlé de cette fonction :

1
2
auto indice = ta_comboBox.currentIndex();
QObject::connect(ta_comboBox, &QComboBox::currentIndexChanged, [indice](){ton_slot(indice);});

Cependant lorsque je l'adapte à mon code j'obtiens une erreur que je ne comprend pas :

1
'this' was not captured for this lambda function

Et il ne me semble pas que .currentIndex récupère de manière dynamique le chagement de valeur (vu que ce n'est pas un signal)

Désolé si je ne suis pas très clair dans mes explications.

Merci d'avance pour votre aide

Salut

Si, les explications sont très claires.

HS : C'est "Qt" et pas "QT"

Je suppose que tu ne connais pas bien les fonctions lambdas ? Peut être un peu de lecture : les fonctions lambdas

Il y a en fait 2 erreurs dans ta lambda :

  • indice est capture par valeur (copie), donc cela sera une constante pendant toute la durée de vie de ta lambda (elle se met pas a jour lorsque l'indice change)
  • this n'est pas capturé

Il faut donc remplacer tes 2 lignes par :

1
2
3
4
5
6
7
QObject::connect(
    ta_comboBox,                                       // l'objet qui émet le signal
    &QComboBox::currentIndexChanged,                   // le signal qui déclenche la connexion
    this,                                              // l'objet qui reçoit le signal
    [this](){ ton_slot(ta_comboBox.currentIndex()); }  // la fonction qui est exécutée lorsque 
                                                       // le signal est émis
);

Un autre lien, sur les connexions dans Qt5 : Les signaux et slots dans Qt 5.4. Je te conseille de lire les 2 liens, ton code devrait etre plus clair ensuite. Si ce n'est pas le cas, n’hésite pas a poser d'autres questions.

+0 -0

Salut,

Merci beaucoup pour ta réponse. Je viens de lire les deux liens que tu m'as conseillé. Effectivement je n'étais pas très familié avec les fonctions lambda. J'ai donc lu deux trois complément à ce que tu m'as donné. Je ne pense toujours pas forcément avoir saisi toutes les subtilités..

Pour ce qui est de mon code voici comme j'ai adapté ta solution :

1
2
3
4
5
6
7
QObject::connect(
    malistedeComboxBox.at(i),                                       // l'objet qui émet le signal
    &QComboBox::currentIndexChanged,                   // le signal qui déclenche la connexion
    malistedeQlineEdit.at(i),                            // l'objet qui reçoit le signal
    [this,i](){ mon_slot(malistedeComboxBox.at(i)->currentIndex()); }  // la fonction qui est exécutée lorsque 
                                                       // le signal est émis
);

J'ai par ailleurs (suite à la lecture du cours) enlevé mon slot de ma liste des slots dans le .h et l'ai déclaré avec les autres fonctions

1
void mon_slot(int a);

J'ai rajouté 'i' dans la liste des élément capturé par la fonction lambda, car je recevais une erreur m'indiquant qu'il ne le connaissait pas. J'ai maintenant une erreur qui me retourne

1
no matching function call to

Ce qui me fait dire que j'ai du mal comprendre tes explications. Tu m'avais donné :

1
this //objet qui recoit le signal

J'ai testé avec this et avec le QLineEdit. De ce que j'ai compris, c'est bien this qui devrait fonctionner car ma fonction est déclaré dans mon fichier.h et j'ai donc bien besoin de ce pointeur pour pouvoir l'appeler. Comme ni l'une ni l'autre des solutions ne fonctionne je me dis que je dois être largement passé à côté de quelque chose.

Pourrais tu éclairé s'il te plait l'ignorant que je suis

Merci!

EDIT je viens aussi de penser au fait que j'ai lu qu'il fallait préciser quelle version de current.index() j'utilisais en précisant le type de retour de la fonction, je n'ai pas réussi à appliquer ça à mon code.

+0 -0

J'ai rajouté 'i' dans la liste des élément capturé par la fonction lambda, car je recevais une erreur m'indiquant qu'il ne le connaissait pas. J'ai maintenant une erreur qui me retourne

Puisque i est une valeur externe a ton connect, oui. (Je suppose que tu as une boucle qui crée plusieurs connexions, une pour chaque combobox ?)

J'ai testé avec this et avec le QLineEdit

Il faut faire attention de ne pas confondre les 2 syntaxes (avec un slot et avec une lambda).

Avec un slot, la fonction connect prend en paramètre un objet (QObject) et un pointeur de fonction membre (syntaxe barbare, mais pas trop)

1
2
3
4
5
connect(
   sender, signal,         // osef...
   object,                 // QObject* qui reçoit le slot
   &NomClasse::NomFunction // signature de la fonction membre (slot) qui sera appelée
);

Dans ce cas, l'objet doit obligatoirement être celui qui contient le slot (+ d'autres contraintes : ajouter la macro Q_OBJECT, déclarer la fonction comme slot).

Avec une lambda, tu peux avoir 2 signatures pour connect :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
connect(
   sender, signal,  // osef...
   function         // n'importe quoi d'appelable ("callable") : function 
                    // libre, lambda, std::bind, etc.
);

connect(
   sender, signal,  // osef...
   object,          // QObject*
   function         // n'importe quoi d'appelable ("callable") : function 
                    // libre, lambda, std::bind, etc.
);

Une fonction membre s’exécute dans le contexte particulier de l'objet sur lequel elle est appelée (le this), il faut donc fournir cet objet. Une lambda s’exécute dans son propre contexte, elle n'a pas besoin d’être appelée via un objet.

Dans le cas d'une lambda, l'objet est utilise uniquement pour détruire la connexion si l'objet est détruit. Il ne sert pas a exécuter la lambda.

Par contre, DANS la lambda, tu peux appeler des fonctions membres. Et ces fonctions membres doivent donc être appelée via un objet (le contexte d'appel de la fonction membre). Les objets sont passés dans le contexte de la lambdas via la capture (les []).

Donc pour résumer, tu as 3 contextes :

  • le contexte de la fonction membre dans lequel tu appelles le connect
  • le contexte de la lambda
  • le contexte de la fonction membre DANS la lambda

Au final, dans le connect :

  • le premier this permet de détruire la connexion, mais n'est pas utilise par la lambda
  • le second this (dans la capture []) permet d'envoyer this a la lambda pour appeler la fonction membre DANS la lambda.

Tu peux donc utiliser this ou at(i), c'est pareil, mais c'est different : dans le premier cas, tu detruit la connexion quand this est detruit, dans le second cas lorsque l'une des combobox est detruite (si une combobox peut etre detruite individuellement, c'est mieux la seconde syntaxe).

Par contre, il est preferable de rester coherent :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
auto macombobox = malistedeQlineEdit.at(i);
connect(
  ...
  macombobox,
  [this, macombobox,i](){ mon_slot(macombobox->currentIndex()); } // il faut les 3 dans []
);

// ou 

connect(
  ...
  this
  [this,i](){ mon_slot(malistedeComboxBox.at(i)->currentIndex()); }

Le but de passer un objet dans le connect pour detruire la connexion est d'eviter d'appeler la lambda avec des donnees invalides. Donc on passe les memes donnees dans la lambda et le connect.

EDIT je viens aussi de penser au fait que j'ai lu qu'il fallait préciser quelle version de current.index() j'utilisais en précisant le type de retour de la fonction, je n'ai pas réussi à appliquer ça à mon code.

Oui (probleme restant de Qt4…)

1
2
3
connect(
    comboBox,
    static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
+0 -0

Merci pour toutes ces explications.

Je suppose que tu as une boucle qui crée plusieurs connexions, une pour chaque combobox ?

Oui je suis dans une boucle for dans un slot. En fait mes QLine Edit et ComboBox apparaissent dynamiquement lorsqu'on change la valeur d'une spinBox

Tu peux donc utiliser this ou at(i), c'est pareil, mais c'est different

J'étais déjà entrain de froncer les sourcils pour essayer de tout comprendre, j'ai eu du mal à ne pas décrocher :p

auto macombobox = malistedeQlineEdit.at(i); connect( … macombobox, this, macombobox,i{ mon_slot(macombobox->currentIndex()); } // il faut les 3 dans [] );

est ce que ce n'est pas plutôt auto macomboxBox = listecomboBox.at(i) ?

si une combobox peut etre detruite individuellement, c'est mieux la seconde syntaxe

Les comboxBox disparaissent lorsqu'on diminue la valeur de la spinBox et je pense avoir fait en sorte qu'elles soient détruits correctement. J'ai donc utilisé la seconde syntaxe que tu me proposes.

Par contre, DANS la lambda, tu peux appeler des fonctions membres. Et ces fonctions membres doivent donc être appelée via un objet (le contexte d'appel de la fonction membre).

Ca veut bien dire que je dois déclarer ma fonction (ici mon slot) dans mon .h comme une fonction classique ?

Je me retrouve avec ces deux lignes :

dans le .h

1
void monSlot(int a);

dans le .cpp

1
2
3
4
QObject::connect(listeComboBox.at(i),
static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
this,
[this,i](){monSlot(listeComboBox.at(i)->currentIndex());});

Et il me dit qu'il ne connait pas la fonction :

1
no matching function for call to 'maClass::Connect(QComboBox* const&, void(QComboBOx::*)(int),maClasse*,maClasse::slotQuiCreerLesComboEtLesLignes(int)::<lambda()>)'

Désolé, peut être que c'est une tout autre erreur ou que je n'applique tout simplement pas tes conseils correctement mais je t'avoue que je commence à manquer de recul depuis le temps que je bloque sur ça !

+0 -0

Ca veut bien dire que je dois déclarer ma fonction (ici mon slot) dans mon .h comme une fonction classique ?

Si tu utilises la syntaxe de connect sans lambda, oui (a verifier, j'ai un doute…)

Mais avec une lambda, non. D'ailleurs, ce n'est pas un slot (ie une fonction appellees lorsque le signal est lance). Le slot, c'est la lambda. C'est une simple fonction membre, donc pas besoin de declarer en slot.

Désolé, peut être que c'est une tout autre erreur ou que je n'applique tout simplement pas tes conseils correctement mais je t'avoue que je commence à manquer de recul depuis le temps que je bloque sur ça !

Non, c'est bon, c'est cela que je t'ai dit de faire… du coup, il y a truc que je ne vois pas non plus. C'est le probleme avec ce type de syntaxe, on sait pas ou se trouve l'erreur.

J'ai un doute sur at(). Peut etre le const. Essaie en utilisant directement le pointeur (elementBox dans ton premier code).

+0 -0

Je viens de tester.

J'ai donc du capturer mon elementBox dans la fonction lambda et j'obtiens :

1
2
3
4
 QObject::connect(elementBox,
static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
this,
[this,elementBox](){showLine(elementBox->currentIndex());});

Toujours la même erreur : no matching function call to maClass::connect…

Je t'avoue que je commence à me perdre un peu. Avec cette manière de connecte pour afficher la bonne QLineEdit tout ce passera dans ma fonction monSlot(int a) ? Car je me suis lancé dans le code la fonction même si le connecte ne marche pas pour le moment et je ne comprend pas très bien comment je vais retrouver à ma QLineEdit à afficher.

Merci de ta patience

EDIT : peut être que je ne t'ai pas donné toutes les informations, si il y'a des choses qui te semble évidentes mais qui pourraient empêcher ce bout de code de fonctionner il se peut que j'ai fait des erreurs en amont.

EDIT2 : j'ai une autre erreur qui était en dessous de l'autre (car le message est très très long)

1
2
3
template argument for 'template<class Func1, class Func2> static typename QtPrivate::QEnableIf<(((int)(QtPrivate::FunctionPointer<Func2>::ArgumentCount) >= 0) && (! QtPrivate::FunctionPointer<Func2>::IsPointerToMemberFunction)), QMetaObject::Connection>::Type QObject::connect(const typename QtPrivate::FunctionPointer<Func>::Object*, Func1, const QObject*, Func2, Qt::ConnectionType)' uses local type 'maClasse::monSlotQuiCreerLesComboBoxEtLineEdit(int)::<lambda()>'
               QObject::connect(elementBox,static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),this,[this,elementBox](){showLine(elementBox->currentIndex());});
                                                                                                                                                                                                      ^
+0 -0

Argggggg, l'erreur bete. Le signal et la lambda n'ont pas le meme nombre d'arguments (cf ton EDIT2).

En fait, tu n'as pas besoin de elementBox, l'indice est envoye par le signal.

1
2
3
4
5
6
QObject::connect(
    elementBox,
    static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
    this,
    [this](int index){showLine(index);}
);

Ca devrait mieux fonctionner.

+1 -0

Au risque de mettre tes nerfs à rude épreuve… Ca ne marche toujours pas. Je vais essayer de te donner un peu plus d'informations peut être que je ne t'ai pas communiqué quelque chose d'essentiel.

Ma boucle :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
 for(int i=tmp;i<nbLigne;i++)
         {
              //Création et affichage de la comboBox
              QComboBox *uneComboBox=new QComboBox();
              QStringList choixComboBox;
              choixComboBox << "0" << "1" << "2" ;
              uneComboBox->addItems(choixComboBox);
              colonneComboBox.push_back(ligneNbAuxiliaire);
              layout->addWidget(colonneComboBox.last(),i,1);

              //Création et affichage de la QLigneEdit correspondante
              QLineEdit* uneLineEdit1 = new QLineEdit();
              QLineEdit1->hide();
              colonneLineEdit1.push_back(uneLineEdit1 );
              layout->addWidget(colonneLineEdit1.last(),i,2);

/* Je crée exactement 3 colonnes de QLineEdit je repète donc ce bloc 2 autres fois   
avec deux autre colonnes (colonneLineEdit2 et colonneLinEdit3) 
L idée comme tu as du le comprendre c'est que quand l'utilisateur choisi pour la ligne i une valeur de la
 comboBox, on affiche à la ligne i soit la colonne 1 soit la colonne 1 et la 
 colonne 2 soit les 3 (0,1,2) et  que si il change d'avis je puisse à nouveau cacher les colonnes inutiles*/

             //Je crée le signal 
             QObject::connect(uneComboBox,
                          static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
                              this, 
                              [this](int index){monSlot(index);});

             /*Je ne suis pas très sur de comprendre comment d'ailleurs ici je connecte à la 
                bonne QLineEdit? Ni comment je pourrai dans mon slot récupérer l'indice de ma 
               ligne pour afficher la QLineEdit correspondante*/

}

J'ai dans mon fichier .h :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
namespace Ui {
class maClasse;
}

class maClasse: public QDialog
{
    Q_OBJECT

public:
        void monSlot(int a);

public slots:

       void leSlotQuiCreerLesLignes(int) //C'est là que se trouve le code qui est au dessus

private : 
        QList<QLineEdit *> colonneLineEdit1;
        QList<QLineEdit *> colonneLineEdit2;
        QList<QLineEdit *> colonneLineEdit3;
        QList<QComboBox *> colonneComboBox;
};

Voici la liste des erreurs qu'il me retourne :

1
2
maClasse.cpp:389: erreur : no matching function for call to 'maClasse::connect(QLineEdit*&, void (QComboBox::*)(int), maClasse*, maClasse::leSlotQuiCreerLesLignes(int)::<lambda(int)>)'
QObject::connect(uneComboBox,static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),this,[this](int index){monSlot(index);});
1
2
maclasse.cpp:389: erreur : template argument for 'template<class Func1, class Func2> static QMetaObject::Connection QObject::connect(const typename QtPrivate::FunctionPointer<Func>::Object*, Func1, const typename QtPrivate::FunctionPointer<Func2>::Object*, Func2, Qt::ConnectionType)' uses local type 'maclasse::leSlotQuiCreerLesLignes(int)::<lambda(int)>'
QObject::connect(uneComboBox,static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),this,[this](int index){monSlot(index);}); ^
1
2
maclasse.cpp:389: erreur : template argument for 'template<class Func1, class Func2> static typename QtPrivate::QEnableIf<(((int)(QtPrivate::FunctionPointer<Func2>::ArgumentCount) >= 0) && (! QtPrivate::FunctionPointer<Func2>::IsPointerToMemberFunction)), QMetaObject::Connection>::Type QObject::connect(const typename QtPrivate::FunctionPointer<Func>::Object*, Func1, const QObject*, Func2, Qt::ConnectionType)' uses local type 'maClasse::leSlotQuiCreerLesLignes(int)::<lambda(int)>'
QObject::connect(uneComboBox,static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),this,[this](int index){monSlot(index);});^
1
2
maClasse.cpp:389: erreur : template argument for 'template<class Func1, class Func2> static typename QtPrivate::QEnableIf<(QtPrivate::FunctionPointer<Func2>::ArgumentCount == (-1)), QMetaObject::Connection>::Type QObject::connect(const typename QtPrivate::FunctionPointer<Func>::Object*, Func1, const QObject*, Func2, Qt::ConnectionType)' uses local type 'maClasse::leSlotQuiCreerLesLignes(int)::<lambda(int)>'
QObject::connect(uneComboBox,static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),this,[this](int index){monSlot(index);});^

Voilà là je pense que tu as tout. Je me suis dis que j'avais peut être mal compris qqchose et j'ai essayé de remplacer uneComboBox par colonneComboBox.at(i), même erreur, et même avec une QLineEdit j'ai les mêmes erreurs.

Si jamais cela te fait te dire que j'ai peut être mal fait qqchose ailleurs n'hésite pas à me demander!

Merci encore

+0 -0

Je viens de coller ton code dans un nouveau projet, cela compile sans probleme…

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// .h 
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

class MainWindow : public QMainWindow
{
  Q_OBJECT

public:
  MainWindow(QWidget *parent = 0);
  ~MainWindow();

  void monSlot(int) {}
};

#endif // MAINWINDOW_H

// .cpp
#include "mainwindow.h"
#include <QComboBox>

MainWindow::MainWindow(QWidget *parent)
  : QMainWindow(parent)
{
  QComboBox *uneComboBox=new QComboBox();
  QObject::connect(uneComboBox,
                   static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
                   this,
                   [this](int index){monSlot(index);});
}

MainWindow::~MainWindow()
{

}

Question bete, tu as active le C++11 ? (CONFIG += C++11 dans le .pro).

/Je ne suis pas très sur de comprendre comment d'ailleurs ici je connecte à la bonne QLineEdit? Ni comment je pourrai dans mon slot récupérer l'indice de ma ligne pour afficher la QLineEdit correspondante/

Pas sur de comprendre.

Pour commencer, si tu as une gille, tu ne peux pas afficher un nombre different de colonne pour chaque ligne. Toutes les lignes doivent avoir le meme nombre de colonne.

(Ou alors, ce n'est pas une grille ?)

Pour ton probleme de QLineEdit, c'est simple : il n'y a pas :)

Quand tu selectionnes une valeur dans une combobox, cela appelle ta fonction monSlot, avec l'index envoye par le signal, c'est a dire l'index dans la combobox. Donc ta fonction monSlot recevra les memes infos, quelque soit la combobox.

Pour regler ce probleme, il ajouter une information supplementaire, le numero de ligne correspondant a la combobox. Cette information ne depend pas du signal, mais uniquement du moment ou la lambda est cree. C'est donc une information qui doit etre liee au contexte de la lambda, c'est a dire passe dans la capture. (Et il faut modifier monSlot pour prendre en compte cette info).

1
[this, i](int index){monSlot(i, index);} // monSlot(line, value)
+1 -0

Salut

Question bete, tu as active le C++11 ? (CONFIG += C++11 dans le .pro).

Je t'avais bien dit qu'il ne fallait pas me surestimer :D

Désolé de t'avoir fait perdre autant de temps pour une erreur aussi bête… Que je ne suis d'ailleurs même pas sur de comprendre, je croyais que ce genre d'information était configuré lors de l'initialisation du logiciel/du projet…

/Je ne suis pas très sur de comprendre comment d'ailleurs ici je connecte à la bonne QLineEdit? Ni comment je pourrai dans mon slot récupérer l'indice de ma ligne pour afficher la QLineEdit correspondante/

Pas sur de comprendre.

En fait ma page est une page de saisie d'info par l'utilisateur qui est effectivement sous forme de grille. On dispose de plusieurs colonnes de QLineEdit. mes trois dernières colonnes sont :

-1 colonne de comboBox -2 colonne de QLineEdit

Toutes les QLineEdit sont créées par défaut cachées ( parce que je m'étais bien rendu compte que je ne pouvais les créer comme je voulais avec mes connaissances ) Je voulais donc pouvoir afficher/cacher dynamiquement les QLineEdit en fonction de la valeur de la comboBox à la bonne ligne. (je ne suis pas sur que mon explication soit plus claire)

En tout cas merci encore pour ton temps, le code compile sans problème cette fois et tout marche comme je le voulais! Merci beaucoup

Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte