PyQt5 uses Pyglet's event loop automatically? - python

Why does this code work?
from PyQt5.QtWidgets import QApplication, QWidget
import pyglet
app = QApplication([])
w = QWidget()
w.show()
pyglet.app.run()
PyQt somehow uses Pyglet's event loop, why and how much can I rely on this? I am not skilled enough to debug it. This works with much more complex PyQt5 apps too, all events are handled properly.
I'm on windows 10, python 3.7.2.

Related

CPython extension using omp freezes Qt UI

I am working on a scientific algorithm (image processing), which is written in C++, and uses lots of parallelization, handled by OpenMP. I need it to be callable from Python, so I created a CPython package, which handles the wrapping of the algorithm.
Now I need some UI, as user interaction is essential for initializing some stuff. My problem is that the UI freezes when I run the algorithm. I start the algorithm in a separate thread, so this shouldn't be a problem (I even proved it by replacing the function call with time.sleep, and it works fine, not causing any freeze). For testing I reduced the UI to two buttons: one for starting the algorithm, and another just to print some random string to console (to check UI interactions).
I also experienced something really weird. If I started moving the mouse, then pressed the button to start the computation, and after that kept moving the mouse continuously, the UI did not freeze, so hovering over the buttons gave them the usual blueish Windows-style tint. But if I stopped moving my mouse for a several seconds over the application window, clicked a button, or swapped to another window, the UI froze again. It's even more strange that the UI stayed active if I rested my mouse outside of the application window.Here's my code (unfortunately I cannot share the algorithm for several reasons, but I hope I manage to get some help even like this):
if __name__ == "__main__":
from PyQt5.QtWidgets import QApplication, QPushButton, QVBoxLayout, QWidget
from PyQt5.QtCore import QThread, QObject, pyqtSignal
import time
from CustomAlgorithm import Estimator # my custom Python package implemented in C++
class Worker(QObject):
finished = pyqtSignal()
def run(self):
estimator = Estimator()
estimator.calculate()
# if the above two lines are commented, and the next line is uncommented,
# everything's fine
# time.sleep(5)
print("done")
app = QApplication([])
thread = QThread()
window = QWidget()
layout = QVBoxLayout()
# button to start the calculation
btn = QPushButton("start")
layout.addWidget(btn)
btn.clicked.connect(thread.start)
# button to print some text to console
btn2 = QPushButton("other button")
layout.addWidget(btn2)
btn2.clicked.connect(lambda: print("other button clicked"))
window.setLayout(layout)
# handling events
worker = Worker(app)
worker.moveToThread(thread)
thread.started.connect(worker.run)
worker.finished.connect(thread.quit)
worker.finished.connect(worker.deleteLater)
thread.finished.connect(thread.deleteLater)
window.show()
app.exec_()
I tried multiple variants of using threads, like threading.Thread, multiprocessing.Process, PyQt5.QtCore.QThread (as seen above), even napari's worker implementation, but the result was the same. I even tried removing omp from the code, just in case it interferes somehow with python threads, but it didn't help.
As for the reason I use python, is that the final goal is to make my implementation available in napari.
Any help would be highly appreciated!
Because of Python's "Global Interpreter Lock", only one thread can run Python code at a time. However, other threads can do I/O at the same time.
If you want to allow other threads to run (just like I/O does) you can surround your code with these macros:
Py_BEGIN_ALLOW_THREADS
// computation goes here
Py_END_ALLOW_THREADS
Other Python threads will be allowed to run while the computation is happening. You can't access anything from Python between these two lines - so get that data in order before Py_BEGIN_ALLOW_THREADS.
Reference

QWebEngineView: Timers do not function when visible

I was attempting to implement the code from this question, but I have discovered that QTimer does not fire under certain circumstances.
The simplified code:
app = QApplication([])
view = QWebEngineView()
view.show()
def callback():
sys.exit(0)
QTimer.singleShot(1000, callback)
app.exec()
The application runs forever with this code (ie. callback is never executed). However, if you comment out view.show() then it exits after one second as it should.
As far as I can tell, this problem is particular to QWebEngineView; if we replace the QWebEngineView with a generic QWidget or QLabel, then it works as expected and exits after one second.
What is the cause of this, and how can I fix it?
As prompted by #eyllanesc, I have discovered that this is a bug introduced by the 5.14.0 release of the PyQtWebEngine module; the previous version (5.13.2) does not show this behavior.

how to run qt widget pyqtgraph from python app

