Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

Connect signals with arguments



  • Hello,
    I am trying to do a small videogame with QT for school.
    The main goal is to fight ennemies and go the further.
    When you defite a enemy, you can get item carried by him, and that is my point.
    To get the item, I press a button which is connected to a function that add item to hero's backpack.
    I have to give the item in argument, that why I defined a special class for my button, ItemButton, which extends QPushButton.
    Here is itembutton.cpp

    void ItemButton::emitclicked(Item new_item){
    
        emit clicked();
    
    }
    

    and itembutton.h

    #ifndef ITEMBUTTON_H
    #define ITEMBUTTON_H
    #include <QPushButton>
    #include "item.h"
    
    class ItemButton : public QPushButton
    {
    
    public:
    signals:
        void emitclicked(Item new_item);
    
    };
    #endif // ITEMBUTTON_H
    

    When i connect my button to this function by

    connect(button, SIGNAL(emitclicked(actual_ennemy->getItems().getItem(i))), this, SLOT(new_item(actual_ennemy->getItems().getItem(i))));
    

    i get the following error :

    QObject::connect: No such signal QPushButton::emitclicked(actual_ennemy->getItems().getItem(i)) in ..\Programmation\mainwindow.cpp:501
    

    So QT think i call a signal from QPushButton. But button is a ItemButton (ItemButton *button;)
    So, how can i connect to my signal defined in ItemButton ? Thanks


  • Lifetime Qt Champion

    @Lc44bzh said in Connect signals with arguments:

    emitclicked(actual_ennemy->getItems().getItem(i))

    You can't pass parameters to signal when you connect. Parameters are passed to the signal when the signal is emitted. Same applies to slots: those get their parameters from the signal.
    Please read https://doc.qt.io/qt-5/signalsandslots.html There are also examples.


  • Moderators

    There are several issues with your code:

    • signal cannot have implementation (you can't have emitclicked() body in cpp file, only declaration in header)
    • connect statement only connects signals and slot, you cannot provide any actual data there like actual_ennemy->getItems().getItem(i)). The only stuff you should provide there are argument types. Even better: use new functor-based connection syntax https://doc.qt.io/qt-5/signalsandslots-syntaxes.html
    • connect is called only once, when a connection is made - so it has no idea what data will be sent through it later

    So QT think i call a signal from QPushButton

    Not quite, your code is simply wrong. Here is how to fix it:

    • connect your button's clicked() signal (it can be a normal QPushButton, no need for a subclass!) to a slot in your controller class (seems like you're using MainWindow for that), like this:
      connect(button, &QPushButton::clicked, this, &MainWindow::onButtonClicked
    • in your slot, get the necessary data:
    MainWindow::onButtonClicked()
    {
      actual_ennemy->getItems().getItem(i);
    }
    
    • of course, you also need to know which i to use, and actual_ennemy needs to be set. I know too little of your program to tell you how to do that


  • @jsulm said in Connect signals with arguments:

    @Lc44bzh said in Connect signals with arguments:

    emitclicked(actual_ennemy->getItems().getItem(i))

    You can't pass parameters to signal when you connect. Parameters are passed to the signal when the signal is emitted. Same applies to slots: those get their parameters from the signal.
    Please read https://doc.qt.io/qt-5/signalsandslots.html There are also examples.

    I saw on https://openclassrooms.com/fr/courses/1894236-programmez-avec-le-langage-c/1899731-utilisez-les-signaux-et-les-slots that it may be possible. Never mind.

    @sierdzio said in Connect signals with arguments:

    There are several issues with your code:

    • signal cannot have implementation (you can't have emitclicked() body in cpp file, only declaration in header)
    • connect statement only connects signals and slot, you cannot provide any actual data there like actual_ennemy->getItems().getItem(i)). The only stuff you should provide there are argument types. Even better: use new functor-based connection syntax https://doc.qt.io/qt-5/signalsandslots-syntaxes.html
    • connect is called only once, when a connection is made - so it has no idea what data will be sent through it later

    So QT think i call a signal from QPushButton

    Not quite, your code is simply wrong. Here is how to fix it:

    • connect your button's clicked() signal (it can be a normal QPushButton, no need for a subclass!) to a slot in your controller class (seems like you're using MainWindow for that), like this:
      connect(button, &QPushButton::clicked, this, &MainWindow::onButtonClicked
    • in your slot, get the necessary data:
    MainWindow::onButtonClicked()
    {
      actual_ennemy->getItems().getItem(i);
    }
    
    • of course, you also need to know which i to use, and actual_ennemy needs to be set. I know too little of your program to tell you how to do that

    Thank you, I used your solution and it's working. I defined the objectname of each button with i, and when I am in onButtonClicked, I just read the sender object name to get the index of my item.

    Thank for your help both of you.



  • @Lc44bzh said in Connect signals with arguments:

    I saw on https://openclassrooms.com/fr/courses/1894236-programmez-avec-le-langage-c/1899731-utilisez-les-signaux-et-les-slots that it may be possible. Never mind.

    The tutorial itself is not wrong. Maybe you misunderstood it :)

    I defined the objectname of each button with i, and when I am in onButtonClicked, I just read the sender object name to get the index of my item

    Using objectNames is possible, but not the best solution, since you heavily rely on them then.
    (Edit: actually, using objectNames to get your indices is bad style and very ugly)

    Edit:

    Depends on your use case and how the rest of your game should work, but consider using something like a QMap or any other container to store your items and/or map them to your buttons / enemies, or whatever you want to do with them.


  • Lifetime Qt Champion

    @Lc44bzh hi and welcome to devnet,

    You misunderstood the tutorial. You are mixing the function signature with function parameters.

    The connect statement in its old form takes string parameters that are the signature of the signal function and the signature of the slot function. You can't pass parameter values in there (nor can you with the syntax).



  • @Lc44bzh said in Connect signals with arguments:

    I saw on https://openclassrooms.com/fr/courses/1894236-programmez-avec-le-langage-c/1899731-utilisez-les-signaux-et-les-slots that it may be possible. Never mind.

    In the connect statement you only have the types of the parameters. However, you cannot provide the actual parameters in the connect statement. Instead you have to provide them when you call (emit) the signal. A button has no parameters when clicked. You can connect clicked() to some slot which extracts the information and then emit another signal with this parameter connected to the slot you already initially had.

    There might be a shortcut for what you want to do. All this assumes that you already know actual_ennemy and the index i when you create the button (which I assume is the case). Then you can replace your connect with the following:

    connect(button, &QPushButton::clicked, this, [this](){ new_item(actual_ennemy->getItems().getItem(i)); });
    

    This is the newer connect syntax. Your button can now be a QPushButton again. The third parameter to connect is just a context object. this makes sense in this context. Instead of an actual slot here we connect to a lambda function. Depending on which variables are member of your class and which are local variables you might have to capture additional variables in the lambda. This is the easiest way I know to quickly connect a signal with fewer arguments than the required slot.



  • Thanks for your answers. Now, when I create a button, I store the item pointer associated with him. And I use connect with Item type, so I can get the item related to my button. Here is my code, and it work properly :
    itembutton.h :

    #ifndef ITEMBUTTON_H
    #define ITEMBUTTON_H
    #include <QPushButton>
    
    #include "item.h"
    #include <iostream>
    using namespace std;
    
    class ItemButton : public QPushButton
    {
        Q_OBJECT
        
    public:
        ItemButton(Item * it, QWidget * parent);
        
    Q_SIGNAL void clicked(Item *);
    
    private:
    Q_SLOT void reemitClicked();
        
    private:
        Item *myitem;
    };
    #endif // ITEMBUTTON_H
    
    

    itembutton.cpp :

    #include "itembutton.h"
    
    ItemButton::ItemButton(Item *it, QWidget *parent) : QPushButton(parent)
    {
        myitem = it;
        connect(this, SIGNAL(clicked()), this, SLOT(reemitClicked()));
    }
    
    
    void ItemButton::reemitClicked()
    {
        emit clicked(myitem);
    }
    
    

    connect function in mainwindow.cpp :

    connect(button, SIGNAL(clicked(Item*)), this, SLOT(new_item(Item*)));
    


  • @Lc44bzh said in Connect signals with arguments:

    Now, when I create a button, I store the item pointer associated with him. And I use connect with Item type, so I can get the item related to my button.

    Well done. This is also a proper solution. It seems that you significantly improved your understanding of Qt's signals and slots.



  • @Lc44bzh
    Indeed well done!

    As it's my bug-bear, can I suggest you go further and change over from using SIGNAL/SLOT() macros to the New Signal Slot Syntax? You really will benefit, with compile-time checking of your signals, slots & parameters. Note how @SimonSchroeder used this in his example (it used a lambda for the slot, which is more complicated but very powerful, but nonetheless used the new style syntax, as one has to anyway for lambdas).

    For example,

    connect(this, SIGNAL(clicked()), this, SLOT(reemitClicked()));
    

    would be better written as

    connect(this, &ItemButton::clicked, this, &ItemButton::reemitClicked);
    

Log in to reply