PyQt application close with error - python

I have some trouble with creating window application using PyQt. For example: we have the following class for GUI:
class SampleMainWindow(QtGui.QMainWindow):
def __init__(self, parent=None):
...
def hideEvent(self, event):
...
def showEvent(self, event):
...
def activate(self):
...
def closeEvent(self, event):
...
What if we want to use it just for showing results and acept user inputs, and for all control logic we have another class, which use the instance of SampleMainWindow as a field:
class Programm:
def __init__(self):
self.window = SampleMainWindow()
....
app = QtGui.QApplication(sys.argv)
prog = Programm()
prog.window.show()
app.exec_()
So, if we create the instance of Programm, the application will run well, but on close we'll get an error that python.exe process could not be completed normally. But if the instance of SampleMainWindow is not a field of class:
class Programm:
def __init__(self):
win = SampleMainWindow()
....
application close well. What's the reason?
Here are the full version of Gui class:
class SampleMainWindow(QtGui.QMainWindow):
def initTray(self):
self.icon = QtGui.QSystemTrayIcon()
self.icon.setIcon(QtGui.QIcon('Tray.png'))
self.icon.show()
self.icon.activated.connect(self.activate)
def __init__(self, parent=None):
QtGui.QMainWindow.__init__(self)
moduleglobalconstants.APP_RUNNING = True
self.initTray()
self.newSession = True
self.setGeometry(300, 300, 600, 400)
self.setWindowTitle('Eye: Recently Added Lines')
self.statusBar().showMessage('Ready')
exitAction = QtGui.QAction(QtGui.QIcon('Exit.png')
,'Exit'
,self)
exitAction.setShortcut('Ctrl+Q')
exitAction.setStatusTip('Exit application')
self.connect(exitAction
,QtCore.SIGNAL('triggered()')
,QtCore.SLOT('close()'))
menubar = self.menuBar()
fileMenu = menubar.addMenu('&File')
fileMenu.addAction(exitAction)
self.toolbar = self.addToolBar('Exit')
self.toolbar.addAction(exitAction)
self.textEdit = QtGui.QTextEdit()
self.scrollPosition = self.textEdit.textCursor()
self.textEdit.setReadOnly(True)
self.setCentralWidget(self.textEdit)
def hideEvent(self, event):
self.hidden = True
self.setWindowFlags(QtCore.Qt.ToolTip)
self.setVisible(False)
def showEvent(self, event):
self.hidden = False
self.setVisible(True)
def activate(self):
self.setWindowFlags(QtCore.Qt.Window)
self.show()
self.setWindowState(self.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive)
self.activateWindow()
def questionDialog(self, caption = 'Message',text = "Are you sure?"):
self.activate()
msgBox = QtGui.QMessageBox(self)
msgBox.setWindowTitle("Eye")
msgBox.setText(caption);
msgBox.setInformativeText(text);
msgBox.setStandardButtons(QtGui.QMessageBox.Yes| QtGui.QMessageBox.No);
msgBox.setDefaultButton(QtGui.QMessageBox.Yes);
return msgBox.exec_()
def criticalDialog(self,app, caption = 'Eye: Critical Error',text = "Impossible to continue working!"):
self.activate()
msgBox = QtGui.QMessageBox.critical(self,caption,text)
moduleglobalconstants.APP_RUNNING = False
def closeEvent(self, event):
reply = self.questionDialog()
if reply == QtGui.QMessageBox.Yes:
moduleglobalconstants.APP_RUNNING = False
event.accept()
else:
event.ignore()
def showNewLines(self, singleLine = None, lines = None, mode='insert'):
if (mode == 'rewrite'):
self.textEdit.clear()
if lines:
for line in lines:
self.textEdit.insertPlainText(line.strip() + "\n")
elif singleLine:
self.textEdit.insertPlainText(singleLine.strip()+"\n")

