Does closing a GUI in PyQt4 destroy its children Qobjects by default - python

I'm trying to make a GUI that used QTimer to create a state machine, but when ever I close the GUI window the timer continues. I think I'm properly making the object that creates my Qtimer a child of the GUI but with the behavior I'm seeing it doesn't seem like it. Here is some code
class Ui_Form(QtGui.QWidget):
def __init__(self):
super(Ui_Form, self).__init__()
self.backEnd = BackEnd(self)
self.backEnd.start()
class BackEnd(QtCore.QObject):
def __init__(self,parent=None):
super(BackEnd,self).__init__(parent)
self.setParent(parent)
self.timer = QtCore.QTimer()
self.timer.setSingleShot(True)
QtCore.QObject.connect(self.timer, QtCore.SIGNAL("timeout()"), self.timerHandler)
def timerHandler(self):
print "Im here"
self.timer.start(1000)
def start(self):
self.timer.start(1000)
def stop(self):
self.timer.stop()
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
Form = QtGui.QWidget()
ui = Ui_Form()
ui.setupUi(Form)
Form.show()
sys.exit(app.exec_())

Timer does not continue when I close GUI window, it works fine, as desired... anyway, try to override close event for your Ui_Form like this:
def closeEvent(self):
self.backEnd.stop()
I hope that helps.
Also, I've changed your main like this:
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
ui = Ui_Form()
ui.show()
sys.exit(app.exec_())
in your case it might be that Form = QtGui.QWidget() stays alive after you close GUI window. So try that modification first.

Related

With PyQt5, implement two windows looping forever automatically

Using PyQt5, I want to implement a two windows displaying one after another automatically, without the user interacting with any window. Something like this:
While True:
Show Window1
wait 2 seconds
Close Window1
Show Window2
wait 2 seconds
Close Window2
The problem I am having is that the main UI thread is stuck in app.exec_() function, so it cannot implement the opening and closing logic.
import sys
from PyQt5.QtWidgets import *
from PyQt5 import uic
class Win1(QMainWindow):
def __init__(self):
super(Win1, self).__init__()
uic.loadUi('win1.ui', self)
self.show()
class Win2(QMainWindow):
def __init__(self):
super(Win2, self).__init__()
uic.loadUi('win2.ui', self)
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
while True:
win = Win1()
time.sleep(1)
win.close()
win = Win2()
time.sleep(1)
win.close()
app.exec_() # <--------- Program blocks here
I would appreciate if someone can share a minimal example for this working without blocking. Or please point to the mechanism that should be used.
If you are going to work with Qt then you should forget about sequential logic but you have to implement the logic using events. For example, in your case you want one window to be shown every time T and another to be hidden, so that can be implemented with a QTimer and a flag:
import sys
from PyQt5.QtCore import QTimer
from PyQt5.QtWidgets import QApplication, QMainWindow
from PyQt5 import uic
class Win1(QMainWindow):
def __init__(self):
super(Win1, self).__init__()
uic.loadUi('win1.ui', self)
self.show()
class Win2(QMainWindow):
def __init__(self):
super(Win2, self).__init__()
uic.loadUi('win2.ui', self)
self.show()
if __name__ == "__main__":
app = QApplication(sys.argv)
timer = QTimer()
timer.setProperty("flag", True)
win1 = Win1()
win2 = Win2()
def on_timeout():
flag = timer.property("flag")
if flag:
win1.show()
win2.close()
else:
win2.show()
win1.close()
timer.setProperty("flag", not flag)
timer.timeout.connect(on_timeout)
timer.start(1 * 1000)
on_timeout()
app.exec_()
You should not use while loop or time.sleep since they block the eventloop in which the GUI lives, that is: they freeze the windows

How to connect functions to PyQt signals outside of main thread