I have well running python app with GUI made on tkinter. I need to implement qt widget from pyqtgraph showing graph (massive data so matplot lib can't do that).
I import needed Qt stuff:
import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui
and following code somewhere from tkinter class opens it in new window and shows the graph:
def preview_show():
APP = QtGui.QApplication([])
## Define a top-level widget to hold everything
win = QtGui.QWidget()
## Create a grid layout to manage the widgets size and position
layout = QtGui.QGridLayout()
win.setLayout(layout)
pl = pg.PlotWidget()
pl.plot(self.DataSeries, pen=(255, 0, 0), name="Red curve")
layout.addWidget(pl)
win.show()
APP.exec_()
Then, this widget has some functions accessible by right-clicking on the graph and I need them (like exporting image). But when I try to click on any of that functions - python kernel crashes with error code 255. I have an idea that qt events are not processed. So I tried to call Qt event handler in a loop from python, instead of calling APP.exec_():
while TRUE:
APP.processEvents()
time.sleep(0.002)
but it runs the same way (window with graph opens but freezes on attempt to access other built-in functions).
I was thinking about running Qt widget in separate thread but if I can't do this in a loop then I think it will not run in the thread either.
I've read this Python working with Qt, event loop but it doesn't work for me.
So the question is - how to process Qt widget from tkinter app correctly (in a separate window) with all built-in functions running? Am I right that the problem caused by wrong qt events handling from tkinter app?

PyQt4, prevent app.exec_() from hanging the main thread

I have some simple code that loads google.com with the PyQt4 library.
This is the code:
import sys
from PyQt4.QtGui import QApplication
from PyQt4.QtCore import QUrl
from PyQt4.QtWebKit import QWebView
class Browser(QWebView):
def __init__(self):
QWebView.__init__(self)
self.loadFinished.connect(self._result_available)
def _result_available(self, ok):
frame = self.page().mainFrame()
#print(frame.toHtml())
app.exit()
if __name__ == '__main__':
app = QApplication(sys.argv)
view = Browser()
view.load(QUrl('http://www.google.com'))
print('start')
app.exec_()#hangs the main thread
print('end')
My problem with this code is that app.exec_() hangs the main thread for a while, between the print start and print end.
Is there a way to scrape a website in PyQt4 without making the main thread hang for a while.
I would like to resume the main thread's normal execution of code after app.exec_().
It's probably just taking time to download the content from the web. app.exec_() is what runs your application basically. So if anything is indeed hanging it's actually some other factor that influences the proper execution of your application. Any statements that are after the app.exec_() will be executed once you close the application. Usually app.exec_() is called at the end of the main (in C++ you do return app.exec_() which you can also do in PyQt of course).
When working with content that requires time to be downloaded and displayed in the UI, you have to add multithreading in order to allow the main thread to continue work properly (thus not creating the so called UI freeze) but at the same time do some work in the background. The sole purpose of the main thread is to keep track of the UI and run it. Anything else that you add to the main thread will prevent the UI from working in a fluent manner. You have several options here - inheriting QThread (I wouldn't suggest doing that for this scenario in particular), using QThread + QObject in order to incorporate slots and signal in your application (for example: page downloaded? -> if yes, signal the UI to display the content), QRunnable etc.
So my advice is to download the content in a separate thread and once it's done you can add it to your UI as well as provide some other form of visual feedback (this also includes print statements :P).

Can QtGui.QFileDialog.getOpenFileName() be used outside a GUI?

I am experiencing a weird behavior from this simple line of code, which I wanted to use outside a pyqt GUI class
from PyQt4 import QtGui
FilePath=QtGui.QFileDialog.getOpenFileName(None,'choose the file')
When I first implemented it, it worked (probably because I run some other code before that I cannot trace back).
Then after restarting python, it stopped working and it crashes saying:
QWidget: Must construct a QApplication before a QPaintDevice
The same exact code works fine when implemented in a GUI, where the first argument is of course self.
But I would like to use the same filedialog in an external function I use for a different purpose, outside a GUI context.
Is it possible and what could be a way/workaround to implement that ? I would like to avoid using wx, easygui or tk
In order to use the Qt UI elements (such as QFileDialog), you generally have to have a QApplication running. It seems a little unorthodox, but you can get your snippet to run just by instantiating a QApplication prior to your QFileDialog, like so
from PyQt4 import QtGui
app = QtGui.QApplication([])
FilePath=QtGui.QFileDialog.getOpenFileName(None,'choose the file')
Technically, this will work, though I'm not quite sure I can endorse doing this.
Qt applications are built around an event loop and require QApplication instance. Dialogs have their own event loop, so if all you want is a wizard (a series of dialogs that open/close in sequence), then in principle all you need is the QApplication instance. The following works in PyQt 5:
from PyQt5 import Qt
app = Qt.QApplication([])
FilePath=Qt.QFileDialog.getOpenFileName(None,'choose the file')
print(FilePath)
However, if you have other code that assumes an application event loop, all bets are off. In that case, you could do the following:
from PyQt4 import Qt
app = Qt.QApplication([])
FilePath=Qt.QFileDialog.getOpenFileName(None,'choose the file')
print(FilePath)
...create widgets....
QTimer.singleShot(someWidgetMethod)
app.exec_()
The single-shot timer will fire only during the app.exec_(), thus calling some of your code as part of the event loop.
All that being said,it is much better to create a main window and connect slots to signals and call app.exec_(). There are many examples included with PyQt, check those.

Categories