Search code examples
pythonlayoutpyqtpyqt5qt-designer

PyQt5 Button signal doesnt work in a Layout inside a Widget


I am trying to implement a layout inside a Widget which is then added to the Main Window. If I declare the buttons and the layout inside the Main Window it works without a problem. However, if I create a class that inherits QWidget and add the layout and buttons, the signal is not working. I am adding the widget by using the setLayout method.I tried passing the parent as well to the buttons but it was the same result.

I am not sure what else could I be doing wrong, any orientation or hints on what is the problem are appreciated.

from PyQt5.QtWidgets import QApplication,QMainWindow,QWidget,\
                            QVBoxLayout,\
                            QLineEdit,\
                            QPushButton
from PyQt5 import QtCore
from PyQt5.uic import loadUi
import sys 


class MyWidget(QWidget):
    def __init__(self,parent):
        super(MyWidget, self).__init__()
        self.start=QPushButton("Start",parent)
        self.pause=QPushButton("Pause",parent)
        self.end=QPushButton("End",parent)
        self.line=QLineEdit()
        
        self.start.pressed.connect(self.OnStart)
        self.pause.clicked.connect(self.OnPause)
        self.end.clicked.connect(self.OnEnd)
        
        self.layout=QVBoxLayout()
        self.layout.addWidget(self.start)
        self.layout.addWidget(self.pause)
        self.layout.addWidget(self.end)
        self.layout.addWidget(self.line)
        self.line.textChanged.connect(self.doSomething)

    def doSomething(self):
        print("do")

    def OnStart(self):
        print("start")
    def OnPause(self):
        print("pause")
    def OnEnd(self):
        print("end")

class TheWindow(QMainWindow):
    def __init__(self):
        super(TheWindow, self).__init__()
        self.setWindowFlags(QtCore.Qt.WindowCloseButtonHint | QtCore.Qt.WindowMinimizeButtonHint)
        loadUi('template.ui', self)     
        the_widget=MyWidget(self)
        self.horizontalLayout.addLayout(the_widget.layout)



QApplication.setAttribute(QtCore.Qt.AA_Use96Dpi)
app = QApplication([])
app.aboutToQuit.connect(app.deleteLater)
widget = TheWindow()
widget.show()
sys.exit(app.exec_())

The UI template I made with Qt Designer is located here https://github.com/vChavezB/PyQt5_Button_layout


Solution

  • The problem has to do with the ownership of the variables and the behavior of the signals when the sender or receiver is removed. the_widget is a variable that will be destroyed unless some object takes possession of that variable but none does so it will destroy instantly, and since that object is the receiver of the connection then the connection will also be eliminated. And why are the buttons and layout not destroyed? Well, because the layout is established in another layout, the second layout takes ownership, and since the buttons are managed by the layout, then they will be children of the widget to which the layout was added, and that relationship of kinship makes the parent widget take the ownership of the buttons.

    Note: there is no point in creating a class that inherits from QWidget if it is only going to be used to create other widgets.

    In this case it is better if the layout is established in the same class and then add the widget to the layout:

    class MyWidget(QWidget):
        def __init__(self, parent=None):
            super(MyWidget, self).__init__(parent)
            self.start = QPushButton("Start")
            self.pause = QPushButton("Pause")
            self.end = QPushButton("End")
            self.line = QLineEdit()
    
            self.start.pressed.connect(self.OnStart)
            self.pause.clicked.connect(self.OnPause)
            self.end.clicked.connect(self.OnEnd)
    
            lay = QVBoxLayout(self)
            lay.addWidget(self.start)
            lay.addWidget(self.pause)
            lay.addWidget(self.end)
            lay.addWidget(self.line)
            self.line.textChanged.connect(self.doSomething)
    
        def doSomething(self):
            print("do")
    
        def OnStart(self):
            print("start")
    
        def OnPause(self):
            print("pause")
    
        def OnEnd(self):
            print("end")
    
    
    class TheWindow(QMainWindow):
        def __init__(self):
            super(TheWindow, self).__init__()
            self.setWindowFlags(
                QtCore.Qt.WindowCloseButtonHint | QtCore.Qt.WindowMinimizeButtonHint
            )
            loadUi("template.ui", self)
            the_widget = MyWidget()
            self.horizontalLayout.addWidget(the_widget)