This has to do with python's garbage collecting - as (almost) soon an object isn't referenced anymore it destroys it, so I guess the process doesn't terminate as long as the window wasn't garbage-collected.
You can try to add at the end del Programm.window or something like that (there's probably a better way of doing this, but I never programmed QT myself so I can't help you here)

see this:
from PyQt4.QtCore import *
from PyQt4.QtGui import *
import sys
class MyWindow(QWidget):
def __init__(self, parent=None):
QWidget.__init__(self, parent)
self.label = QLabel("Hi, I'm a window", self)
self.button = QPushButton("&Quit", self)
self.connect(self.button, SIGNAL("clicked()"), QCoreApplication.instance(), SLOT("quit()"))
lay = QVBoxLayout()
lay.addWidget(self.label)
lay.addWidget(self.button)
self.setLayout(lay)
def closeEvent(self, event):
print "Closing the app"
self.deleteLater()
if __name__ == '__main__':
app = QApplication(sys.argv)
mw = MyWindow()
mw.show()
sys.exit(app.exec_())
the solution is to implement closeEvent method

Related

Move focus to another widget when the return or right arrow key is pressed?

I have a Qt widget that looks like this:
class launchiiwidget(QtWidgets.QWidget):
def __init__(self):
super().__init__()
layout = QtWidgets.QVBoxLayout(self)
self.textbox = QtWidgets.QTextEdit(self)
self.textbox.setLineWrapMode(QtWidgets.QTextEdit.NoWrap)
self.textbox.setAlignment(QtCore.Qt.AlignCenter)
self.textbox.setFixedSize(QtCore.QSize(600, 100))
self.textbox.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.textbox.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
layout.addWidget(self.textbox)
font = self.textbox.font()
font.setPointSize(80)
self.textbox.setFont(font)
self.listwidget = QtWidgets.QListWidget(self)
self.listwidget.addItem("Red")
self.listwidget.addItem("Blue")
layout.addWidget(self.listwidget)
if __name__ == "__main__":
app = QtWidgets.QApplication([])
widget = launchiiwidget()
widget.setWindowFlags(QtCore.Qt.FramelessWindowHint)
widget.resize(600, 200)
widget.show()
sys.exit(app.exec())
How can I make it so when the "return" or "right arrow key" is pressed, focus moves from wherever it is currently to the first item in listwidget? This should also work while being focused inside of textbox, without triggering a newline.
Note: items get dynamically added to listwidget.
A possible solution could be to use QShorcut but because the OP requires "without triggering a newline". So in this case the solution is to implement an eventfilter to the QWindow:
import sys
from PyQt6 import QtCore, QtGui, QtWidgets
class KeyHelper(QtCore.QObject):
pressed = QtCore.pyqtSignal()
def __init__(self, window):
super().__init__(window)
self._window = window
self.window.installEventFilter(self)
#property
def window(self):
return self._window
def eventFilter(self, obj, event):
if event.type() == QtCore.QEvent.Type.KeyPress:
if event.key() in (
QtCore.Qt.Key.Key_Return,
QtCore.Qt.Key.Key_Enter,
QtCore.Qt.Key.Key_Right,
):
self.pressed.emit()
return True
return super().eventFilter(obj, event)
class Launchiiwidget(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.textbox = QtWidgets.QTextEdit()
self.textbox.setLineWrapMode(QtWidgets.QTextEdit.LineWrapMode.NoWrap)
self.textbox.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
self.textbox.setFixedSize(QtCore.QSize(600, 100))
self.textbox.setVerticalScrollBarPolicy(
QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOff
)
self.textbox.setHorizontalScrollBarPolicy(
QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOff
)
font = self.textbox.font()
font.setPointSize(80)
self.textbox.setFont(font)
self.listwidget = QtWidgets.QListWidget()
self.listwidget.addItem("Red")
self.listwidget.addItem("Blue")
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.textbox)
layout.addWidget(self.listwidget)
def update_focus(self):
self.listwidget.setFocus()
index = self.listwidget.model().index(0, 0)
if index.isValid():
self.listwidget.setCurrentIndex(index)
if __name__ == "__main__":
app = QtWidgets.QApplication([])
widget = Launchiiwidget()
widget.setWindowFlags(QtCore.Qt.WindowType.FramelessWindowHint)
widget.resize(600, 200)
widget.show()
key_helper = KeyHelper(widget.windowHandle())
key_helper.pressed.connect(widget.update_focus)
sys.exit(app.exec())

