I want to replace the built-in method closeEvent of QMainWindow class instance that handles the form close event.
CODE #1
import sys
from PySide2.QtUiTools import QUiLoader
from PySide2.QtWidgets import QApplication
from PySide2.QtCore import QFile, QIODevice
app = QApplication(sys.argv)
ui_file_name = "ui\Main.ui"
ui_file = QFile(ui_file_name)
if not ui_file.open(QIODevice.ReadOnly):
print("Cannot open {}: {}".format(ui_file_name, ui_file.errorString()))
sys.exit(-1)
loader = QUiLoader()
window = loader.load(ui_file)
ui_file.close()
if not window:
print(loader.errorString())
sys.exit(-1)
def MainFormCloseEvent(event):
print(event)
event.ignore()
print(window.closeEvent)
window.closeEvent=MainFormCloseEvent
print(window.closeEvent)
window.show()
sys.exit(app.exec_())
This code does not cause the MainFormCloseEvent function to be called when the form closes.
This code print the following information:
<built-in method closeEvent of PySide2.QtWidgets.QMainWindow object at 0x000000000573BF80>
<function MainFormCloseEvent at 0x0000000002C37430>
But this code works well
CODE #2
import sys
from PySide2.QtUiTools import QUiLoader
from PySide2.QtWidgets import QApplication
from PySide2.QtCore import QFile, QIODevice
from PySide2.QtWidgets import QMainWindow
def MainFormCloseEvent(event):
print(event)
event.ignore()
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
def closeEvent(self, event):
print('Original class method')
app = QApplication(sys.argv)
window = MainWindow()
print(window.closeEvent)
window.closeEvent=MainFormCloseEvent
print(window.closeEvent)
window.show()
sys.exit(app.exec_())
This code print the following information:
<bound method MainWindow.closeEvent of <main.MainWindow(0x51522b0) at 0x0000000004E8AF40>>
<function MainFormCloseEvent at 0x0000000002C37430>
<PySide2.QtGui.QCloseEvent object at 0x0000000004E8F340>
I can't understand the fundamental difference between these codes. I replace the class instance method in the same way, but in the first case it does not work, but in the second it works.
I only noticed the difference that in the first code, the closeEvent method is built-in and in the second code, the closeEvent method is bound. But I did not find in Google what it means and how to make the first code work.
No, you're not replacing the method in the same way.
While creating a method as class attribute and overwriting an instance method at runtime usually has similar results, at the lower level it's not the same, which is extremely important when using complex modules like python binding to libraries written in other languages.
In general, attribute overwriting for methods should be done with extreme care only when it's safe to do it (and you do know what you're doing). It's also important to note that doing it for event handlers is risky, it makes debugging confusing and it also makes calling the default implementation more complex and awkward (you cannot call super()).
Unfortunately, PySide doesn't directly supports setting the UI on an existing widget instance, which is what you would do with PyQt and using a proper class, like in your second example), but there is a possible workaround, as explained in this related post.
class UiLoader(QtUiTools.QUiLoader):
_baseinstance = None
def createWidget(self, classname, parent=None, name=''):
if parent is None and self._baseinstance is not None:
widget = self._baseinstance
else:
widget = super().createWidget(classname, parent, name)
if self._baseinstance is not None:
setattr(self._baseinstance, name, widget)
return widget
def loadUi(self, uifile, baseinstance=None):
self._baseinstance = baseinstance
widget = self.load(uifile)
QtCore.QMetaObject.connectSlotsByName(baseinstance)
return widget
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
ui_file_name = "ui\Main.ui"
ui_file = QFile(ui_file_name)
ui_file.open(QIODevice.ReadOnly)
loader = UiLoader()
loader.loadUi(ui_file, self)
def closeEvent(self, event):
print('Original class method')
Still, you shouldn't overwrite the closeEvent with a basic function, instead you should probably opt for signals, further subclassing, or implement alternate ways to change the behavior (ie, using instance attributes).
I'm trying to figure out how to interact with the main thread in PyQt5 from another thread running in another file.
In the example below I'm trying to make a button green from the followup.py file's initialize function which is a thread started in the workbot_main.py file.
From the followup.py file I can't call 'workbot_main.widget' because it doesn't recognize it and I can't call workbot_main.MainWindow because it doesn't recognize it as an instantiated class so 'self' doesn't work and therefore most things within the class.
How am I supposed to interact with the MainWindow thread from another file?
I tried using slots and signals but I can't get that to work either.
Help would be massively appreciated.
#workbot_main.py
import threading
from workbot import *
import followup
from PyQt5 import QtWidgets
from PyQt5 import QtCore
from PyQt5.QtWidgets import QMainWindow, QApplication
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
#UI_MainWindow is from the workbot.py file which is generated from QTDesigner
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
followup_start_button = self.ui.followup_start_button
followup_start_button.clicked.connect(threads.thread_launch_followup_initialize)
p1_followup_button = self.ui.p1_followup_button
#QtCore.pyqtSlot()
def p1_followup_button_color_green():
self.p1_followup_button.setStyleSheet("background-color : green")
class Threads:
def __init__(self):
pass
def thread_launch_followup_initialize(self):
t1 = threading.Thread(target = followup.initialize, args = ())
t1.start()
threads = Threads()
if __name__ == '__main__':
app = QtWidgets.QApplication([])
widget = MainWindow()
widget.show()
app.exec_()
#followup.py file
def make_green():
workbot_main.MainWindow.p1_followup_button_color_green()
initialize():
make_green()
do other stuff
Edit:
I've tried doing a great many things, the only thing I could get to work is this
#workbot_main.py
import threading
from workbot import *
import followup
from PyQt5 import QtWidgets
from PyQt5 import QtCore
from PyQt5.QtWidgets import QMainWindow, QApplication
from PyQt5.QtCore import QThread, pyqtSignal, QObject, pyqtSlot, Qt
class Change_green(QObject):
setgreen = pyqtSignal()
#pyqtSlot()
def green(self):
self.setgreen.emit()
print("clicked")
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
#UI_MainWindow is from the workbot.py file which is generated from QTDesigner
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.Change_green = Change_green()
self.Change_green.setgreen.connect(lambda: print("connected"))
self.Change_green.setgreen.connect(self.p1_followup_button_color_green)
followup_start_button = self.ui.followup_start_button
followup_start_button.clicked.connect(self.Change_green.green) # <--- this works
p1_followup_button = self.ui.p1_followup_button
#pyqtSlot()
def p1_followup_button_color_green():
self.p1_followup_button.setStyleSheet("background-color : green")
if __name__ == '__main__':
app = QtWidgets.QApplication([])
widget = MainWindow()
widget.show()
app.exec_()
But that is useless to me since the signal is sent from inside the main thread.
What I need to work is this
#workbot_main.py
import threading
from workbot import *
import followup
from PyQt5 import QtWidgets
from PyQt5 import QtCore
from PyQt5.QtWidgets import QMainWindow, QApplication
from PyQt5.QtCore import QThread, pyqtSignal, QObject, pyqtSlot, Qt
class Change_green(QObject):
setgreen = pyqtSignal()
#pyqtSlot()
def green(self):
self.setgreen.emit()
print("clicked")
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
#UI_MainWindow is from the workbot.py file which is generated from QTDesigner
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.Change_green = Change_green()
self.Change_green.setgreen.connect(lambda: print("connected"))
self.Change_green.setgreen.connect(self.p1_followup_button_color_green)
followup_start_button = self.ui.followup_start_button
#starts the initialize function in followup.py which calls the Change_green class above
followup_start_button.clicked.connect(threads.thread_launch_followup_initialize)
p1_followup_button = self.ui.p1_followup_button
#pyqtSlot()
def p1_followup_button_color_green():
self.p1_followup_button.setStyleSheet("background-color : green")
class Threads:
def __init__(self):
pass
def thread_launch_followup_initialize(self):
t1 = threading.Thread(target = followup.initialize, args = ())
t1.start()
threads = Threads()
if __name__ == '__main__':
app = QtWidgets.QApplication([])
widget = MainWindow()
widget.show()
app.exec_()
# followup.py
import workbot_main
color = workbot_main.Change_green()
def initialize():
print("initializing")
color.green()
But if I call the Change_green() function from followup.py, the Change_green() function gets called properly because "clicked" gets printed but there is no following "connected" being printed from the main thread.
It's as if self.setgreen.emit() only works when called from within the main thread.
I couldn't execute your script since you used a QTDesigner project to create the GUI. Also, I don't know which modules are workbot or followup, so I didn't tried to install them as they might be private modules.
I can see that you used the threading module from python. In many tutorials available on the internet, they tell you to not use this module when dealing with Qt. If need multithreading, use the QThread class instead.
The reason is that Qt schedules its own events. When dealing with raw data, that might be ok. But when dealing with QWidgets, their methods are not thread safe, so executing them from another thread might lead you to many undesired problems.
As I recommend using QThreads, I provided a minor example below explaining how to use it (it's not so easy to start using QThreads, I had and still have a hard time when dealing with them).
If you need, you can place the QThread and QObject classes in another file, then import them from main file. There's no problem about that, just be careful to not let their instances' references be collected by the python's garbage collector.
I keep them out of reach by using self.thread = ChangeColorThread(...) inside the Scene's constructor. In this way, the thread's instance is bound to the Scene, and it's reference will not go out of scope when the Scene's constructor block ends.
from PySide2.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget, QLabel
from PySide2.QtCore import QObject, QThread, Signal, QMutex
import random
# I use PySide2 which is the same thing as PyQt5 with the exception of a few variable names
# that might change from one module to another. One of them might be `Signal` (PySide2) to
# `pyqtSignal` (PyQt5) for example.
#
# The imports should also change:
# from PySide2.QtWidgets import ...
# to
# from PyQt5.QtWidgets import ...
#
# But the idea is the same for both modules.
# -------------------------------------------------------------------------------------
# After a few researches on the Multi-Threading topic, I liked the option of using the
# `QObject.moveToThread(thread)` method to create threads based on QObjects.
#
# There are others:
# - Using QThreadPool and QRunnable
# - Using QThread only (by overwriting run() method. Not recommended.)
#
# For me, I got everything working so far using the `moveToThread` method, so that's
# the one I will be using on this example.
# -------------------------------------------------------------------------------------
# Here we create the Worker Object. Everything inside `Worker.main(self)` will be
# executed in another thread: ChangeColorThread.
#
# As we might inspect / change the value of `Worker.running` on both threads at the same time
# (Main Thread and ChangeColorThread), I recommend using a QMutex to restrict access
# to this variable.
class Worker(QObject):
# Note the signal possesses a `object` as a parameter. It signalizes the QObject
# that you want to pass an object type to the signal's receiver.
#
# In this case, it's a tuple (red, green, blue) as `Scene.setRandomColor` has the
# `color` parameter.
produce = Signal(object)
# Called from main thread only to construct the Worker instance.
def __init__(self, delay):
QObject.__init__(self)
self.running = True
self.delay = delay
self.runningLock = QMutex()
# Might be called from both threads.
def stop(self):
self.runningLock.lock()
self.running = False
self.runningLock.unlock()
# Might be called from both threads.
def stillRunning(self):
self.runningLock.lock()
value = self.running
self.runningLock.unlock()
return value
# Executes on ChangeColorThread only.
def main(self):
while (self.stillRunning()):
# Generate the random colors from the ChangeColorThread.
red = random.randint(0, 255)
green = random.randint(0, 255)
blue = random.randint(0, 255)
# Emit the `produce` signal to safely call the `Scene.setRandomColor()`
# on the Main Thread.
#
# In order to update any GUI or call any QWidget method, you must emit
# a connected signal to the main thread, so you don't raise any exceptions
# or segmentation faults (or worse, silent crashes).
self.produce.emit((red, green, blue))
# Tell the ChangeColorThread to sleep for delay microseconds
# (aka a value of 1000 == 1 second)
self.thread().msleep(self.delay)
print('Quit from ChangeColorThread Worker.main()')
self.thread().quit()
# Here we have the other thread we will instantiate. The thread by itself
# is nothing special. It acts like a QObject until `QThread.start()` is
# called. It also has a few important properties you might want to take a look:
# - started (calls one or more connected functions once the thread starts executing).
# - finished (calls one or more connected functions once the thread properly finishes).
#
# When `start()` is called, as we connected `self.started` to `Worker.main`,
# `Worker.main` will start executing on the other thread until a `stop` call
# is requested.
class ChangeColorThread(QThread):
def __init__(self, produce_callback, delay=100):
QThread.__init__(self)
self.worker = Worker(delay)
self.worker.moveToThread(self)
self.started.connect(self.worker.main)
self.worker.produce.connect(produce_callback)
# Stops the worker object's main loop from the main thread.
def stop(self):
self.worker.stop()
# Here we will create the GUI. I kept it simple, it contains a single QLabel.
#
# It also starts the ChangeColorThread thread.
class Scene(QWidget):
def __init__(self):
QWidget.__init__(self)
layout = QVBoxLayout()
self.setLayout(layout)
self.label = QLabel("<h3>See the Color Change?</h3>")
layout.addWidget(self.label)
self.thread = ChangeColorThread(self.setRandomColor, 500)
self.thread.start()
# This function will execute on the Main Thread only.
#
# However, this function is not called by the user. It is scheduled to execute at some point
# by PyQt5 once `Worker.produce` is emitted.
def setRandomColor(self, color):
self.label.setStyleSheet("background-color:rgb(%d,%d,%d)" % (color[0], color[1], color[2]))
# As we don't know wether ChangeColorThread has stopped or not, we signalize
# it to stop before closing the application.
#
# This step is important, because even if PyQt5 / PySide2 tries its best to close the
# remaining thread, once the window is closed, this process can fail. The result is a
# hidden process still running even after the program is closed.
def sceneInterceptCloseEvent(self, evt):
self.thread.stop()
self.thread.wait()
evt.accept()
# Here we create the Main Window. It contains a single scene with the QLabel inside it.
class Window(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.scene = Scene()
self.setCentralWidget(self.scene)
# We call Scene.sceneInterceptCloseEvent in order to signalize the running thread
# to stop its main loop before the application is closed.
def closeEvent(self, evt):
self.scene.sceneInterceptCloseEvent(evt)
if __name__ == '__main__':
app = QApplication()
win = Window()
win.show()
app.exec_()
So the idea of this example is simple. We have 2 threads:
The Main Thread: which handles the GUI, scene creation, user input, and thread management. Whenever the user tries to exit the application, it's on this thread that the other ones will be signalized to be stopped.
ChangeColorThread: which is an infinite loop that executes on the background, to change the QLabel's color at every few seconds, set by the delay variable. It can be signalized to stop by the Main Thread.
Once the application starts executing, the label's background color should change based on the emission of Worker.produce signal, which sets which random color should be used for the label.
You can also move the window or resize it. By doing these manual operations, it proves that the ChangeColorThread is really executing at the background of the application. Otherwise the application would become unresponsive (frozen).
As stated on the script, I used PySide2 instead of PyQt5. But its almost the same thing, as just a few variable names should change from one to another. If you find any problems because of this, just let me know on the comments.
Edit: How to make your script work with both ways
What confused me about your post was that you rewrote your workbot_main.py script 3x, then wrote followup.py 2x, one at the middle and one at the end. I was not understanding your examples. It's hard to keep track of a lot of code, specially if you provide a whole script when you just change one line or another.
After checking them with more paciency, I was able to understand what you wanted to do. I think...
You're trying to this right?
You click on self.ui.followup_start_button
This button is connected to threads.thread_launch_followup_initialize
Which calls Change_green.green()
Which emits the signal setgreen
setgreen is connected to MainWindow.p1_followup_button_color_green
This function on MainThread changes the background color of self.ui.p1_followup_button to the green color.
The main problem I found with your script is that you're creating two different instances of Change_green:
First one: in your followup.py file.
Second one: inside your MainWindow.__init__(self).
So everytime that you tried to access the object created on MainWindow from your thread, you were in fact accessing a different instance of the same class. After solving this problem and making a few tweaks on your script, it worked without any errors so far. (Yes, the button's background color did changed to green.)
Also, you say that you can't solve your problem using QThreads. In the final example below, I also use your own classes (with one additional method and Signal) and a custom QThread I created, to reproduce the same type of idea, but now changing the button color to red.
I put all Threading Classes into followup.py, and left only Scene and Logic Classes on workbot_main.py.
Here is your new workbot_main.py:
# from workbot import * # Can't import from workbot as I don't have this module on my side
import followup
from PySide2 import QtWidgets
from PySide2 import QtCore
from PySide2.QtWidgets import QMainWindow, QApplication, QWidget, QPushButton, QVBoxLayout
from PySide2.QtCore import QThread, Signal, QObject, Slot, Qt
class Ui(QWidget):
def __init__(self):
QWidget.__init__(self)
layout = QVBoxLayout()
self.setLayout(layout)
self.followup_start_button = QPushButton("Start FollowUp (Green)")
self.qthread_button = QPushButton("Start QThread (Red)")
self.p1_followup_button = QPushButton("Check me Turn Out Green or Red")
layout.addWidget(self.followup_start_button)
layout.addWidget(self.qthread_button)
layout.addWidget(self.p1_followup_button)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# --------------------------------------------
# # As I don't have your QTDesigner file, I cannot
# # load it.
# # UI_MainWindow is from the workbot.py file which is generated from QTDesigner:
# self.ui = Ui_MainWindow()
# self.ui.setupUi(self)
# --------------------------------------------
# So I created my own "UI" to load both buttons on the screen
# and execute your script.
self.ui = Ui()
self.setCentralWidget(self.ui)
# --------------------------------------------
# threading module (green)
# --------------------------------------------
self.Change_green = followup.Change_green()
self.Change_green.setgreen.connect(lambda: print("connected green"))
self.Change_green.setgreen.connect(self.p1_followup_button_color_green)
# Starts the initialize function in followup.py which calls the Change_green class above
#
# After creating the Change_green instance, we initialize the thread
# from another file here, and pass the instance to the thread, so it
# can access it from there.
self.threads = followup.Threads(self.Change_green)
self.followup_start_button = self.ui.followup_start_button
self.followup_start_button.clicked.connect(self.threads.thread_launch_followup_initialize)
# This line was wrong. But you probably meant to assign this variable to
# your class. After fixing it your script does not raises any error.
self.p1_followup_button = self.ui.p1_followup_button
# --------------------------------------------
# QThread class (red)
# --------------------------------------------
# With QThread you solve this in one line given that everying is configured
# on MyThread(QThread) class.
self.qthread = followup.MyThread(self.Change_green, self.qthread_button_color_red)
self.ui.qthread_button.clicked.connect(self.qthread.start)
# I Changed pyqtSlot -> Slot (As I use PySide2 instead of PyQt5)
#Slot()
def p1_followup_button_color_green(self):
self.p1_followup_button.setStyleSheet("background-color: green")
# I Changed pyqtSlot -> Slot (As I use PySide2 instead of PyQt5)
#Slot()
def qthread_button_color_red(self):
self.p1_followup_button.setStyleSheet("background-color: red")
if __name__ == '__main__':
app = QtWidgets.QApplication([])
widget = MainWindow()
widget.show()
app.exec_()
Here is your new followup.py:
from PySide2.QtCore import QObject, QThread, Signal, Slot
import threading
# ------------------------------------------------------------
# - Fixing your script to use threading module
# ------------------------------------------------------------
# We don't need to import workbot_main anymore.
#
# `color` is the same object being referenced from `MainWindow.Change_green`,
# which was passed by parameter when the thread was initialized on
# `MainWindow.__init__(self)`.
#
# Now the code should work as expected.
def initialize(color):
print("initializing: ", color)
color.green()
# To make the example simpler, I've put all threading scripts here on followup.py
class Threads:
def __init__(self, color):
self.color = color
def thread_launch_followup_initialize(self):
t1 = threading.Thread(target=initialize, args=[self.color])
t1.start()
# Including this object here which will run in another thread.
#
# As I use PySide2 I changed
# "pyqtSignal" -> "Signal"
# "pyqtSlot" -> "Slot"
class Change_green(QObject):
setgreen = Signal()
setred = Signal()
#Slot()
def green(self):
self.setgreen.emit()
print("clicked on green")
#Slot()
def red(self):
self.setred.emit()
print("clicked on red")
self.thread().quit()
# ------------------------------------------------------------
# - Using QThreads instead of python threading module
# ------------------------------------------------------------
class MyThread(QThread):
def __init__(self, change_green, qthread_button_color_red):
QThread.__init__(self)
self.Change_green = change_green
self.Change_green.setred.connect(lambda: print("connected red"))
self.Change_green.setred.connect(qthread_button_color_red)
# This is important. It sets the QObject to run on another thread.
self.Change_green.moveToThread(self)
# Set Change_green.red method to be runned on the other thread
self.started.connect(self.Change_green.red)
# Set the main thread to wait for the other thread, once this
# even is fired.
self.Change_green.setred.connect(self.wait)
Again, I still use PySide2 instead of PyQt5, so instead of using pyqtSignal and pyqtSlot, I used Signal and Slot. This change also happened on the module imports.
I left the QThread example here (Original Answer before the edit) in case you want to compare both of them.
I want use a parallel downloading videos from youtube, but my code ending with exception "PicklingError". Can you help guys with code, how it should be, please.
Another fixed variant:
import sys
#from pathos.multiprocessing import ProcessingPool as Pool
from multiprocessing import Pool
from pytube import YouTube
from youtubeMultiDownloader import UiMainWindow
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QFileDialog
class YouTubeInstance:
def __init__(self, path):
self.youtube = YouTube
self.path = path
#self.ui_obj = ui_obj
def download_file(self, url):
self.youtube(url).streams.get_highest_resolution().download(self.path)
#self.ui.ui.youtube_outputs.setText(f'Video \'{self.youtube.title}\' has been downloaded successfully!')
class YouTubeMultiDownloader(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.pool = Pool
self.ui = UiMainWindow()
self.ui.setup_ui(self)
self.path_to_dir = None
self.urls = None
def _get_urls_from_form(self):
self.urls = self.ui.youtube_urls.toPlainText().split('\n')
return len(self.urls)
def choose_directory(self):
self.path_to_dir = str(QFileDialog.getExistingDirectory(self, "Select Directory"))
def run_multi_downloads(self):
youtube = YouTubeInstance(self.path_to_dir)
self.pool(self._get_urls_from_form()).map(youtube.download_file, self.urls)
if __name__ == "__main__":
app = QtWidgets.QApplication([])
application = YouTubeMultiDownloader()
application.show()
sys.exit(app.exec_())
Updated:
My ui :)
Error 1 fixed:
Error 2 fixed:
Error 3 actual:
You've got the wrong side of the stick. Take a look at multiprocessing module documents. As it says, calling Pool method is for running multiple instance of same function simultaneously (in parallel). So call Pool method as many numbers you want, meanwhile your method does not any parameters, call it without any arguments:
with Pool(5) as p:
print(p.map(YouTubeMultiDownloader))
It create 5 parallel instance. You can change the code an refine your errors.
First of all, I'm currently migrating my source code from PyQt5 to PySide2 which requires me to change some of the syntaxes. As this site said that it only needs 3 things to do migrate from PyQt to Pyside2.
1.app.exec_. exec_ was used as exec is a Python2 keyword. Under Python3, PyQt5 allows the use of exec but not PySide2.
2.Under PyQt5 it’s QtCore.pyqtSignal and QtCore.pyqtSlot and under PySide2 it’s QtCore.Signal and QtCore.Slot .
3.loading Ui files.
But anyway later on when I tried to run my code it gave me this following error:
QThread: Destroyed while thread is still running
I had more than 2000 lines of code and I cannot determine which is the cause of this other than my last action which is trying to call QFileDialog which shouldn't be a problem (I've tested this with PyQt import and there's no problem and no warning at all). But in PySide2 it definitely might be the cause of it. I look up into this, he doesn't have the same problem as mine exactly. I'm not trying to call QFileDialog from different thread.
this is the minimal reproducible example of my working code in PyQt5:
import sys
import os
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QMainWindow, QFileDialog, QMessageBox, QWidget, QDialog
import random
class MyWidget(QtWidgets.QWidget):
def __init__(self):
QtWidgets.QWidget.__init__(self)
self.path = os.path.abspath(os.path.dirname(sys.argv[0]))
self.button = QtWidgets.QPushButton("Open File")
self.labelFile = QtWidgets.QLabel("empty")
self.labelData = QtWidgets.QLabel("None")
self.layout = QtWidgets.QVBoxLayout()
self.layout.addWidget(self.button)
self.layout.addWidget(self.labelFile)
self.layout.addWidget(self.labelData)
self.setLayout(self.layout)
self.button.clicked.connect(self.open_file)
timer = QtCore.QTimer(self)
timer.timeout.connect(self.update_data_value)
timer.start(1000)
def open_file(self):
x = QFileDialog.getOpenFileName(self,"Pilih File CSV yang Ingin Diproses",self.path,"CSV Files (*.csv)")
self.labelFile.setText(x[0])
def update_data_value(self):
self.DataProcess = DataProcess()
self.DataProcess.progress.connect(self.update_data_label)
self.DataProcess.start()
def update_data_label(self,x):
self.labelData.setText(str(x[0]))
class DataProcess(QtCore.QThread):
progress = QtCore.pyqtSignal(object)
def __init__(self):
QtCore.QThread.__init__(self)
def run(self):
x = random.randint(1,100)
self.progress.emit([str(x)+ " from thread"])
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
widget = MyWidget()
widget.show()
sys.exit(app.exec_())
and this is the non-working one in PySide2 after renaming import accordingly to PySide2 also renaming 'pyqtsignal' to 'Signal'
import sys
import os
from PySide2 import QtCore, QtGui, QtWidgets
from PySide2.QtWidgets import QMainWindow, QFileDialog, QMessageBox, QWidget, QDialog
import random
class MyWidget(QtWidgets.QWidget):
def __init__(self):
QtWidgets.QWidget.__init__(self)
self.path = os.path.abspath(os.path.dirname(sys.argv[0]))
self.button = QtWidgets.QPushButton("Open File")
self.labelFile = QtWidgets.QLabel("empty")
self.labelData = QtWidgets.QLabel("None")
self.layout = QtWidgets.QVBoxLayout()
self.layout.addWidget(self.button)
self.layout.addWidget(self.labelFile)
self.layout.addWidget(self.labelData)
self.setLayout(self.layout)
self.button.clicked.connect(self.open_file)
timer = QtCore.QTimer(self)
timer.timeout.connect(self.update_data_value)
timer.start(1000)
def open_file(self):
x = QFileDialog.getOpenFileName(self,"Pilih File CSV yang Ingin Diproses",self.path,"CSV Files (*.csv)")
self.labelFile.setText(x[0])
def update_data_value(self):
self.DataProcess = DataProcess()
self.DataProcess.progress.connect(self.update_data_label)
self.DataProcess.start()
def update_data_label(self,x):
self.labelData.setText(str(x[0]))
class DataProcess(QtCore.QThread):
progress = QtCore.Signal(object)
def __init__(self):
QtCore.QThread.__init__(self)
def run(self):
x = random.randint(1,100)
self.progress.emit([str(x)+ " from thread"])
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
widget = MyWidget()
widget.show()
sys.exit(app.exec_())
so after creating this minimal example, I realized that PySide QFileDialog makes the QThread stop while PyQt QFileDialog doesn't freeze the main thread. Is there anything I could do to handle this in similar syntax architecture? (e.g not using "movetothread" or "QObject")
The problem is that you're overwriting self.DataProcess every time a new thread is created, which may cause the previous object to be garbage-collected by Python before Qt has a chance to delete it. This can result in a core-dump if Qt tries to delete an object which is no longer there. Problems of this kind are quite common in both PyQt and PySide, and are usually caused by not keeping proper references to dependant objects. The normal solution is to ensure that the affected objects are given a parent and, if necessary, explicitly delete them at an appropriate time.
Here is one way to fix your example:
class MyWidget(QtWidgets.QWidget):
...
def update_data_value(self):
# ensure the thread object has a parent
process = DataProcess(self)
process.progress.connect(self.update_data_label)
process.start()
def update_data_label(self,x):
self.labelData.setText(str(x[0]))
class DataProcess(QtCore.QThread):
progress = QtCore.Signal(object)
def __init__(self, parent):
# ensure the thread object has a parent
QtCore.QThread.__init__(self, parent)
def run(self):
x = random.randint(1,100)
self.progress.emit([str(x)+ " from thread"])
# explicitly schedule for deletion
self.deleteLater()
It's hard to say exactly why PySide behaves differently to PyQt in this particular case. It usually just comes down to low-level differences between the two implementations. Probably there are equivalent cases that affect PyQt but not PySide. However, if you manage object references and cleanup carefully, such differences can usually be eliminated.
When i run python-pyside2 project server first, then it works well.
And the site also work well, but if i press F5 btn to refresh in browser.
Site shows error page Runtime at/
import sys
from urllib.request import urlopen
from bs4 import BeautifulSoup
from PySide2.QtGui import *
from PySide2.QtCore import *
from PySide2.QtWebKitWidgets import *
from PySide2.QtWidgets import QApplication
class dynamic_render(QWebPage):
def __init__(self, url):
self.frame = None
self.app = QApplication(sys.argv)
QWebPage.__init__(self)
self.loadFinished.connect(self._loadFinished)
self.mainFrame().load(QUrl(url))
QTimer.singleShot(0, self.sendKbdEvent)
QTimer.singleShot(100, app.quit)
self.app.exec_()
def _loadFinished(self, result):
self.frame = self.mainFrame()
self.app.quit()
self.app = None
Below, scaping code using pyside2:
I don't know how can i fix it?
Best regards.
Thanks.
Check if already an instance of QApplication is present or not as the error occurs when an instance is already running and you are trying to create a new one.
Write this in your main function:
if not QtWidgets.QApplication.instance():
app = QtWidgets.QApplication(sys.argv)
else:
app = QtWidgets.QApplication.instance()
For my pyside2 unit test the following worked fairly well
import PySide2
import unittest
class Test_qapplication(unittest.TestCase):
def setUp(self):
super(Test_qapplication, self).setUp()
if isinstance(PySide2.QtGui.qApp, type(None)):
self.app = PySide2.QtWidgets.QApplication([])
else:
self.app = PySide2.QtGui.qApp
def tearDown(self):
del self.app
return super(Test_qapplication, self).tearDown()
it was loosely based on stack overflow : unit-and-functional-testing-a-pyside-based-application