wx's idle and UI update events in PyQt - python

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.

Related

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.

How can I get Items to show in my QListWidget before the end of the function?

I want my QListWidget to update with the new item as it is added, but it only updates with all of the items once the function has ended. I have tried using update() and repaint(), but neither work. I actually had to use repaint() on the Widget itself just to get it to show up before the end, but none of the items do. Here is a brief view of the first item to add:
def runPW10(self):
self.PWList.setVisible(True)
self.PWList.setEnabled(True)
# This repaint() has to be here for even the List to show up
self.PWList.repaint()
....
self.PWList.addItem('Executing Change Password Tool')
# This does not help
self.PWList.repaint()
....
There is more to the function, but it is long and this should include what it needed. Please let me know if more is required.
What am I doing wrong that makes this List not update as the item is added?
Add QApplication.processEvents().
QCoreApplication.processEvents (QEventLoop.ProcessEventsFlags flags = QEventLoop.AllEvents)
Processes all pending events for the calling thread according to the specified flags until there are no more events to process.
You can call this function occasionally when your program is busy performing a long operation (e.g. copying a file).
Your widget originally will be shown but unresponsive. To make the application responsive, add processEvents() calls to some whenever you add an item.
Do keep in mind that this can affect performance a lot. This lets the whole application loop execute including any queued events. Don't add this to performance sensitive loops.
Also consider that this allows your user to interact with the application, so make sure that any interactions that can happen either are not allowed, such as somebutton.enabled(False), or are handled gracefully, like a Cancel button to stop a long task.
See the original C++ docs for further information, since pyqt is a direct port.
To complete Drise's answer on this point:
Also consider that this allows your user to interact with the application, so make sure that any interactions that can happen either are not allowed, such as somebutton.enabled(False), or are handled gracefully, like a Cancel button to stop a long task.
You may want to use the QEventLoop.ExcludeUserInputEvents flag this way: QCoreApplication.processEvents(QEventLoop.ExcludeUserInputEvents) to refresh the GUI while preventing the user to activate any widgets.
QEventLoop.ExcludeUserInputEvents
0x01
Do not process user input events, such as ButtonPress and KeyPress. Note that the events are not discarded; they will be delivered the next time processEvents() is called without the ExcludeUserInputEvents flag.

PyQT equivalent of wx CallAfter?

I recently switched from wxPython to PyQT and can't find an equivalent of CallAfter. I need to use pubsub due to some imports and with wx I just sent messages with CallAfter -- is there a way to do something similar in PyQT? Basically, I want to inject something into the mainloop with pyQT.
EDIT FOR MORE INFO:
In my old GUI, using wxPython, I was using python-openzwave which uses an old dispatcher module. I would capture the old dispatcher signals and convert them to pubsub messages (for ease of use) and send the new messages with a CallAfter like this:
wx.CallAfter(pub.sendMessage, messagePack.signal, message = messagePack.message)
And then I was able to update the GUI by capturing the message and working directly on the gui elements because it essentially injected something into the mainloop.
Now, using pyqt, there is no callafter so, I have the same system setup without the callafter but the actions that have to occur after the message is received can't happen because it is in the middle of the mainloop.
The closest thing I can think of is using QTimer.singleShot with a short timeout, which will force it into the next event loop.
def other_function(self):
print 'other'
def my_function(self):
print 'one'
QTimer.singleShot(1, self.other_function)
print 'two'
Qt has the idea of an event loop, where it will check if there are events that need processing, like a button click, or part of a widget needs to be redrawn, etc. Typically, a function gets called as the result of an event. The QTimer.singleShot will stick your function call at the end of the list of things to be processed on the next cycle of the event loop.
But I agree with some of the comments that you probably could just use a separate QObject running in another thread to handle the openzwave events and re-dispatch the messages as Qt Signals, which the main thread can listen for and update the GUI.

Progress Bar over getOpenFileName

In my application I have the following line which opens a file dialog window. Once I get the file name, I do a bunch of processing which takes quite some time, and once this is done the workspace is ready for the user.
filename, _ = QtGui.QFileDialog.getOpenFileName(self, 'Open file', os.curdir, "*.cws")
The file dialog is a modal window (by default), which is great, because it's preventing the user from doing stupid stuff while the workspace is not ready for use yet. I want to put a progress bar somewhere to give a sense of how much has be processed. I made another dialog window which displays a progress bar and some other information.
Now, since the file dialog window is modal, it just sits there frozen while my workspace is processing, and the progress dialog only pops up after everything is done.
I've looked into setting the file dialog window to not modal, but I don't think that is possible. I was thinking to maybe force it to close, and immediately have my progress dialog window pop up and take over the modality. How can I close the file dialog window programmatically? I don't know how to get a reference for the form.
Or perhaps you have a better suggestion on how to address this?
As thuga mentioned, your application event loop is stuck by your heavy processing.
So events (and especialy paint events) are not processed while your processing is running causing the GUI to freeze.
In my opinion, you have 2 options:
Force events to be processesed (not very classy but may work):
It depends on how your "heavy processing" is done.
Assuming the code hanging the loop is "under your hands" (not in a third party lib).
You can add as much call to QApplication.processEvents as you can in it.
If the processing is loop based, it can look like:
for item in itemList:
...processitem...
QtGui.QApplication.processEvents()
This as the main drawback of adding dependencies to GUI in parts of code that should not be aware of.
If your code is not loop based then you'll have to add several calls to processEvents that will pollute the processing code.
Stop hanging the event loop (more complicated but more maintainable)
That means you will have to deal with Threads and/or subprocesses as thuga suggested.
This solution assumes that GUI code and business code are separated well enough.
You can have a look at this article from Qt Quarterly that gives some highlights on this issue.
Because of python Global Interpreter Lock (GIL) you may not see better results with threads.
Consider using the multiprocessing library.

Python / Pyside Code not executing sequentially?

I am building a Qt-based GUI using Pyside. Inside a specific class who has access to a QMainWindow (_theMainWindow) class which in turn has access to 2 other Qt Widgets (theScan & theScanProgress) I am trying to show() the last one by executing
def initiateScan(self):
self._theMainWindow.theScan.theScanProgress.show()
This works just fine, the theScanProgress widget appears.
However, when I add the line that makes the application sleep (and a print statement), as below
def initiateScan(self):
self._theMainWindow.theScan.theScanProgress.show()
print("test")
time.sleep(3)
the program seems to go to sleep BEFORE the widget appears, i.e. as if the time.sleep(3) gets executed before self._theMainWindow.theScan.theScanProgress.show()
Any ideas why this happens?
This is because of the main loop which processes gui events. If you are not using threads you can only execute one function at a time. I strongly suspect that show emits a signal which goes into the event queue, which is in turn blocked until the current function returns.
Put another way, Qt is event driven, and it can only do one event at a time. What ever you did call initiateScan added an event to the stack that was to execute the function (something like you pushed a button, which emitted a signal, which then triggered the function), and that function can do some computation, alter internal state of your objects, and add events to the stack. What show does underneath is emit a signal to all of it's children for them to show them selves. For that code to run, it has to wait for the current event (the function with your sleep) to return. During the sleep the entire gui will be unresponsive for the exact same reason.
[I probably butchered some of the terms of art]
show only schedules the appearance of the progress widget. But since you are blocking the main thread with your sleep, the main thread cannot perform the scheduled action until you release it.
You have to use threads or find another way to wait 3 seconds.

Categories