Timers cannot be stopped from another thread - Remove Focus - python

import sys
from PyQt5.QtCore import QThread
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QLineEdit
class Worker(QThread):
def __init__(self, textBox):
super().__init__()
self.textBox = textBox
def run(self):
while True:
if self.textBox.text() == "close":
app.quit()
break
if self.textBox.text() == "removeFocus":
self.textBox.clearFocus()
class window(QWidget):
def __init__(self):
super().__init__()
vBox = QVBoxLayout()
self.setLayout(vBox)
self.resize(600, 400)
textBox = QLineEdit()
vBox.addWidget(textBox)
worker = Worker(textBox)
worker.start()
self.show()
if __name__ == "__main__":
app = QApplication(sys.argv)
window = window()
sys.exit(app.exec())
When I type "close" in the textBox it works very fine but when I type "removeFocus", it still works but I get this error:
QObject::killTimer: Timers cannot be stopped from another thread
Why am I getting such an error even though the program is running?
(Since the process I want to do is very simple, I don't think I can go into much detail. I've just started learning Python. This is the first time I use this site. I'm sorry if I made a mistake while creating a post. Thank you)

In Qt you must not access or modify the GUI information from another thread (see this for more information) since it does not guarantee that it works (the GUI elements are not thread-safe), in your case luckily you have no problems but It is dangerous to use your approach in real.
In your case it is also unnecessary to use threads since it is enough to use the textChanged signal from QLineEdit.
import sys
from PyQt5.QtCore import pyqtSlot
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QLineEdit
class Window(QWidget):
def __init__(self):
super().__init__()
vBox = QVBoxLayout(self)
self.resize(600, 400)
self.textBox = QLineEdit()
vBox.addWidget(self.textBox)
self.textBox.textChanged.connect(self.on_text_changed)
#pyqtSlot(str)
def on_text_changed(self, text):
if text == "close":
QApplication.quit()
elif text == "removeFocus":
self.textBox.clearFocus()
if __name__ == "__main__":
app = QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec())

Related

PyQt - how to rerender window

I am new to PyQt and Qt at all and I have a problem with window rendering or updating or how to call it. Problem is that when I call QWidget.show(), none of added components after are displayed. Here is a simple code, where in init(), there is called self.show(). First QLabel item is displayed and the second one is not. What am I doing wrong?
Code:
from PyQt6.QtWidgets import QApplication, QWidget, QLabel
import sys
class MyGui(QWidget):
box = {}
def __init__(self) -> None:
super().__init__()
self.setFixedSize(200, 400)
self.setStyleSheet("background-color:#000000")
self.box["top"] = QLabel(self)
self.box["top"].setFixedSize(200, 200)
self.box["top"].setStyleSheet("background-color:red")
self.box["top"].move(0,0)
self.show()
self.box["botom"] = QLabel(self)
self.box["botom"].setFixedSize(200, 200)
self.box["botom"].setStyleSheet("background-color:green")
self.box["botom"].move(0,200)
if __name__ == "__main__":
app = QApplication(sys.argv)
my_app = MyGui()
my_app.show()
try:
sys.exit(app.exec())
except SystemExit:
print("Closing window..")
Screenshots of GUI window:
With self.show()
Without self.show()

App crashes after closing multiple QMessageBoxes