I'm creating a PyQt app where I want to have a background thread that connects some event handlers and then loops forever until the main window is closed. The problem I am experiencing is that the event handlers I am connecting only work if they are functions defined inside my MainWindow class. I have created a minimal repro below:
import threading
from PyQt5.QtWidgets import QApplication, QDialog, QPushButton, QVBoxLayout
class MainWindow(QDialog):
def __init__(self):
super(MainWindow, self).__init__()
self.button1 = QPushButton("Click Me", self)
self.button2 = QPushButton("Me Too!", self)
layout = QVBoxLayout()
layout.addWidget(self.button1)
layout.addWidget(self.button2)
self.setLayout(layout)
def test(self):
print("test inside class")
def test2():
print("test outside class")
def main(window):
window.button1.clicked.connect(window.test)
window.button2.clicked.connect(test2)
# Loop that runs in thread...
app = QApplication([])
window = MainWindow()
window.show()
threading.Thread(target=main, args=[window]).start()
app.exec_()
When I run this code, the first button prints a message to the console as expected, but the second one does nothing when clicked. If I run the main(window) function in the main thread, then both buttons work. I'm aware that in my small sample program this would be the obvious solution, but for reasons that are complex to explain I need to be able to connect the event handlers from the background thread in my application. Why does connecting a function like test2() that is defined outside the MainWindow class not work when I do it outside of the main thread?
I'm still finding out the reason for the problem but the solution is to indicate the type of connection, in this case Qt::DirectConnection that will make the function test2 run on the same thread of the object that emits the signal (the object that emits the signal is the button that lives in the main thread).
import threading
from PyQt5 import QtCore, QtWidgets
class MainWindow(QtWidgets.QDialog):
def __init__(self):
super(MainWindow, self).__init__()
self.button1 = QtWidgets.QPushButton("Click Me")
self.button2 = QtWidgets.QPushButton("Me Too!")
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.button1)
layout.addWidget(self.button2)
#QtCore.pyqtSlot()
def test(self):
print("test inside class")
def test2():
print("test outside class")
def main(window):
window.button1.clicked.connect(window.test)
window.button2.clicked.connect(test2, QtCore.Qt.DirectConnection)
while True:
QtCore.QThread.sleep(1)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
threading.Thread(target=main, args=(window,), daemon=True).start()
sys.exit(app.exec_())

Pyside2 second window(QDialog) closes the main one

