Qt: Dead lock detected – Sender is QPushButton(), receiver is PyQtSlotProxy() – for specific indexes in a QPushButton list

Question:

I am new to pyqt, and I tried to make an application window that contains a list of buttons that are able to toggle a different window. Since I want the number of these buttons to be of a varying quantity, I created a list of QPushButton elements for iterating over them, creating as many as defined by the length of the list, nevertheless I noticed a very weird behavior :

The following code …

import sys
from random import randint
from PyQt5 import QtWidgets

class AnotherWindow(QtWidgets.QWidget):
    """
    This "window" is a QWidget. If it has no parent,
    it will appear as a free-floating window.
    """

    def __init__(self):
        super().__init__()
        layout = QtWidgets.QVBoxLayout()
        self.label = QtWidgets.QLabel("Another Window % d" % randint(0, 100))
        layout.addWidget(self.label)
        self.setLayout(layout)

class MainWindow(QtWidgets.QMainWindow):
    def __init__(self,windows):
        super().__init__()

        self.windows=[]
        self.buttons=[]
        l=QtWidgets.QVBoxLayout()

        for i in range(len(windows)):
            window=AnotherWindow()
            self.windows.append(window)
            button=QtWidgets.QPushButton(f'window {windows[i]}')
            print(i," ",button)
            self.buttons.append(button)
            self.buttons[i].clicked.connect(self.toggle_window,i)
            l.addWidget(self.buttons[i])
            
        w = QtWidgets.QWidget()
        w.setLayout(l)
        self.setCentralWidget(w)
        print(len(self.windows))

    def toggle_window(self,i):
        if self.windows[i].isVisible():
            self.windows[i].hide()
        else:
            self.windows[i].show()

if __name__=="__main__":
    app = QtWidgets.QApplication(sys.argv)
    windows=[0,1,2,3]
    windows=[str(i) for i in windows]
    print(windows)
    w = MainWindow(windows)
    w.show()
    app.exec()

produced the following error but only when the 4rth button (window 3) is pressed.

Qt: Dead lock detected while activating a BlockingQueuedConnection: Sender is QPushButton( ... ), receiver is PyQtSlotProxy( ... )

In effort to validate the code, I tried to narrow the list into a linear declaration of a static number of QPushButton instances, indicating that the issue occurs, only when I try to put the buttons on a list. For instance, the following script does not present any similar unpredictable behavior:

import sys
from random import randint
from PyQt5 import QtWidgets

class AnotherWindow(QtWidgets.QWidget):
    """
    This "window" is a QWidget. If it has no parent,
    it will appear as a free-floating window.
    """

    def __init__(self):
        super().__init__()
        layout = QtWidgets.QVBoxLayout()
        self.label = QtWidgets.QLabel("Another Window % d" % randint(0, 100))
        layout.addWidget(self.label)
        self.setLayout(layout)


class MainWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()
        self.window0 = AnotherWindow()
        self.window1 = AnotherWindow()
        self.window2 = AnotherWindow()
        self.window3 = AnotherWindow()
        
        l = QtWidgets.QVBoxLayout()

        button0 = QtWidgets.QPushButton("window 0")
        button0.clicked.connect(self.toggle_window0)
        l.addWidget(button0)

        button1 = QtWidgets.QPushButton("window 1")
        button1.clicked.connect(self.toggle_window1)
        l.addWidget(button1)

        button2 = QtWidgets.QPushButton("window 2")
        button2.clicked.connect(self.toggle_window2)
        l.addWidget(button2)

        button3 = QtWidgets.QPushButton("window 3")
        button3.clicked.connect(self.toggle_window3)
        l.addWidget(button3)

        w = QtWidgets.QWidget()
        w.setLayout(l)
        self.setCentralWidget(w)

    def toggle_window0(self, checked):
        if self.window0.isVisible():
            self.window0.hide()
        else:
            self.window0.show()

    def toggle_window1(self):
        if self.window1.isVisible():
            self.window1.hide()
        else:
            self.window1.show()

    def toggle_window2(self):
        if self.window2.isVisible():
            self.window2.hide()

        else:
            self.window2.show()

    def toggle_window3(self, checked):
        if self.window3.isVisible():
            self.window3.hide()
        else:
            self.window3.show()

if __name__=="__main__":
    app = QtWidgets.QApplication(sys.argv)
    w = MainWindow()
    w.show()
    app.exec()

To test it further, I extended the list to a list of random lengths (more than 10), where I reassured that the issue persist for specific indexes each time. For example if I create 20 buttons using the first approach, the same bug appears for – the 4rth, the 12fth and the last index exclusively – but not for the rest of them. I even tested it on a different machine. Having also searched in forums, I could not find a solution.

Do I do anything completely wrong here? Does anyone understands better to indicate why is this happening?

I kindly thank you in advance!

Environment: Ubuntu 22.04
Pyqt version : 1.9 (under conda)

Asked By: cenestpamoi

||

Answers:

Your problem is the following:

            self.buttons[i].clicked.connect(self.toggle_window,i)

You are passing i as second argument to connect and expect the toggle_window function to be called with this argument. This is not happening. In toggle_window, i will always be False. See musicamente’s comment regarding what this second argument to connect does.

What you should do instead is connect the button click to a function of your window object. From there, you can of course do a callback to a function of your main window as illustrated below:

import sys
from random import randint
from PyQt5 import QtWidgets

class AnotherWindow(QtWidgets.QWidget):

    def __init__(self, parent, i):
        super().__init__()
        self.parent = parent
        self.i = i
        layout = QtWidgets.QVBoxLayout()
        self.label = QtWidgets.QLabel("Another Window {}".format(i))
        layout.addWidget(self.label)
        self.setLayout(layout)

    def toggle(self):
        print("Toggling windows {}".format(self.i))
        if self.isVisible():
            self.hide()
            self.parent.window_toggled(self.i, False)
        else:
            self.show()
            self.parent.window_toggled(self.i, True)

class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, windows):
        super().__init__()

        self.windows=[]
        self.buttons=[]
        l=QtWidgets.QVBoxLayout()

        for i,title in enumerate(windows):
            window=AnotherWindow(self, i)
            self.windows.append(window)
            button=QtWidgets.QPushButton(title)
            button.clicked.connect(window.toggle)
            l.addWidget(button)
            self.buttons.append(button)

        w = QtWidgets.QWidget()
        w.setLayout(l)
        self.setCentralWidget(w)

    def window_toggled(self, i, visible):
        print("Window {} is now {}".format(i, "visible" if visible else "hidden"))

if __name__=="__main__":
    app = QtWidgets.QApplication(sys.argv)
    windows = ["window {}".format(i) for i in range(12)]
    w = MainWindow(windows)
    w.show()
    app.exec()
Answered By: treuss
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.