Search code examples
c++qtsignals-slots

How to disconnect all signals while function is running in Qt?


I have been searching for tow days, but nothing that could help me. I want to disconnect all signals while functions are running. The main trick is the class that emits signal and the class that receives it are both different classes. I have a QPushButton in class that emits signals, and my custom class Screen which receives signals, they are both connected.

The class that manages events (class sender)

    Game::Game(QWidget *parent)
         : QGraphicsView(parent)
        {
            //.................................//some declaration
        
            //add the main screen
            Screen * screen = new Screen();
            screen->SetImageOnTheScreen();
            scene->addItem(screen);
        
            //make the lunch work
            QPushButton * GO = new QPushButton;
            GO->setText("GO!!!");
            GO->setGeometry(134,-98,134,99);
            scene->addWidget(GO);
            QObject::connect(GO, SIGNAL(clicked(bool)), screen, SLOT(generate())); 
            //want to disconnect this connection while generate() doing its job
        
            //.................................//some declaration
        
        }

Class receiver

void Screen::SetImageOnTheScreen()
    {
        //.................................//some declaration
    }
    
    void Screen::setWinningIcon()
    {
        //.................................//some declaration
    }
    
    void Screen::generate()
    {
        line = 0;
    
        std::random_shuffle(mas, mas + 27);
    
        SetImageOnTheScreen();
    
        if(mas[0] == mas[1] || mas[0] == mas[2] || mas[1] == mas[2])
        {
            line = 1;
    
            delay();
            setWinningIcon();
    
            delay();
            SetImageOnTheScreen();
        }
    
        if(mas[3] == mas[4] || mas[3] == mas[5] || mas[4] == mas[5])
        {
            line = 2;
    
            delay();
            setWinningIcon();
    
            delay();
            SetImageOnTheScreen();
        }
    
        if(mas[6] == mas[7] || mas[6] == mas[8] || mas[7] == mas[8])
        {
            line = 3;
    
            delay();
            setWinningIcon();
    
            delay();
            SetImageOnTheScreen();
        }
    }

I was trying to use disconnect, and nothing. Was trying to use QObject::blockSignals() the same story.

Please any help would be great!!

UPDATE

game.h

#ifndef GAME_H
#define GAME_H

#include <QGraphicsScene>
#include <QGraphicsView>
#include <QPushButton>
#include "screen.h"

class Game : public QGraphicsView
{
private:
    QGraphicsScene * scene;
    QPushButton * GO;
    Screen * screen;

protected:
    virtual void wheelEvent (QWheelEvent * event);

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

};

#endif // GAME_H

game.cpp

#include "game.h"
#include <QGraphicsGridLayout>
#include <QDebug>
#include <QPushButton>
#include "screen.h"
#include <QStyle>

Game::Game(QWidget *parent)
    : QGraphicsView(parent)
{
    scene = new QGraphicsScene(this);
    scene->setSceneRect(0,0,400,200);

    screen = new Screen;
    screen->SetImageOnTheScreen();
    scene->addItem(screen);

    GO = new QPushButton;
    GO->setGeometry(0,0,100,50);
    GO->setText("GO");

    QObject::connect(GO, SIGNAL(clicked(bool)), screen, SLOT(generate()));
    //when generate is processing i want to disconnect the button "GO" and the "screen"

    scene->addWidget(GO);
    setScene(scene);
    show();
}

void Game::wheelEvent(QWheelEvent * event)
{
    if (event->type() == QEvent::Wheel)
        return;
    return;
}

Game::~Game()
{

}

screen.h

#ifndef SCREEN_H
#define SCREEN_H

#include <QGraphicsPixmapItem>
#include <QGraphicsTextItem>

class Screen : public QObject, public QGraphicsPixmapItem
{
    Q_OBJECT

private:
    int mas[27];
    int line;

    QGraphicsTextItem * text;

public:
    Screen(QGraphicsPixmapItem * parent = 0);
    void delay(int msecs);
    void setWinningIcon();
    void SetImageOnTheScreen();

public slots:
    void generate();
};

#endif // SCREEN_H

screen.cpp

#include "screen.h"
#include <QDebug>
#include <QTime>
#include <QCoreApplication>
#include "game.h"

