0

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();
}
Community
  • 1
  • 1
Bob
  • 1,433
  • 1
  • 16
  • 36
  • So, you're basically saying that you want to know how to disconnect signals in general or is this an issue that's specific to your custom class? – code_dredd Nov 08 '15 at 13:19
  • I would say both. I want you (guys) to help me to implement this disconnection in a right way. Mine dose not work at all. – Bob Nov 08 '15 at 13:21
  • So what's wrong? Do `blockSignals()` upon entering `generate()` and unblock it on leaving. What doesn't work exactly? – Matt Nov 08 '15 at 13:27
  • @Matt just did this `blockSignals(true) ... some code ... blockSignals(false)` and i still can press `GO` button – Bob Nov 08 '15 at 13:31
  • The documentation for `blockSignals` method says "signals emitted by **this** object are blocked" (my emphasis). It won't block all signals globally. How would you close/exit your app then? – code_dredd Nov 08 '15 at 13:56
  • @ray would you please give a small example? We have class `A` and class `B` they are connected in class `C`. So how to disconnect `A` & `B` in a class `A`? – Bob Nov 08 '15 at 14:01
  • 2
    @Mikhail Won't be easier just to disable button then? `sender()->setEnabled(false);` – Matt Nov 08 '15 at 14:01
  • @Mikhail: In my example, class `A` is the `QMainWindow` and the class `B` is the `QPushButton`. You can see `A` disconnecting itself from `B` using `QObject::disconnect(ui->button, SIGNAL(clicked(bool)), this, SLOT(on_button_clicked()));` What's *not* clear about that? – code_dredd Nov 08 '15 at 14:04
  • @ray I'm trying to use encapsulation pattern so i can't see my Button from another class. – Bob Nov 08 '15 at 14:08
  • @Mikhail: Encapsulation is a general object-oriented principle, not a design pattern. Interestingly, you say you don't want the button to be visible from another class, but you want to connect/disconnect from there? That would obviously make your other location not only aware of the objects, but *also* of the fact that they're connected, so it seems counter-productive for you. – code_dredd Nov 08 '15 at 14:11
  • 1
    @Mikhail: Please take a moment to see my updated post. – code_dredd Nov 09 '15 at 08:48

2 Answers2

4

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>
code_dredd
  • 5,915
  • 1
  • 25
  • 53
  • thank you but can you show how to make it on my example? – Bob Nov 08 '15 at 13:48
  • 1
    @Mikhail: You should adapt my example to your situation. I already explained how you should use `QObject::disconnect` – code_dredd Nov 08 '15 at 13:58
  • 2
    @Mikhail: You seem dissatisfied. I hope you're not expecting someone to just write the code up for you without you making an effort to understand how your own code is working? – code_dredd Nov 08 '15 at 14:24
  • 1
    Imo this is the best answer considered the original post. If you want a better answer, please ask a better question. specifically read http://stackoverflow.com/help/mcve – aep Nov 08 '15 at 14:40
  • @Mikhail: Please consider up-voting and accepting the answer if it was the most helpful to you, as this the way to say 'thank you' around here. It benefits everyone, including yourself. – code_dredd Nov 08 '15 at 14:56
  • @ as you can see i voted-up all the users that did answer. but i can not accept the answer because the problem is still open. – Bob Nov 08 '15 at 15:03
  • 1
    @Mikhail: Have you tried any of the suggestions and examples that I provided? If yes, what *specifically* did not work for you? It's like aep said, "If you want a better answer, please ask a better question". I'd help if you had taken the time to provide a minimal (but working) example that would allow us to *reproduce* the issue. – code_dredd Nov 09 '15 at 00:27
  • @Mikhail: I meant to say "It'd", not "I'd" – code_dredd Nov 09 '15 at 02:06
  • @ray, Yes of course, i tried all the solutions you had provided. And I'm thankful for your help. What Specifically did not work, i can't reach `"GO" button` from the `screen.cpp` unless it is global. And i do not want to use any globals. Please look at my `new` answer, i have provided working example with my problem. – Bob Nov 09 '15 at 07:41
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/94576/discussion-between-ray-and-mikhail). – code_dredd Nov 09 '15 at 08:21
  • But isn't there a race condition with this ? – jean-loup Feb 18 '21 at 14:48
  • @jean-loup Why/Where would there be one? – code_dredd Mar 02 '21 at 23:42
  • @code_dredd if you click back before disconnecting the signal ... – jean-loup Mar 03 '21 at 10:52
  • What makes you think you'd be fast enough to do such a thing?... – code_dredd Apr 28 '21 at 20:03
  • @code_dredd If you understand what a race condition is, then this is one. – jean-loup May 11 '21 at 13:38
  • @jean-loup If you say so. Go ahead and propose a fix then. I want to see it now. – code_dredd May 11 '21 at 17:49
  • @jean-loup Nothing? – code_dredd May 15 '21 at 23:54
  • @code_dredd apparently if you call blockSignals(true) before and blockSignals(false) after event processing, it will not re-enter. I haven't look into the code. – jean-loup Jul 07 '21 at 12:54
  • @jean-loup Now you say "apparently" and that you "haven't look into the code"?... Look at my code. I'm not calling `blockSignals`. I'm enabling/disabling UI elements. How do your comments even apply? Also, there's [no indication](https://doc.qt.io/qt-5/qobject.html#blockSignals) that this could lead to race conditions. In fact, the [Qt docs](https://doc.qt.io/qt-5/qsignalblocker.html) recognize this is a common practice, saying "`QSignalBlocker` can be used wherever you would otherwise use a **pair of calls to `blockSignals()`**." Also, see https://stackoverflow.com/a/26359323/4594973 – code_dredd Jul 08 '21 at 23:50
  • @code_dredd I haven't look into the blockSignals code so i cannot be sure that it really avoid the race condition that is theoritically possible: Delayed event and double click. Maybe the blocksignals is purging the event queue from double clicks ? Also your original solution with disconnect, which is the one i was answering to, and the setEnabled method, both contain a rc theoritically, and I experienced it in practice as well. Now I see that you mention blockSignals, so you have my upvote! – jean-loup Jul 09 '21 at 09:38
  • @jean-loup *"both contain a rc theoritically"* By your reasoning, there's a "theoretical" race condition between *any* two lines of code, and I don't think I'm going there; it's getting to a silly/unreasonable level of nitpick. The docs don't show any RC warnings for the code in question, I've never had any issues with the above throughout the years, and, other than your anecdotal experience, which may've been buggy code due to something else, I have no objective reason to believe there're any real race conditions in the alternatives I provided in my answer. Still, thanks for the up-vote. – code_dredd Jul 10 '21 at 04:28
1

What I use is blockSignals, which blocks the signaling object from sending signals: it does not however block the receiving object from receiving them, which may be your case.

Using blockSignals is good for when you want to set values to your Ui widgets but you don't want those widgets to then send valueChanged signals. An example of this would be if you have a bunch of widgets that independently all update some calculation- you wouldn't want each of them recalculating, just once when they are all set.

I would not use disconnect/connect here, because you can get into problems where signals are connected twice.

Klathzazt
  • 2,415
  • 19
  • 27