How to update QWidget's content?

There is a QWidget, which is called with the button "Start" in the main widget.
It shows some text, which should be updated every minute during the work of the QWidget.
How can I make this infinity updating within this code?
class ExampleWidget(QWidget):
def __init__(self, parent=None):
super().__init__()
self.setWindowTitle('Example Widget ScrollArea')
self.initUi()
def initUi(self):
area = QScrollArea(self)
area.setWidgetResizable(True)
self.scrollAreaWidgetContents = QLabel(some_text, self)
area.setWidget(self.scrollAreaWidgetContents)
button = QPushButton("Close")
button.clicked.connect(self.goMainWindow)
layoutV = QVBoxLayout()
layoutV.addWidget(area)
layoutV.addWidget(button)
self.setLayout(layoutV)
def goMainWindow(self):
self.hide()
def sizeHint(self):
return QSize(400, 200)
class MainWindow(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
start_main_button = QPushButton('Start', self)
start_main_button.move(40, 40)
start_main_button.clicked.connect(self.start)
self.setGeometry(300, 300, 290, 150)
self.setWindowTitle('Test')
def start(self):
global some_text
some_text = 'some text'
self.result_widget = ExampleWidget()
self.result_widget.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
One of my tries:
def start(self):
global some_text
some_text = 'some text'
self.result_widget = ExampleWidget()
self.result_widget.show()
i = 0
while True:
i+=1
some_text+=str(i)
self.result_widget = ExampleWidget()
self.result_widget.show()
Forget the global variables because they are considered bad practice, on the other hand in a GUI you should avoid having loops that consume a lot of time, and in your case a while True blocks the GUI. In Qt if you want to do periodic tasks you must use a QTimer:
from PyQt5 import QtCore, QtWidgets
class ExampleWidget(QtWidgets.QWidget):
def __init__(self, parent=None):
super().__init__()
self.setWindowTitle('Example Widget ScrollArea')
self.initUi()
def initUi(self):
area = QtWidgets.QScrollArea()
area.setWidgetResizable(True)
self.scrollAreaWidgetContents = QtWidgets.QLabel("some_text")
area.setWidget(self.scrollAreaWidgetContents)
button = QtWidgets.QPushButton("Close")
button.clicked.connect(self.hide)
layoutV = QtWidgets.QVBoxLayout(self)
layoutV.addWidget(area)
layoutV.addWidget(button)
def update_text(self, text):
# update the text
self.scrollAreaWidgetContents.setText(text)
def sizeHint(self):
return QtCore.QSize(400, 200)
class MainWindow(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
start_main_button = QtWidgets.QPushButton('Start', self)
start_main_button.move(40, 40)
start_main_button.clicked.connect(self.start)
self.setGeometry(300, 300, 290, 150)
self.setWindowTitle('Test')
self.result_widget = ExampleWidget()
self.timer = QtCore.QTimer(self, interval=60*1000)
self.timer.timeout.connect(self.on_timeout)
self.counter = 0
self.initial_text = "some_text"
def on_timeout(self):
# this method will be called every 60 * 1000 ms
self.initial_text += str(self.counter)
self.result_widget.update_text(self.initial_text)
self.counter += 1
#QtCore.pyqtSlot()
def start(self):
self.result_widget.show()
# start timer
self.timer.start()
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())

Creating loading display in PySide with threading

I am trying to display a loading graphic(for now just a label) at the beginning of my pyside program while another function is running. After it's done it should continue and load the main GUI. I have this so far
from PySide import QtCore
from PySide import QtGui
class DoStuff:
def __init__(self):
pass
def ReturnInformation(self):
time.sleep(20) #Sleep to simulate processing time
return "information"
class Main(QtGui.QWidget):
def __init__(self):
super(Main, self).__init__()
self.initQ = queue.Queue()
self.initthread = threading.Thread(target=self.InitThread)
self.initthread.daemon = True
self.initthread.start()
self.setStyleSheet("background-color: black;")
self.setCursor(QtCore.Qt.BlankCursor)
self.loaddisplay = QtGui.QLabel(self)
self.loaddisplay.move(20, 20)
self.loaddisplay.setText("Loading...")
self.show()
self.initthread.join()
self.MainDisplay()
self.show()
def InitThread(self):
self.dostuff = DoStuff()
def MainDisplay(self):
self.display = QtGui.QLabel(self)
self.display.setStyleSheet("font: 70pt Helvetica; color: white;")
self.display.move(20, 20)
self.display.setText(self.dostuff.ReturnInformation())
self.manager = QtCore.QTimer(self)
self.manager.timeout.connect(self.Update)
self.manager.start(100000)
def Update(self): #Update the information once in a while
self.timedisplay.setText(self.dostuff.ReturnInformation())
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
GUI = Main()
sys.exit(app.exec_())
The problem is that only the load graphic is displayed and the GUI from MainDisplay() is never displayed. I'm pretty sure this has something to do with how I'm calling the show()function. Is that the problem, and how do I fix it?
Also, how would I delete the loading label once it has finished loading?
P.S. (I asked this question before, but it got no answers or comments and low views so I deleted it and am asking the question again)
Although python provides several ways to execute task through threads these do not necessarily conform to the rules of Qt, it is appropriate to use the tools of the framework such as QThread:
class DoStuffThread(QtCore.QThread):
displaySignal = QtCore.Signal(str)
timeSignal = QtCore.Signal(str)
def __init__(self, *args, **kwargs):
QtCore.QThread.__init__(self, *args, **kwargs)
self.timer = QtCore.QTimer()
self.timer.moveToThread(self)
self.timer.timeout.connect(self.onTimeout)
self.stuff = DoStuff()
def onTimeout(self):
data = self.stuff.ReturnInformation()
self.timeSignal.emit(data)
def run(self):
data = self.stuff.ReturnInformation()
self.displaySignal.emit(data)
self.timer.start(20000)
loop = QtCore.QEventLoop()
loop.exec_()
class DoStuff:
def ReturnInformation(self):
time.sleep(2) # Sleep to simulate processing time
return "information-{}".format(QtCore.QTime.currentTime().toString("hh:mm:ss"))
class Main(QtGui.QWidget):
def __init__(self):
super(Main, self).__init__()
self.setStyleSheet("background-color: black;")
self.setCursor(QtCore.Qt.BlankCursor)
self.setLayout(QtGui.QVBoxLayout())
self.loaddisplay = QtGui.QLabel(self)
self.display = QtGui.QLabel(self)
self.timedisplay = QtGui.QLabel(self)
self.layout().addWidget(self.loaddisplay)
self.layout().addWidget(self.display)
self.layout().addWidget(self.timedisplay)
self.thread = DoStuffThread(self)
self.thread.displaySignal.connect(self.display.setText, QtCore.Qt.QueuedConnection)
self.thread.timeSignal.connect(self.timedisplay.setText, QtCore.Qt.QueuedConnection)
self.thread.start()
self.loaddisplay.move(20, 20)
self.loaddisplay.setText("Loading...")
self.display.setStyleSheet("font: 70pt Helvetica; color: white;")
self.display.move(20, 20)
def closeEvent(self, event):
self.thread.quit()
QtGui.QWidget.closeEvent(self, event)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
GUI = Main()
GUI.show()
sys.exit(app.exec_())

Deleting widgets from pyqt

class Window(QtGui.QMainWindow):
def __init__(self):
super(Window, self).__init__()
self.tabs()
def home(self):
df = QtGui.QPushButton('hello', self)
df.show()
def series(self):
df = QtGui.QCheckBox('hello', self)
df.show()
def tabs(self):
btn_home = QtGui.QPushButton(QtGui.QIcon('home.png'), 'Home', self)
btn_home.clicked.connect(self.home)
btn_series = QtGui.QPushButton(QtGui.QIcon('series.png'),'Series', self)
btn_series.clicked.connect(self.series)
self.show()
def run():
app = QtGui.QApplication(sys.argv)
GUI = Window()
sys.exit(app.exec_())
if __name__ == '__main__': run()
I wanted to delete the widgets shown from home module when i click series button and delete widgets from series module when i click home button.
So far whats happening is when i click series button he previous widgets from home module are still there.
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
import sys
class Window(QMainWindow):
def __init__(self):
super(Window, self).__init__()
self.widget =QWidget()
self.layout = QHBoxLayout()
self.widget.setLayout(self.layout)
self.setCentralWidget(self.widget)
self.tabs()
def home(self):
self.clear()
self.df1 = QPushButton('hello')
self.layout.addWidget(self.df1)
def series(self):
self.clear()
self.df2 = QCheckBox('hello')
self.layout.addWidget(self.df2)
def tabs(self):
self.btn_home = QPushButton(QIcon('home.png'), 'Home')
self.btn_home.clicked.connect(self.home)
self.layout.addWidget(self.btn_home)
self.btn_series = QPushButton(QIcon('series.png'),'Series')
self.btn_series.clicked.connect(self.series)
self.layout.addWidget(self.btn_series)
self.show()
def clear(self):
item = self.layout.itemAt(2)
if item != None :
widget = item.widget()
if widget != None:
self.layout.removeWidget(widget)
widget.deleteLater()
def run():
app = QApplication(sys.argv)
GUI = Window()
sys.exit(app.exec_())
if __name__ == '__main__': run()
My version is
self.main_canvas.children().remove(cogmapui)
cogmapui.deleteLater()
I checked it by putting a print("Deleted") in the cogmapui's __del__ function and, yes, it gets called.

How to control QProgressBar with Signal

Pushing the button starts 100 rounds cycle. With QLabel.setText() we update self.label from inside of scope of clicked() function.
Aside from updating self.label we would like to update the progressbar as well.
But since progressbar is a local variable we can't update it from inside of onClick() function.
import time
class ProgressBar(QtGui.QProgressBar):
def __init__(self, parent=None, total=20):
super(ProgressBar, self).__init__(parent=parent)
self.setMinimum(1)
self.setMaximum(105)
self.setTextVisible(True)
def set_to_value(self, value):
self.setValue(value)
QtGui.qApp.processEvents()
def closeEvent(self, event):
self._active=False
class Dialog(QtGui.QDialog):
def __init__(self):
super(QtGui.QDialog,self).__init__()
layout = QtGui.QVBoxLayout()
self.setLayout(layout)
self.label = QtGui.QLabel('idle...')
layout.addWidget(self.label)
progressbar = ProgressBar(self)
layout.addWidget(progressbar)
button = QtGui.QPushButton('Process')
button.clicked.connect(self.onClick)
layout.addWidget(button)
def onClick(self):
for i in range(101):
message = '...processing %s of 100'%i
self.label.setText(message)
QtGui.qApp.processEvents()
time.sleep(1)
if __name__ == '__main__':
app = QtGui.QApplication([])
dialog = Dialog()
dialog.resize(300, 100)
dialog.show()
app.exec_()
Declare the progress bar as:
self.progressbar = ProgressBar(self)
The code declares a local progressbar object connected to a custom 'customSignal` using:
QtCore.QObject.connect(self, QtCore.SIGNAL("customSignal(int)"), progressbar, QtCore.SLOT("setValue(int)"))
with four arguments passed to QtCore.QObject.connect().
The first argument self is the object that will be emitting the signal. Since the function that will do the "sleep-every-second-processing" is declared under the main Dialog instance we pass self here.
The second argument is the name of the signal itself: 'customSignal'.
The progressbar object is used as third and its method setValue as fourth last argument.
class ProgressBar(QtGui.QProgressBar):
def __init__(self, parent=None):
super(ProgressBar, self).__init__(parent=parent)
class Dialog(QtGui.QDialog):
def __init__(self):
super(QtGui.QDialog,self).__init__()
layout = QtGui.QVBoxLayout()
self.setLayout(layout)
self.label = QtGui.QLabel('idle...')
layout.addWidget(self.label)
progressbar = ProgressBar(self)
QtCore.QObject.connect(self, QtCore.SIGNAL("customSignal(int)"), progressbar, QtCore.SLOT("setValue(int)"))
layout.addWidget(progressbar)
button = QtGui.QPushButton('Process')
button.clicked.connect(self.clicked)
layout.addWidget(button)
def clicked(self):
for value in range(101):
message = '...processing %s of 100'%value
self.label.setText(message)
self.emit(QtCore.SIGNAL("customSignal(int)"), value)
QtGui.qApp.processEvents()
time.sleep(1)
if __name__ == '__main__':
app = QtGui.QApplication([])
dialog = Dialog()
dialog.resize(300, 100)
dialog.show()
app.exec_()
--------------------
Here is variation of the same solution except linking to the progressbar method.
import time
class ProgressBar(QtGui.QProgressBar):
def __init__(self, parent=None):
super(ProgressBar, self).__init__(parent=parent)
def set_to_value(self, value):
self.setValue(value)
QtGui.qApp.processEvents()
return True
def closeEvent(self, event):
self._active=False
class Dialog(QtGui.QDialog):
def __init__(self):
QtGui.QDialog.__init__(self, parent=None)
layout = QtGui.QVBoxLayout()
self.setLayout(layout)
self.label = QtGui.QLabel('idle...')
layout.addWidget(self.label)
progressbar = ProgressBar(self)
QtCore.QObject.connect(self, QtCore.SIGNAL("customSignal(int)"), progressbar.set_to_value )
layout.addWidget(progressbar)
button = QtGui.QPushButton('Process')
button.clicked.connect(self.clicked)
layout.addWidget(button)
def clicked(self):
for value in range(101):
message = '...processing %s of 100'%value
self.label.setText(message)
self.emit(QtCore.SIGNAL("customSignal(int)"), value)
QtGui.qApp.processEvents()
time.sleep(1)
if __name__ == '__main__':
app = QtGui.QApplication([])
dialog = Dialog()
dialog.resize(300, 100)
dialog.show()
app.exec_()
======================
This code now links a custom signal to a function referred as Slot in Qt.
from PySide import QtCore, QtGui
import time
class ProgressBar(QtGui.QProgressBar):
def __init__(self, parent=None):
super(ProgressBar, self).__init__(parent=parent)
#QtCore.Slot(int)
def set_to_value(self, value):
self.setValue(value)
QtGui.qApp.processEvents()
return True
def closeEvent(self, event):
self._active=False
class Dialog(QtGui.QDialog):
def __init__(self):
QtGui.QDialog.__init__(self, parent=None)
layout = QtGui.QVBoxLayout()
self.setLayout(layout)
self.label = QtGui.QLabel('idle...')
layout.addWidget(self.label)
progressbar = ProgressBar(self)
# QtCore.QObject.connect(self, QtCore.SIGNAL("customSignal(int)"), progressbar.set_to_value )
QtCore.QObject.connect(self, QtCore.SIGNAL("customSignal(int)"), progressbar, QtCore.SLOT("set_to_value(int)"))
layout.addWidget(progressbar)
button = QtGui.QPushButton('Process')
button.clicked.connect(self.clicked)
layout.addWidget(button)
def clicked(self):
for value in range(101):
message = '...processing %s of 100'%value
self.label.setText(message)
self.emit(QtCore.SIGNAL("customSignal(int)"), value)
QtGui.qApp.processEvents()
time.sleep(1)
if __name__ == '__main__':
dialog = Dialog()
dialog.resize(300, 100)
dialog.show()

Categories