Screen::Screen(QGraphicsPixmapItem * parent)
    : QObject(), QGraphicsPixmapItem(parent)
{
    for(int i = 0, j =1; i < 27; i++, j++)
    {
        if(j == 10)
            j = 1;

        mas[i] = j;
    }

    line = 0;
    text = new QGraphicsTextItem(this);
}

void Screen::delay(int msecs)
{
    QTime dieTime= QTime::currentTime().addMSecs(msecs);
    while (QTime::currentTime() < dieTime)
        QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
}

void Screen::setWinningIcon()
{
    switch(line)
    {
    case 1:
        text->setPlainText("TEST_#2 I'm NOT allowed to press /GO/ \nfor 3 seconds but i can");
        text->setDefaultTextColor(Qt::red);
        break;
    case 2:
        text->setPlainText("TEST_#3 I'm NOT allowed to press /GO/ \nfor 3 seconds but i can");
        text->setDefaultTextColor(Qt::red);
        break;
    case 3:
        text->setPlainText("TEST_#4 I'm NOT allowed to press /GO/ \nfor 3 seconds but i can");
        text->setDefaultTextColor(Qt::red);
        break;
    }
}

void Screen::SetImageOnTheScreen()
{
    text->setPlainText("TEST_#1 I'm ALLOWED to press /GO/");
    text->setPos(0,50);
    text->setDefaultTextColor(Qt::green);
}

void Screen::generate()
{
    //In here i want to prevent to press "GO" while "generate()" is processing

    //Screen::grabMouse(); //here it is my solution

    std::random_shuffle(mas, mas + 27);

    SetImageOnTheScreen();

    if(mas[0] == mas[1] || mas[0] == mas[2] || mas[1] == mas[2])
    {
        line = 1;

        delay(500);
        setWinningIcon();

        delay(3000);
        SetImageOnTheScreen();
    }

    if(mas[3] == mas[4] || mas[3] == mas[5] || mas[4] == mas[5])
    {
        line = 2;

        delay(500);
        setWinningIcon();

        delay(3000);
        SetImageOnTheScreen();
    }

    if(mas[6] == mas[7] || mas[6] == mas[8] || mas[7] == mas[8])
    {
        line = 3;

        delay(500);
        setWinningIcon();

        delay(3000);
        SetImageOnTheScreen();
    }

    //Screen::ungrabMouse(); //here it is my solution
}

main.cpp

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

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

    return a.exec();
}

