About a PyQt example program - python

I'm currently need GUI library for a project. I'm familiar with python and found PyQt might be a good choice.
I'm reading a tutorial about PyQt, and quite confused about the following example program
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""
ZetCode PyQt4 tutorial
In this example, we draw text in Russian azbuka.
author: Jan Bodnar
website: zetcode.com
last edited: September 2011
"""
import sys
from PyQt4 import QtGui, QtCore
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
self.text = u'\u041b\u0435\u0432 \u041d\u0438\u043a\u043e\u043b\u0430\
\u0435\u0432\u0438\u0447 \u0422\u043e\u043b\u0441\u0442\u043e\u0439: \n\
\u0410\u043d\u043d\u0430 \u041a\u0430\u0440\u0435\u043d\u0438\u043d\u0430'
self.setGeometry(300, 300, 280, 170)
self.setWindowTitle('Draw text')
self.show()
def paintEvent(self, event):
qp = QtGui.QPainter()
qp.begin(self)
self.drawText(event, qp)
qp.end()
def drawText(self, event, qp):
qp.setPen(QtGui.QColor(168, 34, 3))
qp.setFont(QtGui.QFont('Decorative', 10))
qp.drawText(event.rect(), QtCore.Qt.AlignCenter, self.text)
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Here, in main function, an Example object is created, thus __init__() function, initUI() is called.
My question is where does paintEvent() function is called?? since if we run the program, self.text(some Russian letters) will exactly appear on the widget.
In other words, what does sys.exit(app.exec_()) actually do? why it will call paintEvent() function?
Thanks!

From PyQt docs:
int QApplication.exec_ ()
Enters the main event loop and waits until exit() is called, then
returns the value that was set to exit() (which is 0 if exit() is
called via quit()).
It is necessary to call this function to start event handling. The
main event loop receives events from the window system and dispatches
these to the application widgets.
From another source:
sys.exit(app.exec_())
Finally, we enter the mainloop of the
application. The event handling starts from this point. The mainloop
receives events from the window system and dispatches them to the
application widgets. The mainloop ends, if we call the exit() method
or the main widget is destroyed. The sys.exit() method ensures a clean
exit. The environment will be informed, how the application ended.
The exec_() method has an underscore. It is because the exec is a
Python keyword. And thus, exec_() was used instead.
About painting:
4.2.1. When Painting Occurs
The paintEvent() method is called automatically when
Your widget is shown for the first time.
After a window has been moved to reveal some part (or all) of the
widget.
The window in which the widget lies is restored after being minimized.
The window in which the widget lies is resized.
The user switches from another desktop to the desktop on which the
widget's window lies.
You can generate paint events manually by calling QWidget::update().
QWidget::update() erases the widget before generating the paint event.
You can pass arguments to update(), which can restrict painting only
to areas (rectangles, in particular) that need it. The two equivalent
forms of the method are QWidget::update (int x, int y, int width, int
height) and QWidget::update (QRect rectangle), where x and y give the
upper-left corner of the rectangle, and width and height are obvious.
Because update() places a paint event into the event queue, no
painting occurs until the current method exits and control returns to
the event handler. This is a good thing because other events may be
waiting there to be processed, and events need to be processed in a
timely manner for the GUI to operate smoothly.
You can also invoke painting of the widget by calling QWidget::repaint (int x, int y, int width, int height, bool erase) (or one of several
convenience-method forms), where all the arguments mean the same as in
the case of the update() method, and erase tells repaint whether to
erase the rectangle before painting it. repaint() calls paintEvent()
directly. It does not place a paint event into the event queue, so use
this method with care. If you try to call repaint() repeatedly from a
simple loop to create an animation, for example, the animation will be
drawn, but the rest of your user interface will be unresponsive
because the events corresponding to mouse button clicks, keyboard
presses, and so on will be waiting in the queue. Even if you are not
performing a task as potentially time-consuming as animation, it is
generally better to use update() to help keep your GUI alive.
If you paint something on your widget outside the paintEvent(), you
still need to include the logic and commands necessary to paint that
same thing in paintEvent(). Otherwise, the painting you did would
disappear the next time the widget is updated.

It becomes more clear when one has some experience with low level programming,
for example in Winapi or the X toolkit in C language. PyQt is a (very) high level toolkit.
It comes with a huge built-in functionality. Similar example would require hundreds or maybe thousands of lines of C code. As a consequence, there is a lot of going on
behind the scenes. Somebody already created code that deals with painting on a basic level.
GUI programming is very complex and with modern GUI toolkits the application programmer is shielded from this complexity. It is inevitable that programmers are confused if they do not know all the technical details.
In PyQt, we are essentially dealing with events in two ways. We connect signals to slots or reimplement event handlers (an event handler is a synonym for a slot). The provided example inherited from the QtGui.QWidget which already has some painting code available.
In order to do our custom painting, we have to reimplement the existing paintEvent() event
handler. Depending on the situation, we may or may not call the parent's paintEvent() method.
The sys.exit(app.exec_()) does not call the paintEvent() method. The exec_() method starts an event loop. The event loop catches and dispatches events. Paint events are triggered by users or by the operating system. For example, when we launch the example, the
paint event is triggered twice. (Put a print "Event occurred" line in the event handler to see how many times this method is called.) Resizing windows, loosing or gaining focus, minimizing or maximizing windows, all these cause painting events to be triggered.

app.exec_() starts the qt main loop, which ends when every created widget is destroyed (e.g. by closing its window). The paintEvent function is a method that you can overload from a QWidget subclass like the given Example class, which gets called when QT displays, updates or repaints the Widget.
You can look up these things in the Qt documentation or the PyQt Documentation (which is mostly just a copy of the QT Documentation in a different format, but sometimes contains some valuable information regarding PyQt-specific things).

Related

How do I paint over an QAbstractSlider?

I've got this cool widget I'm working on and I simply want to paint this central straight line, over the slider widget that I've created.
Since the PaintEvent activates at initialization, anything called in the main class is displayed above the painted lines. With this in mind, can I call a paintevent function to initialize after my slider widgets have already been called/initialized?
Moreover, would it be smart to use threading.event() to stall the paint event function until after these sliders have been called, when I set the event?
Any advice would be great
*** UPDATE ***
event.is_set() blocking doesn't have an effect and the painter still paints below the QAbstractSlider

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

Correct way to close QMainWindow

I recently changed from tkinter to Pyqt5 as I'm developing a semi-large application in Python 3.7.8
Every time I had to close windows I used the method self.destroy(), and there was a small chance that, when I closed all the program and having no windows, the interpreter was still running and I needed to terminate the process manually, even when using sys.exit(app.exec_())
I could had the program running for 15 seconds or 30 minutes, it was completely random.
I just saw another method that is called self.close(), so I replaced the self.destroy() with it, but I'm not sure if this is the intended practice or if there is a better way. I still have to check if the problem doesn't appear again.
It's better to use self.destroy or self.close for pyqt5 applications?
Is there a better way?
close():
Closes this widget.
destroy():
Frees up window system resources.
[...]
This function is usually called from the QWidget destructor.
If you close() the widget, it can be opened/shown again later if required, but if the widget is a top level window and is the last visible one, Qt will automatically quit the application (assuming the QApplication has the quitOnLastWindowClosed() set, which is the default behavior). In this case PyQt will automatically destroy the window and free up memory, meaning that destroy() will be called anyway.
Note that the window will also be automatically destroyed when closed if it has no other reference or parent: as much as any other python object, the garbage collector will delete the widget and its children, which causes a call to the QWidget destroyer.
So, you should always call close(), since it ensures that Qt follows the correct steps: send a QCloseEvent (which could be ignored, if required) and notify the application about that, so that it can actually quit if the window was the last one.

QPixmap not safe outside GUI thread

My program has the main GUI thread to handle the user interface.
Another thread is started to handle the hard work (loops, calculations, etc) without freezing the main GUI.
From my "calculations thread" I am using another module draw_plots which only draws and saves many kinds of plots.
import draw_plots as plots
class calculatorThread(QtCore.QThread):
signal1 = QtCore.pyqtSignal(int, int)
signal2 = QtCore.pyqtSignal(int,int)
signal3 = QtCore.pyqtSignal(int)
def __init__(self,input_file_txt, parameter_file_txt):
QtCore.QThread.__init__(self)
#and etc etc
At some point of this thread I am calling:
plots.stacked_barplot(*arguments)
Everything works fine, however, I get a message on the screen many many times:
QPixmap: It is not safe to use pixmaps outside the GUI thread
Would like to know what am I doing wrong and how to avoid this message.
Well, you issue the plot command from the calculator thread, which in turn uses a QPixmap to draw the plot - all from inside your calculator thread.
Ideally, you shouldn't draw from the calculator thread, but e.g. emit a signal that you're ready for plotting - and do the plot in the main thread. Maybe along the following lines:
class calculatorThread(QtCore.QThread):
plot_emit = QtCore.pyqtSignal()
def run(self):
self.plot_args = your_calculation()
self.plot_ready.emit()
Outside, connect the plot_ready signal to your plot command:
calculator.plot_emit.connect(lambda x:
plots.stacked_barplot(*calculator.plot_args))
You should use a QImage if the computation is done in a separate thread. You can also safely access QImage.bits() and do your image processing directly working on pixel data (a lot faster).
As the warning is stating clearly, QPixmap objects should be used only from the GUI thread. You can convert the computed QImage into a QPixmap if you really need a QPixmap (but you can for example draw QImages directly on a QPainter).

wx's idle and UI update events in PyQt

wx (and wxPython) has two events I miss in PyQt:
EVT_IDLE that's being sent to a frame. It can be used to update the various widgets according to the application's state
EVT_UPDATE_UI that's being sent to a widget when it has to be repainted and updated, so I can compute its state in the handler
Now, PyQt doesn't seem to have these, and the PyQt book suggests writing an updateUi method and calling it manually. I even ended up calling it from a timer once per 0.1 seconds, in order to avoid many manual calls from methods that may update the GUI. Am I missing something? Is there a better way to achieve this?
An example: I have a simple app with a Start button that initiates some processing. The start button should be enabled only when a file has been opened using the menu. In addition, there's a permanent widget on the status bar that displays information.
My application has states:
Before the file is opened (in this state the status bar show something special and the start button is disabled)
File was opened and processing wasn't started: the start button is enabled, status bar shows something else
The processing is running: the start button now says "Stop", and the status bar reports progress
In Wx, I'd have the update UI event of the button handle its state: the text on it, and whether it's enabled, depending on the application state. The same for the status bar (or I'd use EVT_IDLE for that).
In Qt, I have to update the button in several methods that may affect the state, or just create a update_ui method and call it periodically in a timer. What is the more "QT"-ish way?
The use of EVT_UPDATE_UI in wxWidgets seems to highlight one of the fundamental differences in the way wxWidgets and Qt expect developers to handle events in their code.
With Qt, you connect signals and slots between widgets in the user interface, either handling "business logic" in each slot or delegating it to a dedicated method. You typically don't worry about making separate changes to each widget in your GUI because any repaint requests will be placed in the event queue and delivered when control returns to the event loop. Some paint events may even be merged together for the sake of efficiency.
So, in a normal Qt application where signals and slots are used to handle state changes, there's basically no need to have an idle mechanism that monitors the state of the application and update widgets because those updates should occur automatically.
You would have to say a bit more about what you are doing to explain why you need an equivalent to this event in Qt.
I would send Qt signals to indicate state changes (e.g. fileOpened, processingStarted, processingDone). Slots in objects managing the start button and status bar widget (or subclasses) can be connected to those signals, rather than "polling" for current state in an idle event.
If you want the signal to be deferred later on in the event loop rather than immediately (e.g. because it's going to take a bit of time to do something), you can use a "queued" signal-slot connection rather than the normal kind.
http://doc.trolltech.com/4.5/signalsandslots.html#signals
The connection type is an optional parameter to the connect() function:
http://doc.trolltech.com/4.5/qobject.html#connect , http://doc.trolltech.com/4.5/qt.html#ConnectionType-enum
As far as I understand EVT_IDLE is sent when application message queue is empty. There is no such event in Qt, but if you need to execute something in Qt when there are no pending events, you should use QTimer with 0 timeout.
In general, the more Qt-ish way is to update the button/toolbar as necessary in whatever functions require the update, or to consolidate some of the functionality and directly call that function when the program needs it (such as an updateUi function).
You should be aware that in Qt, changing an attribute of a Ui element doesn't cause an immediate redraw, but queues a redraw in the event system, and multiple redraw calls are compressed into one where possible.
As for the multiple changes relating to state, have a look at this blog post about a hopefully-upcoming addition to Qt to more easily handle states. It looks like this would take care of a lot of your complaints, because in your multiple functions, you could just transition the state variable, and the other parts of the UI should update to match. It's not positive this will make it into the next Qt release (although I would bet on it, or something similar), and I have no idea how closely PyQt tracks the Qt releases. Or alternately, you could use the concept and create your own class to track the state as needed.

Categories