Deleting a parent layout in on_change function (SIGSEGV Error) - python

I have a QComboBox, cbo_box, nested in a frame, self.ui.frameFilterControls, with: cbo_box.currentTextChanged.connect(self.choice_changed). In self.choice_changed(), I call a function which clears the frames layout, including cbo_box, and then regenerates the layout with appropriate controls (including a new cbo_box):
layout = self.ui.frameFilterControls.layout()
if layout is not None:
#clear layout
for i in reversed(range(layout.count())):
layout.itemAt(i).widget().setParent(None)
The problem I am having is that when self.choice_changed() returns, it throws a SIGSEGV error:
Process finished with exit code 139 (interrupted by signal 11: SIGSEGV)
I am guessing because it has no-where to return to, but I am not sure. I am wondering if it is possible to instead of calling the function that deletes and recreates the controls in the on_change of cbo_box if I could schedule it to be evaluated at the next form update, or something along those lines?
EDIT:
I have produced a minimal example which reproduces the behavior.
from PySide6 import QtCore, QtWidgets
from PySide6.QtWidgets import (QApplication, QListWidgetItem, QGridLayout, QVBoxLayout, QLineEdit, QComboBox)
import sys
#from output of designer -> simple main form with a frame for a starting point
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(381, 357)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.verticalLayout = QtWidgets.QVBoxLayout(self.centralwidget)
self.verticalLayout.setObjectName("verticalLayout")
self.frame = QtWidgets.QFrame(self.centralwidget)
self.frame.setFrameShape(QtWidgets.QFrame.StyledPanel)
self.frame.setFrameShadow(QtWidgets.QFrame.Raised)
self.frame.setObjectName("frame")
self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.frame)
self.verticalLayout_2.setObjectName("verticalLayout_2")
self.pushButton = QtWidgets.QPushButton(self.frame)
self.pushButton.setObjectName("pushButton")
self.verticalLayout_2.addWidget(self.pushButton)
self.verticalLayout.addWidget(self.frame)
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 381, 22))
self.menubar.setObjectName("menubar")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.pushButton.setText(_translate("MainWindow", "PushButton"))
class MainGUI(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(MainGUI, self).__init__(parent)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.set_frame_controls()
def set_frame_controls(self):
layout = self.ui.frame.layout()
if layout is not None:
#clear layout
for i in reversed(range(layout.count())):
layout.itemAt(i).widget().setParent(None)
cbo_box = QComboBox()
cbo_box.addItem("test 1")
cbo_box.addItem("test 2")
cbo_box.currentTextChanged.connect(self.choice_changed)
layout.addWidget(cbo_box)
def choice_changed(self):
print("choice_changed")
cbo_box = self.sender()
self.set_frame_controls()
print("end choice changed")
def main():
app = QApplication(sys.argv)
form = MainGUI()
form.show()
app.exit(app.exec_())
if __name__ == '__main__':
main()
The console output of the above program when I run it and switch the combo box from "test 1" to "test 2" is as follows:
choice_changed
end choice changed
Process finished with exit code 139 (interrupted by signal 11: SIGSEGV)
The behavior I would like is for the frame to be regenerated entirely, without the error. Thanks!

If you want to remove a widget from a layout it is not enough to set a null parent since this does not remove it from the internal list that the layout has but only removes the c++ object causing the SIGSEGV. Instead you must use deleteLater() which if it ensures the correct elimination of the elements (for example the internal list of the layout).
def set_frame_controls(self):
layout = self.ui.frame.layout()
if layout is not None:
for i in reversed(range(layout.count())):
widget = layout.itemAt(i).widget()
if widget is not None:
widget.deleteLater()
cbo_box = QComboBox()
cbo_box.addItem("test 1")
cbo_box.addItem("test 2")
cbo_box.currentTextChanged.connect(self.choice_changed)
layout.addWidget(cbo_box)

Related

Python threading - Cannot set parent, new parent is in a different thread

I'm trying to make a GUI that, once you press a button a scrollArea would dynamically get updated. I tried using QThread to not lock the GUI and to show the user the scrollArea as it gets populated with elements.
I made a thread worker class where inside a loop I'm creating the elements for the scrollArea.The elements are put on a QVBoxLayout and I'm trying to pass that layout to my GUI scrollArea using a signal of type QObject.
I'm receiving this error: QObject::setParent: Cannot set parent, new parent is in a different thread
I don't know what to do to solve this
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
class WorkerThread(QThread):
my_signal = pyqtSignal(QObject)
def run(self):
top_layout = QtWidgets.QVBoxLayout()
for i in range(2500):
group_box = QtWidgets.QGroupBox()
group_box.setTitle('box {0}'.format(i))
label = QtWidgets.QLabel()
label.setText('label {0}'.format(i))
layout = QtWidgets.QHBoxLayout(group_box)
layout.addWidget(label)
top_layout.addWidget(group_box)
self.my_signal.emit(top_layout)
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(640, 480)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.pushButton = QtWidgets.QPushButton(self.centralwidget)
self.pushButton.setGeometry(QtCore.QRect(520, 30, 71, 51))
self.pushButton.setObjectName("pushButton")
self.scrollArea = QtWidgets.QScrollArea(self.centralwidget)
self.scrollArea.setGeometry(QtCore.QRect(0, 90, 511, 461))
self.scrollArea.setWidgetResizable(True)
self.scrollArea.setObjectName("scrollArea")
self.scrollAreaWidgetContents = QtWidgets.QWidget()
self.scrollAreaWidgetContents.setGeometry(QtCore.QRect(0, 0, 505, 455))
self.scrollAreaWidgetContents.setObjectName("scrollAreaWidgetContents")
self.scrollArea.setWidget(self.scrollAreaWidgetContents)
MainWindow.setCentralWidget(self.centralwidget)
self.pushButton.clicked.connect(self.click)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.pushButton.setText(_translate("MainWindow", "add"))
def click(self):
self.worker = WorkerThread()
self.worker.start()
self.worker.my_signal.connect(self.update_scrollArea)
def update_scrollArea(self, top_layout):
top_widget = QtWidgets.QWidget()
top_widget.setLayout(top_layout)
self.scrollArea.setWidget(top_widget)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
I've had problems with creating PyQt5 objects in threads in the past, and one option for you would be to use a timer instead.
class Ui_MainWindow(object):
def __init__(self):
self.timer = QtCore.QBasicTimer()
self.timer.start(250, self)
self.i = 0
def timerEvent(self, event):
if self.i < 2500:
self.i+=1
top_layout = QtWidgets.QVBoxLayout()
group_box = QtWidgets.QGroupBox()
group_box.setTitle('box {0}'.format(i))
label = QtWidgets.QLabel()
label.setText('label {0}'.format(i))
layout = QtWidgets.QHBoxLayout(group_box)
layout.addWidget(label)
top_layout.addWidget(group_box)
self.my_signal.emit(top_layout)
def setupUi(self, MainWindow):
...
This way you will interrupt the main loop once every 250msec to update one loop iteration at a time and you will not freeze up the GUI.

How avoid change tab in QTabWidget | PyQt5

I'm trying to implement a tab with the name "+" in which if I click on it opens a ApplicationModal window for configurate the content in that tab. I want to avoid the change of tab when i click in it until press "Accept" in the ApplicationModal window. How can I block this change? I don't know if I explain me.
This it's de code
tabs.py for the MainWindow
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(628, 504)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.tabWidget = QtWidgets.QTabWidget(self.centralwidget)
self.tabWidget.setGeometry(QtCore.QRect(36, 34, 541, 396))
self.tabWidget.setObjectName("tabWidget")
self.tab1 = QtWidgets.QWidget()
self.tab1.setObjectName("tab1")
self.tabWidget.addTab(self.tab1, "")
self.tab2 = QtWidgets.QWidget()
self.tab2.setObjectName("tab2")
self.tabWidget.addTab(self.tab2, "")
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 628, 21))
self.menubar.setObjectName("menubar")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
self.tabWidget.setCurrentIndex(0)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab1), _translate("MainWindow", "Tab 1"))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab2), _translate("MainWindow", "+"))
win0.py for the ApplicationModal window
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_Win0(object):
def setupUi(self, Win0):
Win0.setObjectName("Win0")
Win0.setWindowModality(QtCore.Qt.ApplicationModal)
Win0.resize(255, 203)
self.centralwidget = QtWidgets.QWidget(Win0)
self.centralwidget.setObjectName("centralwidget")
self.comboBox = QtWidgets.QComboBox(self.centralwidget)
self.comboBox.setGeometry(QtCore.QRect(17, 9, 154, 22))
self.comboBox.setObjectName("comboBox")
self.accpet_button = QtWidgets.QPushButton(self.centralwidget)
self.accpet_button.setGeometry(QtCore.QRect(43, 130, 75, 23))
self.accpet_button.setObjectName("accpet_button")
self.cancel_button = QtWidgets.QPushButton(self.centralwidget)
self.cancel_button.setGeometry(QtCore.QRect(135, 131, 75, 23))
self.cancel_button.setObjectName("cancel_button")
Win0.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(Win0)
self.menubar.setGeometry(QtCore.QRect(0, 0, 255, 21))
self.menubar.setObjectName("menubar")
Win0.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(Win0)
self.statusbar.setObjectName("statusbar")
Win0.setStatusBar(self.statusbar)
self.retranslateUi(Win0)
QtCore.QMetaObject.connectSlotsByName(Win0)
def retranslateUi(self, Win0):
_translate = QtCore.QCoreApplication.translate
Win0.setWindowTitle(_translate("Win0", "New Window"))
self.accpet_button.setText(_translate("Win0", "Accept"))
self.cancel_button.setText(_translate("Win0", "Cancel"))
And the main.py for develop the functionality
from tabs import Ui_MainWindow
from win0 import Ui_Win0
from PyQt5 import QtCore, QtGui, QtWidgets
class Win0(Ui_Win0):
win0 = None
def __init__(self, win):
self.win0 = win
super().__init__()
self.setupUi(self.win0)
class Main(Ui_MainWindow):
win0 = None
win1 = None
def __init__(self, win):
self.win = win
super().__init__()
self.setupUi(self.win)
self.tabWidget.tabBarClicked.connect(self.tabClick)
def tabClick(self, event):
if event == 1:
print("New tab")
self.win1 = QtWidgets.QMainWindow()
new_window = Win0(self.win1)
self.win1.show()
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = Main(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
If you want a window that blocks the others until it's closed, you're looking for a modal dialog.
A modal window creates a mode that disables the main window but keeps it visible, with the modal window as a child window in front of it. Users must interact with the modal window before they can return to the parent application. This avoids interrupting the workflow on the main window.
[From the wikipedia article about modal windows]
It is relatively easy to achieve that in Qt, by using a QDialog.
Designer already provides two basic templates with basic Ok/Cancel buttons when you create a new form, so create a new dialog with buttons on right or bottom (this will automatically connect the button box with the dialog's accept/reject slots), add the combobox, save and convert the file with pyuic (in the following example, I exported the file as dialog.py and used the default Ui_Dialog).
Then the implementation is very easy. The important thing is to add the parent argument to the QDialog instance: this ensures that the dialog uses the parent as a reference for the "window modality", so that that parent is blocked until the dialog is closed by accepting or rejecting it (rejection is usually done by clicking on a RejectRole button like Cancel or by closing the dialog in any way, including pressing Esc).
Do note that I changed your approach by using multiple inheritance in order to make things easier (see the guidelines on using Designer about this approach, which is usually the better and most suggested method when using pyuic generated files).
from tabs import Ui_MainWindow
from dialog import Ui_Dialog
from PyQt5 import QtCore, QtGui, QtWidgets
class SelectDialog(QtWidgets.QDialog, Ui_Dialog):
def __init__(self, parent=None):
super().__init__(parent)
self.setupUi(self)
class Main(QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self):
super().__init__()
self.setupUi(self)
self.tabWidget.tabBarClicked.connect(self.tabClick)
def tabClick(self, tab):
if tab == 1:
dialog = SelectDialog(self)
if dialog.exec_():
print(dialog.comboBox.currentIndex())
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
mainWindow = Main()
mainWindow.show()
sys.exit(app.exec_())

Very basic pyqt5 dialog app quits with exit code -1073740791

I tried to design a very basic GUI app that shows the entered height on a dialog, but after I press the ‘OK’ button on the main window, the whole program crashes and the process finishes with this exit code:
Process finished with exit code -1073740791 (0xC0000409)
Here’s the full code for the app, the UI files are below:
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.uic import *
class My_Dialog(QDialog):
def __init__(self):
super(My_Dialog, self).__init__()
loadUi("dialog.ui", self)
self.mid_label.setText(My_Window.mid_label_nexttext)
class My_Window(QMainWindow):
def __init__(self):
super(My_Window, self).__init__()
loadUi("mainwindow.ui", self)
self.mid_label_nexttext = None
self.height_selecter_spinbox.textChanged.connect(lambda x: self.spin_changed(x))
self.pushButton.clicked.connect(self.onMyPushButtonClick)
def onMyPushButtonClick(self):
dlg = My_Dialog()
if dlg.exec_():
print("Success!")
else:
print("Cancel!")
def spin_changed(self, s):
self.mid_label_nexttext = s
self.update()
def main():
app = QApplication(sys.argv)
window = My_Window()
window.show()
app.exec_()
if __name__ == "__main__":
main()
The main window’s UI:
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'mainwindow.ui'
#
# Created by: PyQt5 UI code generator 5.15.0
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(513, 171)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.pushButton = QtWidgets.QPushButton(self.centralwidget)
self.pushButton.setGeometry(QtCore.QRect(310, 80, 75, 23))
self.pushButton.setObjectName("pushButton")
self.layoutWidget = QtWidgets.QWidget(self.centralwidget)
self.layoutWidget.setGeometry(QtCore.QRect(90, 40, 209, 29))
self.layoutWidget.setObjectName("layoutWidget")
self.layout = QtWidgets.QHBoxLayout(self.layoutWidget)
self.layout.setContentsMargins(0, 0, 0, 0)
self.layout.setObjectName("layout")
self.label_firstpart = QtWidgets.QLabel(self.layoutWidget)
font = QtGui.QFont()
font.setPointSize(15)
self.label_firstpart.setFont(font)
self.label_firstpart.setObjectName("label_firstpart")
self.layout.addWidget(self.label_firstpart)
self.height_selecter_spinbox = QtWidgets.QSpinBox(self.layoutWidget)
font = QtGui.QFont()
font.setPointSize(13)
self.height_selecter_spinbox.setFont(font)
self.height_selecter_spinbox.setMinimum(100)
self.height_selecter_spinbox.setMaximum(250)
self.height_selecter_spinbox.setProperty("value", 175)
self.height_selecter_spinbox.setObjectName("height_selecter_spinbox")
self.layout.addWidget(self.height_selecter_spinbox)
self.label_lastpart = QtWidgets.QLabel(self.layoutWidget)
font = QtGui.QFont()
font.setPointSize(15)
self.label_lastpart.setFont(font)
self.label_lastpart.setObjectName("label_lastpart")
self.layout.addWidget(self.label_lastpart)
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 513, 21))
self.menubar.setObjectName("menubar")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.pushButton.setText(_translate("MainWindow", "OK"))
self.label_firstpart.setText(_translate("MainWindow", "My height is "))
self.label_lastpart.setText(_translate("MainWindow", "cm."))
The dialog’s UI:
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'dialog.ui'
#
# Created by: PyQt5 UI code generator 5.15.0
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again. Do not edit this file unless you know what you are doing.
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_Dialog(object):
def setupUi(self, Dialog):
Dialog.setObjectName("Dialog")
Dialog.resize(400, 300)
self.dialog_buttonbox = QtWidgets.QDialogButtonBox(Dialog)
self.dialog_buttonbox.setGeometry(QtCore.QRect(30, 240, 341, 32))
self.dialog_buttonbox.setOrientation(QtCore.Qt.Horizontal)
self.dialog_buttonbox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok)
self.dialog_buttonbox.setObjectName("dialog_buttonbox")
self.widget = QtWidgets.QWidget(Dialog)
self.widget.setGeometry(QtCore.QRect(80, 90, 151, 41))
self.widget.setObjectName("widget")
self.dialog_layout = QtWidgets.QHBoxLayout(self.widget)
self.dialog_layout.setContentsMargins(0, 0, 0, 0)
self.dialog_layout.setObjectName("dialog_layout")
self.left_label = QtWidgets.QLabel(self.widget)
font = QtGui.QFont()
font.setPointSize(12)
self.left_label.setFont(font)
self.left_label.setObjectName("left_label")
self.dialog_layout.addWidget(self.left_label)
self.mid_label = QtWidgets.QLabel(self.widget)
font = QtGui.QFont()
font.setPointSize(12)
self.mid_label.setFont(font)
self.mid_label.setObjectName("mid_label")
self.dialog_layout.addWidget(self.mid_label)
self.right_label = QtWidgets.QLabel(self.widget)
font = QtGui.QFont()
font.setPointSize(12)
self.right_label.setFont(font)
self.right_label.setObjectName("right_label")
self.dialog_layout.addWidget(self.right_label)
self.retranslateUi(Dialog)
self.dialog_buttonbox.accepted.connect(Dialog.accept)
self.dialog_buttonbox.rejected.connect(Dialog.reject)
QtCore.QMetaObject.connectSlotsByName(Dialog)
def retranslateUi(self, Dialog):
_translate = QtCore.QCoreApplication.translate
Dialog.setWindowTitle(_translate("Dialog", "Dialog"))
self.left_label.setText(_translate("Dialog", "You are"))
self.mid_label.setText(_translate("Dialog", "100"))
self.right_label.setText(_translate("Dialog", "cm tall."))
I would appreciate some help on fixing this error, and on why it occured.
You are trying to access an attribute that does not exist:
self.mid_label.setText(My_Window.mid_label_nexttext)
My_Window is a class, while mid_label_nexttext was assigned to the instance of that class (self is always the reference to the current instance).
If you want to set the text for that label from a "parent" window, you either add an extra argument to the __init__ that allows to get the text, or you set it from the main window.
Use the text as init argument
class My_Dialog(QDialog):
def __init__(self, text):
super(My_Dialog, self).__init__()
loadUi("dialog.ui", self)
# ensure that "text" is a valid string, you can't use setText(None)
if text:
self.mid_label.setText(text)
class My_Window(QMainWindow):
# ...
def onMyPushButtonClick(self):
dlg = My_Dialog(self.mid_label_nexttext)
# ...
Set the text from the parent
class My_Dialog(QDialog):
def __init__(self):
super(My_Dialog, self).__init__()
loadUi("dialog.ui", self)
# NO setText() here!!!
class My_Window(QMainWindow):
# ...
def onMyPushButtonClick(self):
dlg = My_Dialog()
if self.mid_label_nexttext:
dlg.mid_label.setText(self.mid_label_nexttext)
# ...
Note that the first method is usually better, mostly for modularity reasons: let's say that you create that dialog from different classes in different situations, whenever you need to change the object name of the label (or the whole interface structure) you can easily change its reference in the dialog subclass, otherwise you'll need to change every reference in your code.
Note: the call to self.update() is useless; if you want to update the label on the dialog whenever the spinbox value is changed, you need to directly access the label (like in the last example) or use signals. Also, you don't need to use lambda if you're using the same argument parameter, just connect to the function.

