Start a process on click - python

I am trying to add multiprocessing to my project. To exemplify, I made a little project with just a button, and when the button is pressed, I want to start a process with a method, but the method is never called.
How could I call the method within a process?
Here is my code:
from multiprocessing import Process
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
import sys
class Window(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Python ")
self.setGeometry(100, 100, 600, 400)
self.UiComponents()
self.show()
def UiComponents(self):
button = QPushButton("CLICK", self)
button.setGeometry(200, 150, 100, 30)
proc = Process(target=self.button_clicked)
procs = []
procs.append(proc)
button.clicked.connect(proc.start)
def button_clicked(self):
print("pressed")
App = QApplication(sys.argv)
window = Window()
sys.exit(App.exec())

generally mixing both Qt GUI objects and anything that allows threading/multiprocessesing is not safe, and will either not work or cause a segmentation fault.
the correct way to do it without crashing your application is to move the target function to be a function that doesn't relate to QT GUI. (not a member of any Qt GUI object or has a reference to any Qt GUI object)
also processes can only start once, so you need to create one on every button press as follows:
from multiprocessing import Process
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
import sys
def some_function():
print("hello")
class Window(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Python ")
self.setGeometry(100, 100, 600, 400)
self.UiComponents()
self.show()
def UiComponents(self):
button = QPushButton("CLICK", self)
button.setGeometry(200, 150, 100, 30)
self.procs = []
button.clicked.connect(self.button_clicked)
def button_clicked(self):
proc = Process(target=some_function)
self.procs.append(proc)
proc.start()
if __name__ == "__main__": # for windows compatibility
App = QApplication(sys.argv)
window = Window()
sys.exit(App.exec())
the button is connected to a function which belongs to a Qt object, but the process is connected to a function that is not in any way connected to Qt, and will therefore not crash your application.

Related

Python pyqt6 window blocks main loop

I have a small program that does something, but i want to "switch modes", for now i press a key and an input prompts on the console, but to make it easier i want to make a window with pyqt6, the problem is that the window blocks or halts the main loop while it's open, i tried with threading/multiprocessing but i can't make it work.
import threading
from queue import Queue
from PySide6.QtWidgets import *
from PySide6.QtGui import *
from PySide6.QtCore import Qt
queue = Queue()
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
layout = QVBoxLayout()
label = QLabel("Change modes")
btn1 = QPushButton("MODE 1")
btn2 = QPushButton("MODE 2")
layout.addWidget(label)
layout.addWidget(btn1)
layout.addWidget(btn2)
widget = QWidget()
widget.setLayout(layout)
self.setCentralWidget(widget)
btn1.clicked.connect(self.mode1)
btn2.clicked.connect(self.mode2)
self.show()
def mode1(self):
queue.put("mode1")
def mode2(self):
queue.put("mode2")
if __name__ == '__main__':
app = QApplication()
window = MainWindow()
app.exec()
mode = "none"
while True:
_mode = queue.get()
if mode != _mode:
mode = _mode;
print(f"mode: {mode}")
# do stuff here
the only way that the while loop executes is when i close the window.
Traditional Python multiprocessing/multithreading libraries such as multiprocessing and threading do not work well with Qt-like (PyQt and PySide) graphical programs. Fortunately, among other solutions, PySide provides the QThread interface, allowing multithreading in PySide graphical interfaces. It can be applied to your program as follows:
import threading
from queue import Queue
from PySide6.QtWidgets import *
from PySide6.QtGui import *
from PySide6.QtCore import Qt, QThread
queue = Queue()
class Worker(QThread):
def __init__(self):
super(Worker, self).__init__()
def run(self):
mode = "none"
while True:
_mode = queue.get()
if mode != _mode:
mode = _mode;
print(f"mode: {mode}")
# do stuff here
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
layout = QVBoxLayout()
label = QLabel("Change modes")
btn1 = QPushButton("MODE 1")
btn2 = QPushButton("MODE 2")
layout.addWidget(label)
layout.addWidget(btn1)
layout.addWidget(btn2)
widget = QWidget()
widget.setLayout(layout)
self.setCentralWidget(widget)
btn1.clicked.connect(self.mode1)
btn2.clicked.connect(self.mode2)
self.show()
self.worker = Worker() # Create a Worker instance
self.worker.start() # Start the Worker instance (which calls the run function of the Worker instance)
def mode1(self):
queue.put("mode1")
def mode2(self):
queue.put("mode2")
def closeEvent(self, event):
self.worker.terminate() # When the window closes, stop the thread
if __name__ == '__main__':
app = QApplication()
window = MainWindow()
app.exec()
Please note the changed import statement of PySide6.QtCore (to import QThread), the addition of the self.worker variable in the __init__ function of the MainWindow class (to actually start the thread), as well as the addition of a closeEvent function in the MainWindow class (to terminate the thread when the window closes).

threading queue blocks main pyqt5 GUI window

Although I read a ton of posts on the web, but couldn't find a solution for my self.
Basically, I am using pyqt5 for making a GUI application and am using threading for creating threads and lastly using queue I tried to send signals between main and child thread.
I have a qt5 progress bar in which it is value needs to updated by the child thread progress. When I try to use progressbar.setValue(x) I get below error:
QObject::setParent: Cannot set parent, new parent is in a different thread
The reason for above is that I cannot update the progress bar value from child thread, it needs to happen from main thread.
So I tried to use queue and send a message to the main thread to do it.
I tried below:
def buttonclicked():
global progressQueue
progressQueue = Queue()
thread = threading.Thread(target=childfunc, daemon=True, name="nemo", args=())
thread.start()
progressQueue.join()
while True:
if not progressQueue.empty():
msg = progressQueue.get()
pellow.setValue(msg)
return 0
def childfunc():
for i in range(1,100):
progressQueue.put(i)
Here the problem is that, the while loop freezes the mainwindow/gui app from interaction. Can anyone suggest, what do I need to do here?
A complete working example:
import queue
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from threading import Thread
app = QApplication(sys.argv)
class mainwindow1(QMainWindow):
def __init__(self):
super().__init__()
mainwindow = QWidget()
mainwindow.setObjectName("mainwindow")
mainwindow.setContentsMargins(100, 100, 100, 100)
self.setCentralWidget(mainwindow)
global pellow
global scanProgressbar
scanProgressbar = QProgressBar()
pellow = QPushButton('Click Here')
pellow.setObjectName("defaultbutton")
cpscanlayout = QVBoxLayout(mainwindow)
cpscanlayout.addWidget(scanProgressbar, alignment=Qt.AlignCenter, stretch=10)
cpscanlayout.addWidget(pellow, alignment=Qt.AlignCenter, stretch=10)
cpscanlayout.setAlignment(Qt.AlignCenter)
# cpscanlayout.setContentsMargins(0, 0, 0, 0);
cpscanlayout.setSpacing(5);
pellow.setCursor(QCursor(Qt.PointingHandCursor))
pellow.clicked.connect(buttonclicked)
self.show()
app.exec_()
def buttonclicked():
global progressQueue
progressQueue = Queue()
thread = threading.Thread(target=childfunc, daemon=True, name="nemo", args=())
thread.start()
progressQueue.join()
while True:
if not progressQueue.empty():
msg = progressQueue.get()
scanProgressbar.setValue(msg)
return 0
The code has several errors such as imports (import queue but it doesn't look like it uses Queue, it imports Thread but it uses threading.Thread, etc).
On the other hand, the misconception is that in Qt you should not use code that blocks the eventloop, such as while True. If you want to exchange information between threads then in the case of Qt you should not use Queue since it does not notify when there is a new data, instead you must use the signals that are also thread-safe and do notify when there is new data.
import sys
import threading
import time
from PyQt5.QtCore import pyqtSignal, QObject, Qt
from PyQt5.QtGui import QCursor
from PyQt5.QtWidgets import (
QApplication,
QMainWindow,
QProgressBar,
QPushButton,
QVBoxLayout,
QWidget,
)
class Signaller(QObject):
progress_changed = pyqtSignal(int)
class mainwindow1(QMainWindow):
def __init__(self):
super().__init__()
mainwindow = QWidget()
mainwindow.setObjectName("mainwindow")
mainwindow.setContentsMargins(100, 100, 100, 100)
self.setCentralWidget(mainwindow)
scanProgressbar = QProgressBar()
pellow = QPushButton("Click Here")
pellow.setObjectName("defaultbutton")
cpscanlayout = QVBoxLayout(mainwindow)
cpscanlayout.addWidget(scanProgressbar, alignment=Qt.AlignCenter, stretch=10)
cpscanlayout.addWidget(pellow, alignment=Qt.AlignCenter, stretch=10)
cpscanlayout.setAlignment(Qt.AlignCenter)
# cpscanlayout.setContentsMargins(0, 0, 0, 0);
cpscanlayout.setSpacing(5)
pellow.setCursor(QCursor(Qt.PointingHandCursor))
pellow.clicked.connect(self.buttonclicked)
self.signaller = Signaller()
self.signaller.progress_changed.connect(scanProgressbar.setValue)
def buttonclicked(self):
thread = threading.Thread(
target=childfunc, args=(self.signaller,), daemon=True, name="nemo"
)
thread.start()
def childfunc(signaller):
for i in range(1, 100):
signaller.progress_changed.emit(i)
time.sleep(0.1)
app = QApplication(sys.argv)
w = mainwindow1()
w.show()
app.exec_()

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()

Timers cannot be stopped from another thread - Remove Focus

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())

Hide current QMainWindow when next QMainWindow is called

I have multiple windows in a Python GUI application using PyQt5.
I need to hide current window when a button is clicked and show the next window.
This works fine from WindowA to WindowB but I get an error while going from WindowB to WindowC.
I know there is some problem in initialization as the initialization code in WindowB is unreachable, but being a beginner with PyQt, i can't figure out the solution.
WindowA code:
from PyQt5 import QtCore, QtGui, QtWidgets
from WindowB import Ui_forWindowB
class Ui_forWindowA(object):
def setupUi(self, WindowA):
# GUI specifications statements here
self.someButton = QtWidgets.QPushButton(self.centralwidget)
self.someButton.clicked.connect(self.OpenWindowB)
# More GUI specifications statements here
def retranslateUi(self, WindowA):
# More statements here
def OpenWindowB(self):
self.window = QtWidgets.QMainWindow()
self.ui = Ui_forWindowB()
self.ui.setupUi(self.window)
WindowA.hide()
self.window.show()
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
WindowA = QtWidgets.QMainWindow()
ui = Ui_forWindowA()
ui.setupUi(WindowA)
MainWindow.show()
sys.exit(app.exec_())
WindowB code:
from PyQt5 import QtCore, QtGui, QtWidgets
from WindowB import Ui_forWindowB
class Ui_forWindowB(object):
def setupUi(self, WindowB):
# GUI specifications statements here
self.someButton = QtWidgets.QPushButton(self.centralwidget)
self.someButton.clicked.connect(self.OpenWindowC)
# More GUI specifications statements here
def retranslateUi(self, WindowB):
# More statements here
def OpenWindowB(self):
self.window = QtWidgets.QMainWindow()
self.ui = Ui_forWindowC()
self.ui.setupUi(self.window)
WindowB.hide() # Error here
self.window.show()
# The below code doesn't get executed when Ui_forWindowB is called from A
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
WindowB = QtWidgets.QMainWindow()
ui = Ui_forWindowB()
ui.setupUi(WindowB)
MainWindow.show()
sys.exit(app.exec_())
It works fine from A to B where
WindowA.hide() # Works Properly
While calling WindowC from WindowB
WindowB.hide() # Shows error: name 'WindowB' is not defined
I understand that the initialization isn't done as the "if" statement doesn't get executed.
How to get this working?
I have many more windows to connect in this flow
When you run a Python script, the first file executed will be assigned the name __main__, therefore, if you first execute WindowA the code inside the block if __name__ == "__main__" gets executed and the application is started using WindowA as the main window, similarly if you execute your WindowB script first, the application will be started usingWindowB as the main window.
You cannot start two applications within the same process so you have to choose which one you want to be the main window, all the others will be secondary windows (even if they inherit from QMainWindow).
Nevertheless, you should be able to instantiate new windows from a method in your main window.
As a good practice, you could create a main script to handle the initialization of your application and start an empty main window that will then handle your workflow, also, you may want to wrap your UI classes, specially if they are generated using Qt creator, here is an example:
main.py
import PyQt5
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtWidgets import QApplication
from views.main_window import MainWindow
class App(QApplication):
"""Main application wrapper, loads and shows the main window"""
def __init__(self, sys_argv):
super().__init__(sys_argv)
# Show main window
self.main_window = MainWindow()
self.main_window.show()
if __name__ == '__main__':
app = App(sys.argv)
sys.exit(app.exec_())
main_window.py
This is the main window, it doesn't do anything, just control the workflow of the application, i.e. load WindowA, then WindowB etc., notice that I inherit from Ui_MainWindow, by doing so, you can separate the look and feel from the logic and use Qt Creator to generate your UI:
from PyQt5.QtWidgets import QWidget, QMainWindow
from views.window_a import WindowA
from views.window_b import WindowB
from widgets.main_window import Ui_MainWindow
class MainWindow(Ui_MainWindow, QMainWindow):
"""Main application window, handles the workflow of secondary windows"""
def __init__(self):
Ui_MainWindow.__init__(self)
QMainWindow.__init__(self)
self.setupUi(self)
# start hidden
self.hide()
# show window A
self.window_a = WindowA()
self.window_a.actionExit.triggered.connect(self.window_a_closed)
self.window_a.show()
def window_a_closed(self):
# Show window B
self.window_b = WindowB()
self.window_b.actionExit.triggered.connect(self.window_b_closed)
self.window_b.show()
def window_b_closed(self):
#Close the application if window B is closed
self.close()
window_a.py
from PyQt5.QtWidgets import QWidget, QMainWindow
from widgets.main_window import Ui_forWindowA
class WindowA(Ui_forWindowA, QMainWindow):
"""Window A"""
def __init__(self):
Ui_forWindowA.__init__(self)
QMainWindow.__init__(self)
self.setupUi(self)
# Do some stuff
window_b.py
from PyQt5.QtWidgets import QWidget, QMainWindow
from widgets.main_window import Ui_forWindowB
class WindowA(Ui_forWindowB, QMainWindow):
"""Window B"""
def __init__(self):
Ui_forWindowB.__init__(self)
QMainWindow.__init__(self)
self.setupUi(self)
# Do some stuff
Hopefully should give you an idea to get you going.

Categories

Resources