i have an odd problem with the GUI i am working on with Python using PyQt5.
The GUI has a single button that calls a class method which executes a Worker QThread.
In the Worker's run()-method I emit two consecutive signals that call the main GUIs show_messagebox()-method which displays a messagebox containing the information from the signal.
Displaying multiple QMessageBoxes works fine, but after closing the last one, the whole program including the main GUI exits/crashes. Unfortunately I don't get an error message.
Calling self.show_messagebox.emit() only once in the run()-method works as intended and doesn't close the whole program.
I assume that for some reason the program thinks that the last remaining QMessageBox (the first one that gets emitted) becomes the parent and after closing it the program is done?
Unfortunately I haven't found a solution on how to solve this issue, which is why I decided to ask.
Here is the full source code:
import sys
from PyQt5.QtCore import pyqtSignal, QObject, QThread
from PyQt5.QtWidgets import QPushButton, QVBoxLayout, QMainWindow, QWidget, QMessageBox
from PyQt5 import QtWidgets
class Worker(QObject):
finished = pyqtSignal()
show_messagebox = pyqtSignal(str, str)
def run(self):
self.show_messagebox.emit("Test1", "Test Text 1")
self.show_messagebox.emit("Test2", "Test Text 2")
self.finished.emit()
class WindowMain(QMainWindow):
def __init__(self):
super(WindowMain, self).__init__()
layout = QVBoxLayout()
self.button = QPushButton("Start")
self.messagebox = QMessageBox()
layout.addWidget(self.button)
self.button.clicked.connect(self.execute)
w = QWidget()
w.setLayout(layout)
self.setCentralWidget(w)
self.show()
def execute(self):
self.thread = QThread()
self.worker = Worker()
self.worker.moveToThread(self.thread)
self.thread.started.connect(self.worker.run)
self.worker.finished.connect(self.thread.quit)
self.worker.finished.connect(self.worker.deleteLater)
self.worker.show_messagebox.connect(self.show_messagebox)
self.thread.finished.connect(self.thread.deleteLater)
self.thread.start()
def show_messagebox(self, title, text, icon=QMessageBox.Information):
messagebox = QMessageBox()
messagebox.setWindowTitle(title)
messagebox.setText(text)
messagebox.setIcon(icon)
messagebox.exec_()
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
window = WindowMain()
window.show()
sys.exit(app.exec_())
Thanks in advance for any help!

Why is my loading screen not showing up using QThread?

I'm making a desktop application in which once databases are being loaded I want to display a loading screen. A simple search led me to use gif files in QLabel with QThread object. But in my case QThread will not show anything.
The thread works fine, but there is something wrong with my implementation, and I cannot figure out what. My sample code is as follows:
from PyQt5.QtWidgets import QWidget, QLabel, QVBoxLayout, QDialog, QApplication, QPushButton
from PyQt5.QtCore import QThread
from PyQt5.QtGui import QMovie
import sys
import time
class myThread(QThread):
def run(self):
test = QWidget() # Only creating this to give parent to QDialog and QLabel objects in upcoming lines
dialog = QDialog(test)
vbox = QVBoxLayout()
lbl = QLabel(test)
self.moviee = QMovie('Loading.gif')
lbl.setMovie(self.moviee)
self.moviee.start()
vbox.addWidget(lbl)
dialog.setLayout(vbox)
dialog.show()
def stop(self):
self.moviee.stop()
class Main(QWidget):
def __init__(self):
super().__init__()
print('Thread is to be called here...')
thread = myThread()
thread.run()
print('Thread has been called...')
btn= QPushButton('Test button')
vbox = QVBoxLayout()
vbox.addWidget(btn)
self.setLayout(vbox)
time.sleep(5) # sleep function used to emulate funcitons in actual program
# thread.stop()
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
window = Main()
sys.exit(app.exec_())
The block of code with QMovie object works fine when it is in the Main loop, so there is definitely somthing wrong with my implementation of QThread.
There are a couple of issues with your code. As #musicamante remarkes, you cannot create widget outside of the main thread. This means you can't create the dialog within myThread.run. Instead you could move the management of the dialog to Main and use slots and signals to open and close the dialog.
Secondly, all time-consuming tasks like your time.sleep(5) should be put in myThread.run.
With this in mind, you could do something like this
from PyQt5.QtWidgets import QWidget, QLabel, QVBoxLayout, QDialog, QApplication, QPushButton
from PyQt5.QtCore import QThread
from PyQt5.QtGui import QMovie
import sys
import time
class myThread(QThread):
def run(self):
# time consuming actions
time.sleep(5)
class Main(QWidget):
def __init__(self):
super().__init__()
print('Thread is to be called here...')
self.load()
print('Thread has been called...')
btn= QPushButton('Test button')
vbox = QVBoxLayout()
vbox.addWidget(btn)
self.setLayout(vbox)
self.show()
def load(self):
# setup dialog
dialog = QDialog(self)
vbox = QVBoxLayout()
lbl = QLabel(self)
self.moviee = QMovie('Loading.gif')
lbl.setMovie(self.moviee)
self.moviee.start()
vbox.addWidget(lbl)
dialog.setLayout(vbox)
# setup thread
thread = myThread()
thread.finished.connect(thread.deleteLater)
thread.finished.connect(dialog.close)
thread.finished.connect(dialog.deleteLater)
thread.start()
dialog.exec()
if __name__ == '__main__':
app = QApplication(sys.argv)
window = Main()
app.exec()

