I have the following files (Main window/UI):
files
UI:
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'mainwindow.ui'
#
# Created: Sat Apr 23 15:53:12 2011
# by: PyQt4 UI code generator 4.7.3
#
# WARNING! All changes made in this file will be lost!
from PyQt4 import QtCore, QtGui
class Ui_MainWindow(object):
### Presetting Model ###
model = QtGui.QFileSystemModel()
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(1000, 600)
self.centralWidget = QtGui.QWidget(MainWindow)
self.centralWidget.setObjectName("centralWidget")
self.horizontalLayout_2 = QtGui.QHBoxLayout(self.centralWidget)
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
self.horizontalLayout = QtGui.QHBoxLayout()
self.horizontalLayout.setObjectName("horizontalLayout")
self.treeView = QtGui.QTreeView(self.centralWidget)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(2)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.treeView.sizePolicy().hasHeightForWidth())
self.treeView.setSizePolicy(sizePolicy)
self.treeView.setHeaderHidden(True)
self.treeView.setObjectName("treeView")
self.horizontalLayout.addWidget(self.treeView)
self.plainTextEdit = QtGui.QPlainTextEdit(self.centralWidget)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(4)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.plainTextEdit.sizePolicy().hasHeightForWidth())
self.plainTextEdit.setSizePolicy(sizePolicy)
self.plainTextEdit.setObjectName("plainTextEdit")
self.horizontalLayout.addWidget(self.plainTextEdit)
self.horizontalLayout_2.addLayout(self.horizontalLayout)
MainWindow.setCentralWidget(self.centralWidget)
### MENU ###
self.menuBar = QtGui.QMenuBar(MainWindow)
self.menuBar.setGeometry(QtCore.QRect(0, 0, 600, 27))
self.menuBar.setObjectName("menuBar")
MainWindow.setMenuBar(self.menuBar)
### Setting up tree view model ###
self.treeView.setModel(self.model)
self.treeView.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection)
# self.treeView.setRootIndex(self.model.setRootPath(Xmldocument.directorypath))
### Hiding additional info in treeview ###
hHeader = self.treeView.header()
hHeader.hideSection(1)
hHeader.hideSection(2)
hHeader.hideSection(3)
MainWindow.setWindowTitle(QtGui.QApplication.translate("MainWindow", "VeloCondDB Browser", None, QtGui.QApplication.UnicodeUTF8))
QtCore.QMetaObject.connectSlotsByName(MainWindow)
##############################################################
def __del__(self):
print "DESTRUCTOR"
Main Window:
import sys
import os
from browserclass_UI import Ui_MainWindow
from PyQt4 import QtCore, QtGui
class Browser(QtGui.QMainWindow):
#############################################################################################
def __init__(self, parent=None):
"""Constructor for the main window."""
QtGui.QWidget.__init__(self, parent)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
menuitems = ["Open", "Close"]
menu = self.ui.menuBar.addMenu('File')
for item in menuitems:
entry = menu.addAction(item)
self.connect(entry, QtCore.SIGNAL('triggered()'), lambda item=item: self.doStuff(item))
#############################################################################################
def doStuff(self, item):
print item
#############################################################################################
if __name__ == "__main__":
browser = QtGui.QApplication(sys.argv)
myapp = Browser()
myapp.show()
sys.exit(browser.exec_())
When lines from:
menuitems...
to
self.connect...
are not commented, destructor from UI is never being called. If they are commented everything works fine. Any ideas?
You can't rely on __del__ being called for all of your objects, especially when your program is ending (see that other answer)
According to PyQt documentation:
However, if a slot is a lambda function or a partial function then
its reference count is automatically incremented to prevent it from
being immediately garbage collected.
Some circular references to your Browser object might be created when you connect the signal the lambda functions, and that is what keeps it from being destroyed. As ui is referenced by Browser, it doesn't get destroyed either.
So, you have to disconnect the slots manually when these slots are lambda functions. or use another method than lambda to bind an extra parameters to the slot (e.g. QSignalMapper, or the signal QMenu.triggered that has the QAction as parameter):
def __init__(self, parent):
...
for item in menuitems:
entry = menu.addAction(item)
menu.triggered.connect(self.doStuff)
def doStuff(self, entry):
print entry.text()
Related
I have a GUI made in Designer (pyqt5). A function in my main class needs to work on a separate thread. I also catch the stdout on a QtextEdit LIVE during operations. Everything so far works.
Right now I'm trying to implement a ProgressBar onto my main GUI form. The bar needs to show live progression just like it does on the textEdit.
The example code below works on Linux without any warnings. But on Windows I get the error:
QObject::setParent: Cannot set parent, new parent is in a different thread
I know that this is due to me having a ui element modification within my threaded function. I did my research but all the answers point to using QThreads (just when I started to understand basic threading!). I would prefer a way to update my GUI without having to change the current threading system below.
Here is the example code:
import sys
import threading
import time
from PyQt5.QtWidgets import QApplication, QMainWindow
from PyQt5.QtCore import QObject, pyqtSignal
from PyQt5.QtGui import QTextCursor
from ui_form import Ui_Form
class EmittingStream(QObject):
textWritten = pyqtSignal(str)
def write(self, text):
self.textWritten.emit(str(text))
class Form(QMainWindow):
finished = pyqtSignal()
def __init__(self, parent=None):
super(Form, self).__init__(parent)
# Install the custom output stream
sys.stdout = EmittingStream(textWritten=self.normalOutputWritten)
self.ui = Ui_Form()
self.ui.setupUi(self)
self.ui.pushButton_run.clicked.connect(self.start_task)
self.finished.connect(self.end_task)
def start_task(self):
self.thread = threading.Thread(target=self.run_test)
self.thread.start()
self.ui.pushButton_run.setEnabled(False)
def end_task(self):
self.ui.pushButton_run.setEnabled(True)
def __del__(self):
# Restore sys.stdout
sys.stdout = sys.__stdout__
def normalOutputWritten(self, text):
"""Append text to the QTextEdit."""
cursor = self.ui.textEdit.textCursor()
cursor.movePosition(QTextCursor.End)
cursor.insertText(text)
self.ui.textEdit.setTextCursor(cursor)
self.ui.textEdit.ensureCursorVisible()
def run_test(self):
for i in range(100):
per = i + 1
self.ui.progressBar.setValue(per)
print("%%%s" % per)
time.sleep(0.15) # simulating expensive task
print("Task Completed!")
time.sleep(1.5)
self.ui.progressBar.reset()
self.finished.emit()
def main():
app = QApplication(sys.argv)
form = Form()
form.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
the ui:
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'form.ui'
#
# Created: Mon Apr 30 13:43:19 2018
# by: PyQt5 UI code generator 5.2.1
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_Form(object):
def setupUi(self, Form):
Form.setObjectName("Form")
Form.resize(800, 600)
self.centralwidget = QtWidgets.QWidget(Form)
self.centralwidget.setObjectName("centralwidget")
self.pushButton_run = QtWidgets.QPushButton(self.centralwidget)
self.pushButton_run.setGeometry(QtCore.QRect(40, 20, 311, 191))
self.pushButton_run.setObjectName("pushButton_run")
self.textEdit = QtWidgets.QTextEdit(self.centralwidget)
self.textEdit.setGeometry(QtCore.QRect(40, 230, 721, 241))
self.textEdit.setObjectName("textEdit")
self.progressBar = QtWidgets.QProgressBar(self.centralwidget)
self.progressBar.setGeometry(QtCore.QRect(40, 490, 721, 23))
self.progressBar.setObjectName("progressBar")
Form.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(Form)
self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 25))
self.menubar.setObjectName("menubar")
Form.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(Form)
self.statusbar.setObjectName("statusbar")
Form.setStatusBar(self.statusbar)
self.retranslateUi(Form)
QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form):
_translate = QtCore.QCoreApplication.translate
Form.setWindowTitle(_translate("Form", "MainWindow"))
self.pushButton_run.setText(_translate("Form", "RUN"))
Somehow I need to -instantly- inform the gui thread (from my running thread) that the progress bar value is changing (a process that could take up minutes to complete).
Define a custom signal that sends updates to the progress-bar:
class Form(QMainWindow):
finished = pyqtSignal()
updateProgress = pyqtSignal(int)
def __init__(self, parent=None):
super(Form, self).__init__(parent)
...
self.updateProgress.connect(self.ui.progressBar.setValue)
def run_test(self):
for i in range(100):
per = i + 1
self.updateProgress.emit(per)
...
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_())
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.
I am trying to get stdout and error messages to show on my main window. The window is by pyqt and made via designer. I have a QTextEdit on it. This is where the output should show itself. Also I have a dialog box (again made via designer) where i set some settings for my program before running it. The dialog box is opened like this:
def open_settings(self):
dialog = SettingsDialog()
dialog.open_settings_tab() # its on a tab widget
I already read and used the info on these links to achieve my goal:
Print out python console output to Qtextedit
How to capture output of Python's interpreter and show in a Text widget?
Both are pretty much the same with different object names. The issue I'm having is that whenever I open a dialog box and return to the main window the stdout no longer shows itself on the QTextEdit. Instead it goes back to showing itself on Sublime Editor.
I believe it has something to do with the class instancing.
Here is how the Dialog class starts:
class SettingsDialog(QDialog):
def __init__(self, parent=None):
super(SettingsDialog, self).__init__(parent)
self.ui = Ui_SettingsDialog()
self.ui.setupUi(self)
and finally here is how my main window (form) class starts:
class MyForm(QMainWindow):
def __init__(self, parent=None):
super(MyForm, self).__init__(parent)
# Install the custom output stream
sys.stdout = EmittingStream(textWritten=self.normalOutputWritten)
self.ui = Ui_MyForm()
self.ui.setupUi(self)
Any ideas of why the stdout stops working (in qtextedit) once i go into the dialog screen and come back?
NEW Update:
The code is very long. I made a small program thats showing the issue:
PS: I found that the problem is related with this line shown below:
self.ui.pushButton_path.clicked.connect(Form(self).change_path)
if i comment it out the problem goes away.. But I need to call that function (which opens a QDialog, from the main form). What is the proper way?
main:
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QDialog
from PyQt5.QtCore import QObject, pyqtSignal
from PyQt5.QtGui import QTextCursor
from ui_form import Ui_Form
from ui_dialog import Ui_Dialog
class EmittingStream(QObject): # test
textWritten = pyqtSignal(str)
def write(self, text):
self.textWritten.emit(str(text))
class Form(QMainWindow):
def __init__(self, parent=None):
super(Form, self).__init__(parent)
# Install the custom output stream
sys.stdout = EmittingStream(textWritten=self.normalOutputWritten) # test
self.ui = Ui_Form()
self.ui.setupUi(self)
self.ui.pushButton_open.clicked.connect(self.open_dialog)
self.ui.pushButton_text.clicked.connect(self.test_write)
def __del__(self): # test
# Restore sys.stdout
sys.stdout = sys.__stdout__
def normalOutputWritten(self, text): # test
"""Append text to the QTextEdit."""
# Maybe QTextEdit.append() works as well, but this is how I do it:
# self.ui.tEdit_cli.insertPlainText(text)
cursor = self.ui.textEdit.textCursor()
cursor.movePosition(QTextCursor.End)
cursor.insertText(text)
self.ui.textEdit.setTextCursor(cursor)
self.ui.textEdit.ensureCursorVisible()
def open_dialog(self):
dialog = Dialog()
dialog.open_tab()
def test_write(self):
print("something written")
def change_path(self):
pass
class Dialog(QDialog):
def __init__(self, parent=None):
super(Dialog, self).__init__(parent)
self.ui = Ui_Dialog()
self.ui.setupUi(self)
self.ui.pushButton_close.clicked.connect(self.close_dialog)
self.ui.pushButton_path.clicked.connect(Form(self).change_path) # this is what causes the issue. but i need to use it!
def open_tab(self):
self.ui.tabWidget.setCurrentIndex(0)
self.exec_()
def close_dialog(self):
self.close()
def main():
app = QApplication(sys.argv)
form = Form()
form.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
ui_dialog:
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'dialog.ui'
#
# Created by: PyQt5 UI code generator 5.6
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_Dialog(object):
def setupUi(self, Dialog):
Dialog.setObjectName("Dialog")
Dialog.resize(400, 300)
self.horizontalLayout = QtWidgets.QHBoxLayout(Dialog)
self.horizontalLayout.setObjectName("horizontalLayout")
self.tabWidget = QtWidgets.QTabWidget(Dialog)
self.tabWidget.setObjectName("tabWidget")
self.tab = QtWidgets.QWidget()
self.tab.setObjectName("tab")
self.pushButton_close = QtWidgets.QPushButton(self.tab)
self.pushButton_close.setGeometry(QtCore.QRect(100, 80, 211, 131))
self.pushButton_close.setObjectName("pushButton_close")
self.pushButton_path = QtWidgets.QPushButton(self.tab)
self.pushButton_path.setGeometry(QtCore.QRect(30, 30, 75, 23))
self.pushButton_path.setObjectName("pushButton_path")
self.tabWidget.addTab(self.tab, "")
self.tab_2 = QtWidgets.QWidget()
self.tab_2.setObjectName("tab_2")
self.tabWidget.addTab(self.tab_2, "")
self.horizontalLayout.addWidget(self.tabWidget)
self.retranslateUi(Dialog)
QtCore.QMetaObject.connectSlotsByName(Dialog)
def retranslateUi(self, Dialog):
_translate = QtCore.QCoreApplication.translate
Dialog.setWindowTitle(_translate("Dialog", "Dialog"))
self.pushButton_close.setText(_translate("Dialog", "close"))
self.pushButton_path.setText(_translate("Dialog", "path"))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab), _translate("Dialog", "Tab 1"))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_2), _translate("Dialog", "Tab 2"))
"""
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_())
"""
ui_form:
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'form.ui'
#
# Created by: PyQt5 UI code generator 5.6
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_Form(object):
def setupUi(self, Form):
Form.setObjectName("Form")
Form.resize(800, 600)
self.centralwidget = QtWidgets.QWidget(Form)
self.centralwidget.setObjectName("centralwidget")
self.textEdit = QtWidgets.QTextEdit(self.centralwidget)
self.textEdit.setGeometry(QtCore.QRect(90, 230, 601, 271))
self.textEdit.setObjectName("textEdit")
self.pushButton_open = QtWidgets.QPushButton(self.centralwidget)
self.pushButton_open.setGeometry(QtCore.QRect(140, 80, 241, 81))
self.pushButton_open.setObjectName("pushButton_open")
self.pushButton_text = QtWidgets.QPushButton(self.centralwidget)
self.pushButton_text.setGeometry(QtCore.QRect(440, 80, 251, 81))
self.pushButton_text.setObjectName("pushButton_text")
Form.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(Form)
self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 21))
self.menubar.setObjectName("menubar")
Form.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(Form)
self.statusbar.setObjectName("statusbar")
Form.setStatusBar(self.statusbar)
self.retranslateUi(Form)
QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form):
_translate = QtCore.QCoreApplication.translate
Form.setWindowTitle(_translate("Form", "MainWindow"))
self.pushButton_open.setText(_translate("Form", "open dialog"))
self.pushButton_text.setText(_translate("Form", "write somthing"))
"""
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
Form = QtWidgets.QMainWindow()
ui = Ui_Form()
ui.setupUi(Form)
Form.show()
sys.exit(app.exec_())
"""
You must use the object to invoke a method, you must not use the class, so the instruction Form(self) is not valid.
You must make the connection where you can access the signal and the slot simultaneously, for example open_dialog would be a good place:
class Form(QMainWindow):
...
def open_dialog(self):
dialog = Dialog(self)
dialog.ui.pushButton_path.clicked.connect(self.change_path) # +++
dialog.open_tab()
def test_write(self):
print("something written")
def change_path(self):
pass
class Dialog(QDialog):
def __init__(self, parent=None):
super(Dialog, self).__init__(parent)
self.ui = Ui_Dialog()
self.ui.setupUi(self)
self.ui.pushButton_close.clicked.connect(self.close_dialog)
# self.ui.pushButton_path.clicked.connect(Form(self).change_path) ---
...
I have class Ui_MainWindow(object) that creates a window with a progress bar and class OtherClass(object) that contains method in which the local int variable increments in cycle.
How to connect local variable value change to progres bar value change?
mainGUI.py
import sys
from PyQt4.uic.Compiler.qtproxies import QtGui
from PyQt4 import QtGui
from Ui_MainWindow import Ui_MainWindow
def main():
app = QtGui.QApplication(sys.argv)
MainWindow = QtGui.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Ui_MainWindow.py
from PyQt4 import QtCore, QtGui
from MainGui.OtherClass import OtherClass
try:
_fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
_fromUtf8 = lambda s: s
class Ui_MainWindow(object):
def myButtonSlot(self):
objVar=OtherClass()
objVar.method()
def setupUi(self, MainWindow):
MainWindow.setObjectName(_fromUtf8("MainWindow"))
MainWindow.resize(389, 332)
self.centralwidget = QtGui.QWidget(MainWindow)
self.centralwidget.setObjectName(_fromUtf8("centralwidget"))
self.verticalLayout = QtGui.QVBoxLayout(self.centralwidget)
self.verticalLayout.setObjectName(_fromUtf8("verticalLayout"))
self.pushButton = QtGui.QPushButton(self.centralwidget)
self.pushButton.setObjectName(_fromUtf8("pushButton"))
self.pushButton.clicked.connect(self.myButtonSlot)
self.verticalLayout.addWidget(self.pushButton)
self.progressBar = QtGui.QProgressBar(self.centralwidget)
self.progressBar.setProperty("value", 24)
self.progressBar.setObjectName(_fromUtf8("progressBar"))
self.verticalLayout.addWidget(self.progressBar)
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtGui.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 389, 21))
self.menubar.setObjectName(_fromUtf8("menubar"))
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtGui.QStatusBar(MainWindow)
self.statusbar.setObjectName(_fromUtf8("statusbar"))
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
MainWindow.setWindowTitle(QtGui.QApplication.translate("MainWindow", "MainWindow", None, QtGui.QApplication.UnicodeUTF8))
self.pushButton.setText(QtGui.QApplication.translate("MainWindow", "PushButton", None, QtGui.QApplication.UnicodeUTF8))
OtherClass.py
class OtherClass(object):
def method(self):
for i in range(100): # i want to connect variable i to progress bar value
print i
for j in range(100500):
pass
You need to re-organize your code a little bit.
Firstly, you should never edit the code in the UI module generated by pyuic. Instead, import it into your main module and implement all your application logic there.
Secondly, you should create a main-window class in your main module, and do all the setup inside its __init__ method.
One way to solve your problem of connecting the loop variable to the progress bar, is to make OtherClass a subclass of QObject and emit a custom signal:
from PyQt4 import QtCore
class OtherClass(QtCore.QObject):
valueUpdated = QtCore.pyqtSignal(int)
def method(self):
# i want to connect variable i to progress bar value
for i in range(100):
print i
self.valueUpdated.emit(i)
for j in range(100500):
pass
With that in place, you would then move the setup for pushButton and its slot to "mainGUI.py", and re-generate "Ui_MainWindow.py" with pyuic. A slot would then be added to handle the custom valueChanged signal, which would update the progress bar and also process any pending GUI events.
So "mainGUI.py" would end up looking something like this:
import sys
from PyQt4 import QtGui
from Ui_MainWindow import Ui_MainWindow
from OtherClass import OtherClass
class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
self.setupUi(self)
self.pushButton.clicked.connect(self.myButtonSlot)
self.otherclass = OtherClass(self)
self.otherclass.valueUpdated.connect(self.handleValueUpdated)
def myButtonSlot(self):
self.otherclass.method()
def handleValueUpdated(self, value):
self.progressBar.setValue(value)
QtGui.qApp.processEvents()
def main():
app = QtGui.QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
The following post has a version that increments the progress bar 10% each time you press the button. And a version which uses a timer to increment the progress bar. (I'm just in the process of learning this myself)
In Qt Designer, add a progress bar and a button. Click on 'Edit Signals/Slots', drag a line from the button to somewhere in the window and when the button is 'pressed()' add a slot(or signal??) called 'button_pressed()' (use the + button to make this). When you have done this, the OK button is greyed out - select the slot you made, and press OK.
Save the file as ui_MainWindow.ui (note the capitals carefully).
Convert to a py file using the batch file >
pyuic4 -x ui_MainWindow.ui -o ui_MainWindow.py
This file should look something like....(you don't need to edit this).
from PyQt4 import QtCore, QtGui
try:
_fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
def _fromUtf8(s):
return s
try:
_encoding = QtGui.QApplication.UnicodeUTF8
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig)
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName(_fromUtf8("MainWindow"))
MainWindow.resize(800, 600)
self.centralwidget = QtGui.QWidget(MainWindow)
self.centralwidget.setObjectName(_fromUtf8("centralwidget"))
self.progressBar = QtGui.QProgressBar(self.centralwidget)
self.progressBar.setGeometry(QtCore.QRect(110, 90, 118, 23))
self.progressBar.setProperty("value", 24)
self.progressBar.setObjectName(_fromUtf8("progressBar"))
self.pushButton = QtGui.QPushButton(self.centralwidget)
self.pushButton.setGeometry(QtCore.QRect(120, 200, 75, 23))
self.pushButton.setObjectName(_fromUtf8("pushButton"))
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtGui.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 21))
self.menubar.setObjectName(_fromUtf8("menubar"))
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtGui.QStatusBar(MainWindow)
self.statusbar.setObjectName(_fromUtf8("statusbar"))
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
QtCore.QObject.connect(self.pushButton, QtCore.SIGNAL(_fromUtf8("pressed()")), MainWindow.button_pressed)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow", None))
self.pushButton.setText(_translate("MainWindow", "PushButton", None))
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
MainWindow = QtGui.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
Create a 'program.py' file. This is the file you will run...
import sys
from PyQt4 import QtGui
#from PyQt5 import QtCore, QtGui, QtWidgets #works for pyqt5
from mainWindow import MainWindow
def main():
#app = QtWidgets.QApplication (sys.argv) #works for pyqt5
app = QtGui.QApplication (sys.argv) #works for pyqt4
m = MainWindow ()
m.show ()
sys.exit (app.exec_ () )
if __name__ == '__main__':
main ()
Now this is where the good stuff happens when you subclass the mainwindow. Call this file 'mainWindow.py'. Careful with the capitalizations.
from PyQt4 import QtCore, QtGui
from ui_MainWindow import Ui_MainWindow #note the capitalization
class MainWindow (QtGui.QMainWindow):
def __init__ (self, parent = None):
super (MainWindow, self).__init__ ()
self.ui = Ui_MainWindow ()
self.ui.setupUi (self)
#------------do your custom stuff from here on-----------
self.progress = 0 #Start value of progress bar
self.ui.progressBar.setValue(self.progress)
def button_pressed(self):
print('button pressed')
self.ui.statusbar.showMessage(str(self.progress)) #this is at bottom left of window. Discovered this accidentially when doing this!
self.ui.progressBar.setValue(self.progress)
self.progress+=10
There is a good tutorial here which I used to create an alternate 'mainWindow.py' which uses a timer to increment the progress bar. It does not block the code with a loop using sleep or by doing a CPU intensive loop. I don't understand multithreading, multi-processor options yet to comment on using these.
#from PyQt5 import QtCore, QtGui, QtWidgets #works for PyQt5
from PyQt4 import QtCore, QtGui
from ui_MainWindow import Ui_MainWindow #note the capitalization
class MainWindow (QtGui.QMainWindow):
def __init__ (self, parent = None):
super (MainWindow, self).__init__ ()
self.ui = Ui_MainWindow () #same name as appears in mainWindowUi.py
self.ui.setupUi (self)
self.progress = 0 #Start value of progress bar
self.ui.progressBar.setValue(self.progress)
self.timer = QtCore.QBasicTimer()
def button_pressed(self):
self.timerEvent(64) #this needs an argument to work but I'm not sure what is is yet so I just put in some random number
def timerEvent(self, e):
self.ui.progressBar.setValue(self.progress)
if self.progress >=100:
self.timer.stop()
else:
if self.timer.isActive():
pass
else:
self.timer.start(10,self) #10 milliseconds
self.progress+=1
You have to use a signal and slot...and multiprocessing or multithreading.
There's a good example here that specifically takes you through the progress bar:
ZetCode Progress Bar
Also, question has been answered here before:
SO Progress Bar