import sys
from PySide2.QtCore import QFile
from PySide2.QtWidgets import QApplication, QMainWindow
from PySide2.QtUiTools import QUiLoader
class MyMainWindow(QMainWindow):
def __init__(self):
super().__init__()
loader = QUiLoader()
self.ui = loader.load("mainWindow.ui", self)
self.ui.pushButton_call_dialog.clicked.connect(self.call_dialog)
self.ui.close()
self.ui.show()
def call_dialog(self):
loader = QUiLoader()
self.dialog = loader.load("dialog.ui")
self.dialog.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MyMainWindow()
window.show
sys.exit(app.exec_())
Hi everyone,
any idea why the second (dialog) window closes the entire application?
Of course, it is not a crash since i'm getting a message saying:
Process finished with exit code 0
Thanks for your help
You could handle your QDialog on a separate class, and then make them interact only, the structure might change a bit, but you can see if it's a viable answer:
import sys
from PySide2.QtWidgets import *
class MyWindow(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
button = QPushButton("Dialog")
button.clicked.connect(self.open_dialog)
self.setCentralWidget(button)
def open_dialog(self):
dialog = MyDialog()
dialog.show()
dialog.exec_()
class MyDialog(QDialog):
def __init__(self):
QDialog.__init__(self)
button = QPushButton("Close")
button.clicked.connect(self.close_dialog)
layout = QHBoxLayout()
layout.addWidget(button)
self.setLayout(layout)
def close_dialog(self):
self.close()
if __name__ == "__main__":
app = QApplication()
m = MyWindow()
m.show()
sys.exit(app.exec_())
Just notice that you should include the setUp step on each class.
Hope it helps.
To put the dialog into a separate class didn't work for either.
Every time the Dialog.close() event was called, it closes the whole application.
What worked for me, was to use hide() instead

Properly update restart GUI from another thread PyQT

So i have an process which has its own thread and writes(appends) to an output.txt. Also i have a GUI which reads from this output.txt. I want to update the GUI whenever there is new content in the Output.txt.
right now i do this by quiting and re-exec() the GUI. This works but looks awfull.
class _do_scan(QtCore.QThread):
trigger = QtCore.pyqtSignal()
def __init__(self):
QThread.__init__(self)
def run(self):
self.do_scan()
def do_scan():
#when found smth and written to output.txt
self.trigger.emit()
def main(options = None):
app = QtGui.QApplication(sys.argv)
scan_thread = _do_scan()
scan_thread.start()
scan_thread.trigger.connect(lambda: app.quit())
while scan_thread.isRunning():
window = Window()
window.show()
app.exec_()
window = Window()
window.show()
sys.exit(app.exec_())
Any idea how i can manage it better?
i tried to use
scan_thread.trigger.connect(lambda: window.update())
scan_thread.trigger.connect(lambda: window.repaint())
but none worked
Thanks in advance

RealTime output from a subprogram to stdout of a pyQT Widget

Hi I have seen there are already many questions on this issue however none of them seems to answer my query .
As per below link i even tried winpexpect as i am using windows , however it dosent seems to e working for me .
Getting realtime output from ffmpeg to be used in progress bar (PyQt4, stdout)
I am running a subprogram with subprocess.Popen and want to see the real time result in a pyQt Widget. Currently it shows the result in the pyQt widget but only after the sub command is finished executing . I need to know if there s a way when we can get the output from a subprocess at real time into the window . See the code below which i tried for all this .
import sys
import os
from PyQt4 import QtGui,QtCore
from threading import Thread
import subprocess
#from winpexpect import winspawn
class EmittingStream(QtCore.QObject):
textWritten = QtCore.pyqtSignal(str)
def write(self, text):
self.textWritten.emit(str(text))
class gui(QtGui.QMainWindow):
def __init__(self):
# ...
super(gui, self).__init__()
# Install the custom output stream
sys.stdout = EmittingStream(textWritten=self.normalOutputWritten)
self.initUI()
def normalOutputWritten(self, text):
cursor = self.textEdit.textCursor()
cursor.movePosition(QtGui.QTextCursor.End)
cursor.insertText(text)
self.textEdit.ensureCursorVisible()
def callProgram(self):
command="ping 127.0.0.1"
#winspawn(command)
py=subprocess.Popen(command.split(),stdout=subprocess.PIPE,stderr=subprocess.PIPE,shell=True)
result,_=py.communicate()
for line in result:
print line
print result
def initUI(self):
self.setGeometry(100,100,300,300)
self.show()
self.textEdit=QtGui.QTextEdit(self)
self.textEdit.show()
self.textEdit.setGeometry(20,40,200,200)
print "changing sys.out"
print "hello"
thread = Thread(target = self.callProgram)
thread.start()
#Function Main Start
def main():
app = QtGui.QApplication(sys.argv)
ui=gui()
sys.exit(app.exec_())
#Function Main END
if __name__ == '__main__':
main()
QProcess is very similar to subprocess, but it's much more convenient to use in (Py)Qt code. Because it utilizes signals/slots. Also, it runs the process asynchronously so you don't have use QThread.
I've modified (and cleaned) your code for QProcess:
import sys
from PyQt4 import QtGui,QtCore
class gui(QtGui.QMainWindow):
def __init__(self):
super(gui, self).__init__()
self.initUI()
def dataReady(self):
cursor = self.output.textCursor()
cursor.movePosition(cursor.End)
cursor.insertText(str(self.process.readAll()))
self.output.ensureCursorVisible()
def callProgram(self):
# run the process
# `start` takes the exec and a list of arguments
self.process.start('ping',['127.0.0.1'])
def initUI(self):
# Layout are better for placing widgets
layout = QtGui.QHBoxLayout()
self.runButton = QtGui.QPushButton('Run')
self.runButton.clicked.connect(self.callProgram)
self.output = QtGui.QTextEdit()
layout.addWidget(self.output)
layout.addWidget(self.runButton)
centralWidget = QtGui.QWidget()
centralWidget.setLayout(layout)
self.setCentralWidget(centralWidget)
# QProcess object for external app
self.process = QtCore.QProcess(self)
# QProcess emits `readyRead` when there is data to be read
self.process.readyRead.connect(self.dataReady)
# Just to prevent accidentally running multiple times
# Disable the button when process starts, and enable it when it finishes
self.process.started.connect(lambda: self.runButton.setEnabled(False))
self.process.finished.connect(lambda: self.runButton.setEnabled(True))
#Function Main Start
def main():
app = QtGui.QApplication(sys.argv)
ui=gui()
ui.show()
sys.exit(app.exec_())
#Function Main END
if __name__ == '__main__':
main()
Here is an adaptation of the accepted answer for PyQt6 and PyQt5.
PyQt6:
from PyQt6 import QtCore, QtWidgets
import sys
# On Windows it looks like cp850 is used for my console. We need it to decode the QByteArray correctly.
# Based on https://forum.qt.io/topic/85064/qbytearray-to-string/2
import ctypes
CP_console = f"cp{ctypes.cdll.kernel32.GetConsoleOutputCP()}"
class gui(QtWidgets.QMainWindow):
def __init__(self):
super(gui, self).__init__()
self.initUI()
def dataReady(self):
cursor = self.output.textCursor()
cursor.movePosition(cursor.MoveOperation.End)
# Here we have to decode the QByteArray
cursor.insertText(
str(self.process.readAll().data().decode(CP_console)))
self.output.ensureCursorVisible()
def callProgram(self):
# run the process
# `start` takes the exec and a list of arguments
self.process.start('ping', ['127.0.0.1'])
def initUI(self):
# Layout are better for placing widgets
layout = QtWidgets.QVBoxLayout()
self.runButton = QtWidgets.QPushButton('Run')
self.runButton.clicked.connect(self.callProgram)
self.output = QtWidgets.QTextEdit()
layout.addWidget(self.output)
layout.addWidget(self.runButton)
centralWidget = QtWidgets.QWidget()
centralWidget.setLayout(layout)
self.setCentralWidget(centralWidget)
# QProcess object for external app
self.process = QtCore.QProcess(self)
# QProcess emits `readyRead` when there is data to be read
self.process.readyRead.connect(self.dataReady)
# Just to prevent accidentally running multiple times
# Disable the button when process starts, and enable it when it finishes
self.process.started.connect(lambda: self.runButton.setEnabled(False))
self.process.finished.connect(lambda: self.runButton.setEnabled(True))
# Function Main Start
def main():
app = QtWidgets.QApplication(sys.argv)
ui = gui()
ui.show()
sys.exit(app.exec())
# Function Main END
if __name__ == '__main__':
main()
PyQt5:
from PyQt5 import QtCore, QtWidgets
import sys
# On Windows it looks like cp850 is used for my console. We need it to decode the QByteArray correctly.
# Based on https://forum.qt.io/topic/85064/qbytearray-to-string/2
import ctypes
CP_console = f"cp{ctypes.cdll.kernel32.GetConsoleOutputCP()}"
class gui(QtWidgets.QMainWindow):
def __init__(self):
super(gui, self).__init__()
self.initUI()
def dataReady(self):
cursor = self.output.textCursor()
cursor.movePosition(cursor.End)
# Here we have to decode the QByteArray
cursor.insertText(
str(self.process.readAll().data().decode(CP_console)))
self.output.ensureCursorVisible()
def callProgram(self):
# run the process
# `start` takes the exec and a list of arguments
self.process.start('ping', ['127.0.0.1'])
def initUI(self):
# Layout are better for placing widgets
layout = QtWidgets.QVBoxLayout()
self.runButton = QtWidgets.QPushButton('Run')
self.runButton.clicked.connect(self.callProgram)
self.output = QtWidgets.QTextEdit()
layout.addWidget(self.output)
layout.addWidget(self.runButton)
centralWidget = QtWidgets.QWidget()
centralWidget.setLayout(layout)
self.setCentralWidget(centralWidget)
# QProcess object for external app
self.process = QtCore.QProcess(self)
# QProcess emits `readyRead` when there is data to be read
self.process.readyRead.connect(self.dataReady)
# Just to prevent accidentally running multiple times
# Disable the button when process starts, and enable it when it finishes
self.process.started.connect(lambda: self.runButton.setEnabled(False))
self.process.finished.connect(lambda: self.runButton.setEnabled(True))
# Function Main Start
def main():
app = QtWidgets.QApplication(sys.argv)
ui = gui()
ui.show()
sys.exit(app.exec_())
# Function Main END
if __name__ == '__main__':
main()

Categories