PySide2 Non-Blocking QMessageBox doesn't show text

While my application does some time-consuming stuff, I want to display a message box to the user while the applications is busy. I don't want any buttons (like OK or Cancel) and I can't call exec_() on the message box because that is blocking.
I checked a number of qt sites and the code I need seems to boil down to:
message_box = QMessageBox()
message_box.setText(str('Reading Device, Please Wait...'))
message_box.show()
# do work here
message_box.close()
When I run the code, I get the message box, but without the text. What am I doing wrong?
I've included a working example below:
#!/usr/bin/env python3
import sys
import time
from PySide2.QtWidgets import (QLineEdit, QPushButton, QApplication,
QVBoxLayout, QDialog, QMessageBox)
class Form(QDialog):
def __init__(self, parent=None):
super(Form, self).__init__(parent)
self.button = QPushButton("Click Me")
layout = QVBoxLayout()
layout.addWidget(self.button)
self.setLayout(layout)
# Add button signal to dowork slot
self.button.clicked.connect(self.dowork)
def dowork(self):
message_box = QMessageBox()
message_box.setText(str('Reading Device, Please Wait...'))
message_box.show()
delay = 2.5
while delay:
sys.stdout.write('Working...\n')
time.sleep(0.5) # do some time-consuming stuff...
delay -= 0.5
message_box.close()
if __name__ == '__main__':
app = QApplication(sys.argv)
form = Form()
print('starting app...')
form.show()
sys.exit(app.exec_())
If you click the button, the message box pops up and is shown while the 'work' is being done. When the 'work' is finished, the message box disappears again - as it should. But no text is shown in the message box.
There is a similar question here: qmessagebox-not-show-text-when-call-show, but that does not answer my question.
You can not have a task that consumes a lot of time (more than 30 ms) since it blocks the GUI eventloop preventing Qt from doing its job normally, instead it uses a thread next to the signals to update the GUI from the other thread:
import sys
import threading
import time
from PySide2 import QtCore, QtWidgets
class Form(QtWidgets.QDialog):
started = QtCore.Signal()
finished = QtCore.Signal()
def __init__(self, parent=None):
super(Form, self).__init__(parent)
self.button = QtWidgets.QPushButton("Click Me")
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.button)
# Add button signal to dowork slot
self.button.clicked.connect(self.on_clicled)
self._message_box = QtWidgets.QMessageBox()
self._message_box.setText(str('Reading Device, Please Wait...'))
self._message_box.setStandardButtons(QtWidgets.QMessageBox.NoButton)
self.started.connect(self._message_box.show)
self.finished.connect(self._message_box.accept)
#QtCore.Slot()
def on_clicled(self):
thread = threading.Thread(target=self.dowork, daemon=True)
thread.start()
def dowork(self):
delay = 2.5
self.started.emit()
while delay:
sys.stdout.write('Working...\n')
time.sleep(0.5) # do some time-consuming stuff...
delay -= 0.5
self.finished.emit()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
form = Form()
print('starting app...')
form.show()
sys.exit(app.exec_())
You should almost certainly be using QProgressDialog. Set minimum and maximum to zero for an 'indeterminate' look.
https://doc.qt.io/qt-5/qprogressdialog.html
Although forcing a redraw after your setText() will probably work:
self._message_box.repaint()
QtWidgets.QApplication.processEvents()

PyQt5 QFileDialog stops application from closing