PyQt5 Designer with Custom Slots

I'm still trying to figure out PyQt and running into another issue that I've been beating my head over for the last several hours. When I use pyuic5 to convert the .ui file into the .py file, part of the output (in the class Ui_MainWindow) connects signals to slots:
self.browseButton.clicked.connect(MainWindow.browseSlot)
self.importButton.clicked.connect(MainWindow.importSlot)
self.lineEdit.returnPressed.connect(MainWindow.returnPressedSlot)
This is called from the main function:
def main():
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = MainWindowUI()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
Above this, in the same file, I have the following code:
class MainWindowUI(Ui_MainWindow):
def __init__(self):
super().__init__()
self.model = Model()
def setupUi(self, mainWindow):
super().setupUi(mainWindow)
def debugPrint(self, msg):
self.textEdit.append(msg)
def refreshAll(self):
self.lineEdit.setText(self.model.getFileName())
self.textEdit.setText(self.model.getFileContents())
def returnPressedSlot(self):
self.debugPrint('Return key pressed')
def importSlot(self):
self.debugPrint('Import button pressed')
def browseSlot(self):
self.debugPrint('Browse button pressed')
The exact error I'm getting is this:
AttributeError: 'QMainWindow' object has no attribute 'browseSlot'
This actually makes perfect sense, because there's no reason QtWidgets.QMainWindow() should know anything about the custom slots I defined in the MainWindowUI class. So it makes sense that it doesn't work, but I'm confused about what I should be doing differently. This is how every tutorial I've seen sets it up, so clearly I have a fundamental misunderstanding somewhere. Any help in clarifying this issue would be appreciated!
Many thanks in advance.
Edited to add the full code of my Ui_MainWindow class:
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'test.ui'
#
# Created by: PyQt5 UI code generator 5.11.3
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(798, 593)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.gridLayout = QtWidgets.QGridLayout(self.centralwidget)
self.gridLayout.setObjectName("gridLayout")
self.tabWidget = QtWidgets.QTabWidget(self.centralwidget)
self.tabWidget.setObjectName("tabWidget")
self.tab = QtWidgets.QWidget()
self.tab.setObjectName("tab")
self.verticalLayout = QtWidgets.QVBoxLayout(self.tab)
self.verticalLayout.setObjectName("verticalLayout")
self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
self.frame_2 = QtWidgets.QFrame(self.tab)
self.frame_2.setFrameShape(QtWidgets.QFrame.StyledPanel)
self.frame_2.setFrameShadow(QtWidgets.QFrame.Raised)
self.frame_2.setObjectName("frame_2")
self.debugTextBrowser = QtWidgets.QTextBrowser(self.frame_2)
self.debugTextBrowser.setGeometry(QtCore.QRect(10, 10, 351, 461))
self.debugTextBrowser.setObjectName("debugTextBrowser")
self.horizontalLayout_2.addWidget(self.frame_2)
self.frame = QtWidgets.QFrame(self.tab)
self.frame.setFrameShape(QtWidgets.QFrame.StyledPanel)
self.frame.setFrameShadow(QtWidgets.QFrame.Raised)
self.frame.setObjectName("frame")
self.browseButton = QtWidgets.QPushButton(self.frame)
self.browseButton.setGeometry(QtCore.QRect(80, 30, 51, 20))
self.browseButton.setObjectName("browseButton")
self.lineEdit = QtWidgets.QLineEdit(self.frame)
self.lineEdit.setGeometry(QtCore.QRect(80, 10, 281, 20))
self.lineEdit.setObjectName("lineEdit")
self.importButton = QtWidgets.QPushButton(self.frame)
self.importButton.setGeometry(QtCore.QRect(310, 30, 51, 20))
self.importButton.setObjectName("importButton")
self.textEdit = QtWidgets.QTextEdit(self.frame)
self.textEdit.setGeometry(QtCore.QRect(80, 130, 281, 81))
self.textEdit.setObjectName("textEdit")
self.horizontalLayout_2.addWidget(self.frame)
self.verticalLayout.addLayout(self.horizontalLayout_2)
self.setupProgressBar = QtWidgets.QProgressBar(self.tab)
self.setupProgressBar.setProperty("value", 0)
self.setupProgressBar.setTextVisible(False)
self.setupProgressBar.setInvertedAppearance(False)
self.setupProgressBar.setTextDirection(QtWidgets.QProgressBar.TopToBottom)
self.setupProgressBar.setObjectName("setupProgressBar")
self.verticalLayout.addWidget(self.setupProgressBar)
self.tabWidget.addTab(self.tab, "")
self.tab_2 = QtWidgets.QWidget()
self.tab_2.setObjectName("tab_2")
self.tabWidget.addTab(self.tab_2, "")
self.tab_3 = QtWidgets.QWidget()
self.tab_3.setObjectName("tab_3")
self.tabWidget.addTab(self.tab_3, "")
self.tab_4 = QtWidgets.QWidget()
self.tab_4.setObjectName("tab_4")
self.tabWidget.addTab(self.tab_4, "")
self.tab_5 = QtWidgets.QWidget()
self.tab_5.setObjectName("tab_5")
self.tabWidget.addTab(self.tab_5, "")
self.gridLayout.addWidget(self.tabWidget, 0, 0, 1, 1)
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 798, 18))
self.menubar.setObjectName("menubar")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
self.tabWidget.setCurrentIndex(0)
self.browseButton.clicked.connect(MainWindow.browseSlot)
self.importButton.clicked.connect(MainWindow.importSlot)
self.lineEdit.returnPressed.connect(MainWindow.returnPressedSlot)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.browseButton.setText(_translate("MainWindow", "Browse"))
self.importButton.setText(_translate("MainWindow", "Import"))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab), _translate("MainWindow", "Setup"))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_2), _translate("MainWindow", "Production Forecast"))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_3), _translate("MainWindow", "Production Forecast"))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_4), _translate("MainWindow", "Page"))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_5), _translate("MainWindow", "Page"))
PyQt recommends in your docs that you should inherit from the appropriate widget, in this case QMainWindow, and use Ui_MainWindow as an interface, it is also recommended that you use the decorator #QtCore.pyqtSlot() since you save resources and avoid having problems with overloaded signals.
class MainWindowUI(QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self):
super().__init__()
self.setupUi(self)
self.model = Model()
def setupUi(self, mainWindow):
super().setupUi(mainWindow)
def debugPrint(self, msg):
self.textEdit.append(msg)
def refreshAll(self):
self.lineEdit.setText(self.model.getFileName())
self.textEdit.setText(self.model.getFileContents())
#QtCore.pyqtSlot()
def returnPressedSlot(self):
self.debugPrint('Return key pressed')
#QtCore.pyqtSlot()
def importSlot(self):
self.debugPrint('Import button pressed')
#QtCore.pyqtSlot()
def browseSlot(self):
self.debugPrint('Browse button pressed')
def main():
app = QtWidgets.QApplication(sys.argv)
w = MainWindowUI()
w.show()
sys.exit(app.exec_())
if __name__ == '__main__': main()
The PyQt 5 Designer generates files with .ui extension. The file contains xml code which can be converted to Python code with the pyuic5 command. The pyuic5 command produces a file with .py extension.
But each time the pyuic5 command is run, it overwrites any changes made to the Python file. To prevent this we need to copy the "main" code in the generated Python file to a separate file and then use this file as our main program file. In this file we can import the Python class generated by the pyuic5 command. We can also add our custom slots in this file
This practice of separating Qt Desginer code from custom code is good Qt programming practice. It has been suggested in Section 3 of the video ebook: "Python GUI Programming Recipes using PyQt5".