Solution

  • Update

    I've taken the more recent code you've posted and made some changes to it.

    Summary

    Let's start with your comment:

    i can't reach "GO" button from the screen.cpp unless it is global. And i do not want to use any globals.

    You're on the right track in trying to avoid globals. However, I don't think your Screen class really needs to know about your GO button. Instead, you could do what I suggested in our discussion, which is that, instead of connecting your GO button directly to your Screen's generate() slot, you should instead connect the button to a separate on_button_clicked event handler in your Game class that will:

    1. disable or disconnect the GO button,
    2. call your screen's generate() method, and
    3. re-enable or re-connect the button after generate() returns.

    This would also imply that generate() may no longer need to be a slot, but that's up to you.

    Your Code - With My Suggested Updates

    I've made the following changes:

    In game.h, I added a slot to the Game class:

    private slots:
        void on_go_clicked();
    

    In game.cpp, I added the implementation as follows:

    void Game::on_go_clicked()
    {
        GO->setEnabled(false);  // or QObject::disconnect(....)
        screen->generate();
        GO->setEnabled(true);   // or QObject::connect(....)
    }
    

    And also replaced your constructor's QObject::connect call with the one below:

    QObject::connect(GO, SIGNAL(clicked(bool)), this, SLOT(on_go_clicked()));
    

    Now, you can keep your Screen class unaware of the existence of your GO button, which means less coupling and complexity, but still get to prevent the user from using your button in the meantime, which is what you want.

    Original Response

    Basically, you need to use QObject::disconnect as follows:

    QObject::disconnect(emitter, SIGNAL(signalName()), receiver, SLOT(slotName()));
    

    You can do this in the slot after the event gets handled.

    Disconnecting the signals/slots between the objects of interest is a better approach than trying to disconnect all signals globally for the whole application for several reasons, including unintended side-effects that may lead to bugs or other unexpected behavior.

    In your particular example, you have:

    QObject::connect(GO, SIGNAL(clicked(bool)), screen, SLOT(generate()));
    

    So to disconnect, you may only need to write this when you start processing:

    QObject::disconnect(GO, SIGNAL(clicked(bool)), screen, SLOT(generate()));
    

    And then re-connect after you're done processing.

    Or, as @Matt said in a comment, you could simply disable the button widget in the user interface and not mess around with the signals. If the user cannot click the button, then the signal cannot be emitted by the user.

    This is probably a simpler and more reliable solution.

    Signal-Blocking Update

    If you still want to connect/disconnect and you're using Qt 5.3+, then you should use QSignalBlocker, which also takes care of preserving and restoring things back to their previous state. Quoting from their docs:

    {
    const QSignalBlocker blocker(someQObject);
    // no signals here
    }
    

    is thus equivalent to

    const bool wasBlocked = someQObject->blockSignals(true);
    // no signals here
    someQObject->blockSignals(wasBlocked);
    

    Working Sample Code

    A short example app consisting of a window with a QPushButton and a QListWidget in a QMainWindow follows below.

    mainwindow.cpp

    #include "mainwindow.h"
    #include "ui_mainwindow.h"
    
    MainWindow::MainWindow(QWidget *parent) :
        QMainWindow(parent),
        ui(new Ui::MainWindow)
    {
        ui->setupUi(this);
    }
    
    MainWindow::~MainWindow()
    {
        delete ui;
    }
    
    void MainWindow::on_button_clicked()
    {
        ui->listWidget->addItem(QString("Clicked"));
    
        // this is the important line :o
        QObject::disconnect(ui->button, SIGNAL(clicked(bool)), this, SLOT(on_button_clicked()));
    }
    

    mainwindow.h

    #ifndef MAINWINDOW_H
    #define MAINWINDOW_H
    
    #include <QMainWindow>
    
    namespace Ui {
        class MainWindow;
    }
    
    class MainWindow : public QMainWindow
    {
        Q_OBJECT
    
    public:
        explicit MainWindow(QWidget *parent = 0);
        ~MainWindow();
    
    private slots:
        // this naming convention allows Qt to
        // create connections automatically
        void on_button_clicked();
    
    private:
        Ui::MainWindow *ui;
    };
    
    #endif // MAINWINDOW_H
    

    main.cpp

    #include "mainwindow.h"
    #include <QApplication>
    
    int main(int argc, char *argv[])
    {
        QApplication a(argc, argv);
        MainWindow w;
        w.show();
    
        return a.exec();
    }
    

    mainwindow.ui

    You can copy-paste the contents of the .ui file to reproduce the simple layout if needed. It's below:

    <?xml version="1.0" encoding="UTF-8"?>
    <ui version="4.0">
     <class>MainWindow</class>
     <widget class="QMainWindow" name="MainWindow">
      <property name="geometry">
       <rect>
        <x>0</x>
        <y>0</y>
        <width>400</width>
        <height>300</height>
       </rect>
      </property>
      <property name="windowTitle">
       <string>MainWindow</string>
      </property>
      <widget class="QWidget" name="centralWidget">
       <layout class="QVBoxLayout" name="verticalLayout_2">
        <item>
         <widget class="QPushButton" name="button">
          <property name="text">
           <string>Click Me</string>
          </property>
         </widget>
        </item>
        <item>
         <widget class="QListWidget" name="listWidget"/>
        </item>
       </layout>
      </widget>
      <widget class="QMenuBar" name="menuBar">
       <property name="geometry">
        <rect>
         <x>0</x>
         <y>0</y>
         <width>400</width>
         <height>27</height>
        </rect>
       </property>
      </widget>
      <widget class="QToolBar" name="mainToolBar">
       <attribute name="toolBarArea">
        <enum>TopToolBarArea</enum>
       </attribute>
       <attribute name="toolBarBreak">
        <bool>false</bool>
       </attribute>
      </widget>
      <widget class="QStatusBar" name="statusBar"/>
     </widget>
     <layoutdefault spacing="6" margin="11"/>
     <resources/>
     <connections/>
    </ui>