I am trying to write a PyQt5 application that does the following:
Creates and opens a Main Window. The MainWindow has a function that opens a QFileDialog window.
Function to open QFileDialog can be triggered in two ways (1) from a file menu option called 'Open' (2) automatically, after the Main window is shown.
My problem is that I haven't found a way to get the QfileDialog to automatically open (2) that doesn't cause the application to hang when the main window is closed. Basic example of code can be found below:
import sys
from PyQt5.QtWidgets import (QApplication, QMainWindow, QMenuBar, QWidget,
QHBoxLayout, QCalendarWidget, QScrollArea, QFileDialog, QAction, QFrame)
from PyQt5.QtGui import QIcon
from PyQt5.QtCore import Qt
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.openAction = QAction(QIcon('/usr/share/icons/breeze/places/64/folder-open.svg'), 'Open', self)
self.openAction.triggered.connect(self.openDialog)
self.menubar = QMenuBar(self)
fileMenu = self.menubar.addMenu('&File')
fileMenu.addAction(self.openAction)
self.event_widgets = EventWidgets(self)
self.setMenuBar(self.menubar)
self.setCentralWidget(self.event_widgets)
def openDialog(self):
ics_path = QFileDialog.getOpenFileName(self, 'Open file', '/home/michael/')
class EventWidgets(QWidget):
def __init__(self, parent):
super(EventWidgets, self).__init__(parent)
self.initUI()
def initUI(self):
self.calendar = QCalendarWidget(self)
self.frame = QFrame()
self.scrollArea = QScrollArea()
self.scrollArea.setWidget(self.frame)
horizontal_box = QHBoxLayout()
horizontal_box.addWidget(self.calendar)
horizontal_box.addWidget(self.scrollArea)
self.setLayout(horizontal_box)
if __name__ == '__main__':
app = QApplication(sys.argv)
app_window = MainWindow()
app_window.showMaximized()
app_window.openDialog()
sys.exit(app.exec_())
Code has been tested on KDE Neon and Arch Linux, both have same issue.
I can get round this issue by handling the close event of the Main Window manually - i.e. adding this function to MainWindow:
def closeEvent(self, event):
sys.exit()
But I am not sure a) why this is necessary b) if it is best practice.
I tried your code on macOS Sierra and it works as it's supposed to. However I would propose a different approach to solve your problem.
What you could do is to implement the showEvent() function in your MainWindow class, which is executed whenever a widget is displayed (either using .show() or .showMaximized()) and trigger your custom slot to open the QFileDialog. When doing this you could make use of a single shot timer to trigger the slot with some minimal delay: the reason behind this is that if you simply open the dialog from within the showEvent(), you will see the dialog window but not the MainWindow below it (because the QFileDialog is blocking the UI until the user perform some action). The single shot timer adds some delay to the opening of the QFileDialog, and allows the MainWindow to be rendered behind the modal dialog. Here is a possible solution (not that I made some minor changes to your code, which you should be able to easily pick up):
import sys
from PyQt5 import QtCore
from PyQt5 import QtWidgets
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.openAction = QtWidgets.QAction('Open', self)
self.openAction.triggered.connect(self.openDialog)
menuBar = self.menuBar()
fileMenu = menuBar.addMenu('&File')
fileMenu.addAction(self.openAction)
self.event_widgets = EventWidgets(self)
self.setCentralWidget(self.event_widgets)
def showEvent(self, showEvent):
QtCore.QTimer.singleShot(50, self.openDialog)
#QtCore.pyqtSlot()
def openDialog(self):
ics_path = QtWidgets.QFileDialog.getOpenFileName(self, 'Open file', '/Users/daniele/')
class EventWidgets(QtWidgets.QWidget):
def __init__(self, parent):
super(EventWidgets, self).__init__(parent)
self.calendar = QtWidgets.QCalendarWidget(self)
self.frame = QtWidgets.QFrame()
self.scrollArea = QtWidgets.QScrollArea()
self.scrollArea.setWidget(self.frame)
horizontal_box = QtWidgets.QHBoxLayout()
horizontal_box.addWidget(self.calendar)
horizontal_box.addWidget(self.scrollArea)
self.setLayout(horizontal_box)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
app_window = MainWindow()
app_window.showMaximized()
sys.exit(app.exec_())

Categories

Resources