AttributeError: 'MyMainWindow' object has no attribute 'pushButton'

Trying to proceed click button event in my code but got some issue.
AttributeError: 'MyMainWindow' object has no attribute 'pushButton'
Seems, like clicked event can`t find pushbutton from my subclass. Probably i did some mistakes in syntax, so really waiting for your help guys. Sorry if the question very banal, pyQt5 is simply new for me, trying to figure out in all of this.
Here is my files.
ui_main.py
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file '1.ui'
#
# Created by: PyQt5 UI code generator 5.7.1
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(349, 131)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.gridLayout = QtWidgets.QGridLayout(self.centralwidget)
self.gridLayout.setObjectName("gridLayout")
self.formLayout = QtWidgets.QFormLayout()
self.formLayout.setObjectName("formLayout")
self.lineEdit = QtWidgets.QLineEdit(self.centralwidget)
self.lineEdit.setAlignment(QtCore.Qt.AlignHCenter|QtCore.Qt.AlignTop)
self.lineEdit.setObjectName("lineEdit")
self.formLayout.setWidget(1, QtWidgets.QFormLayout.SpanningRole, self.lineEdit)
self.label = QtWidgets.QLabel(self.centralwidget)
self.label.setAlignment(QtCore.Qt.AlignCenter)
self.label.setObjectName("label")
self.formLayout.setWidget(0, QtWidgets.QFormLayout.SpanningRole, self.label)
self.gridLayout.addLayout(self.formLayout, 0, 0, 1, 1)
self.horizontalLayout = QtWidgets.QHBoxLayout()
self.horizontalLayout.setObjectName("horizontalLayout")
self.pushButton = QtWidgets.QPushButton(self.centralwidget)
self.pushButton.setObjectName("pushButton")
self.horizontalLayout.addWidget(self.pushButton)
self.pushButton_2 = QtWidgets.QPushButton(self.centralwidget)
self.pushButton_2.setObjectName("pushButton_2")
self.horizontalLayout.addWidget(self.pushButton_2)
self.gridLayout.addLayout(self.horizontalLayout, 1, 0, 1, 1)
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 349, 21))
self.menubar.setObjectName("menubar")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.label.setText(_translate("MainWindow", "TextLabel"))
self.pushButton.setText(_translate("MainWindow", "PushButton"))
self.pushButton_2.setText(_translate("MainWindow", "PushButton"))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
py_main.py
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, qApp
from PyQt5.QtCore import Qt, QEvent, QObject
from ui_main import Ui_MainWindow
class MyMainWindow(QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
super(MyMainWindow, self).__init__(parent)
qApp.installEventFilter(self)
self.pushButton.clicked.connect(self.clickButton()) #here is where the issue is occurs
self.show()
self.setupUi(self)
def eventFilter(self, obj, event):
if event.type() == QEvent.KeyPress:
if event.key() == Qt.Key_Escape:
self.close()
return super(MyMainWindow, self).eventFilter(obj, event)
def clickButton(self):
sender = self.sender()
self.statusbar().showMessage(sender.text() + ' was pressed')
if __name__ == '__main__':
app = QApplication(sys.argv)
win = MyMainWindow()
test = TestClass()
sys.exit(app.exec_())
Waiting forward for your help!
In your code there are 3 errors:
You must call the setupUI function first since the button is created here and then connect that button to the slot.
When the button is connected to the slot you just have to pass the slot name without parenthesis.
Change statusbar() to statusBar().
Code:
# ...
class MyMainWindow(QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
super(MyMainWindow, self).__init__(parent)
qApp.installEventFilter(self)
self.setupUi(self)
self.pushButton.clicked.connect(self.clickButton)
self.show()
# ...
def clickButton(self):
sender = self.sender()
self.statusBar().showMessage(sender.text() + ' was pressed')
# ...
it work for me
call the setupUI function first
self.setupUi(self)
self.pushButton.clicked.connect(self.click_button)
tag QDialog and Ui_Dialog
main.py
MyMainWindow(QDialog, Ui_Dialog):
mainGui.py
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
Dialog = QtWidgets.QDialog()
ui = Ui_Dialog()
ui.setupUi(Dialog)
Dialog.show()
sys.exit(app.exec_())
OUTPUT:
SOLVED: AttributeError Main object has no attribute pushButton

Categories