11

I'm developing a GUI with PyQt. The GUI has a qListWidget, a qTableWidget, and a plot implemented with Mayavi. The list refers to shapes that are plotted (cylinders and cones for example). When a shape is selected in the list, I want the shape's properties to be loaded into the table (from a dictionary variable) and the shape to be highlighted in the plot. I've got the Mayavi plotting working fine. Also, if the table is edited, I need the shape to be re-plotted, to reflect the new property value (like for a cylinder, if the radius is changed).

So, when a list item is selected -> update the table with the item's properties (from a dictionary variable), highlight the item on the plot

When the table is edited -> update the dictionary variable and re-plot the item

The Problem: when I select a list item and load data into the table, the qTableWidget ItemChanged signal fires every time a cell is updated, which triggers re-plotting the shape numerous times with incomplete data.

Is there a typical means of disabling the GUI event loop while the table is being programmatically updated? (I have experience with Excel VBA, in that context setting Application.EnableEvents=False will prevent triggering a WorksheetChange event every time a cell is programmatically updated.) Should I have a "table update in progress" variable to prevent action from being taken while the table is being updated? Is there a way to update the Table Widget all at once instead of item by item? (I'll admit I'm intentionally avoiding Model-View framework for the moment, hence the qListWIdget and qTableWidget).

Any suggestions?

I'm a first time poster, but a long time user of StackOverflow, so I just want to say thanks in advance for being such an awesome community!

ekhumoro
  • 115,249
  • 20
  • 229
  • 336
flutefreak7
  • 2,321
  • 5
  • 29
  • 39
  • 1
    In case others land here: PyQt provides the blockSignals() method for all QObjects (all widgets and anything that can send signals). http://doc.qt.digia.com/stable/qobject.html#blockSignals To prevent a widget from sending signals, widget.blockSignals(True) will do the trick. You can check the blockage status with widget.signalsBlocked(). Turn signals back on with widget.blockSignals(False). This is especially useful while populating a widget during which the itemChanged signal may not be desired. – flutefreak7 Dec 07 '12 at 23:13

3 Answers3

17

blockSignals(bool) is intended for suppressing QObjects and their subclasses from emitting signals, thus preventing any other objects from receiving them in slots. But this is a QObject method. If you are specifically trying to prevent one object from emitting signals in response to changes that you are making, which might trigger calculations or some other expensive processing in a slot, then this is what you want.

But if your situation is that making repeated changes is causing expensive paint operations over and over (or other expensive events being generated on the widget), then you have the ability to disable updates with updatesEnabled(bool). A benefit of this method is that it recursively disables the children of the target widget, preventing them from being updated as well. So nothing in the hierarchy will receive updates until you enable again.

mainWidget.setUpdatesEnabled(False)
# do a bunch of operations that would trigger expensive events
# like repaints
mainWidget.setUpdatesEnabled(True)

Ultimately it depends on whether the source of your problem comes from triggering signals, or triggering widget events. Blocking the signals will still allow the widget to process its events, but just not notify any other listeners about it. updatesEnabled is a common way to wrap a number of list/table/tree updates. When it is enabled again afterwards, a single post update will be performed.

jdi
  • 90,542
  • 19
  • 167
  • 203
  • Thanks for the additional information about updatesEnabled(). This will likely come in handy for other operations. The hierarchical nature is nice since I could disable a layout element or a whole tab without disabling the entire GUI. – flutefreak7 Dec 11 '12 at 15:41
13

Signals can be temporarily blocked for any object that inherits QObject:

self.tableWidget.blockSignals(True)
# perform updates, etc
self.tableWidget.blockSignals(False)
ekhumoro
  • 115,249
  • 20
  • 229
  • 336
0

If you disable the entire event loop, the app becomes unresponsive. And, even if the user doesn't notice, the OS might, and put up some kind of "hang" notification (like OS X's brightly-colored spinning beachball, which no user will ever miss).

You might want to disable repaints without disabling the event loop entirely. But even that's probably too drastic.

All you're really trying to do is make sure the table stops redrawing itself (without changing the way you've implemented your table view, which you admit isn't ideal, but you have reasons for).

So, just disable the ItemChanged updates. The easiest way to do this, in almost every case, is to call blockSignals(True) on the widget.

In the rare cases where this won't work (or when you're dealing with ancient code that's meant to be used in both Qt4-based and earlier projects), you can still get the handler(s) for the signal, stash them away, and remove them, then do your work, then restore the previous handler(s).

You could instead create a flag that the handlers can access, and change them so they do nothing if the flag is set. This is the traditional C way of doing things, but it's usually not what you want to do in Python.

abarnert
  • 354,177
  • 51
  • 601
  • 671
  • 1
    Thanks! Hadn't thought of just disabling the signal itself (Still getting used to signal-slot structure). Your response lead me to Google "pyqt disable signal" which lead to a relavant StackOverflow question: http://stackoverflow.com/questions/7323295/suppress-pyqt-event-temporarily which reveals the blockSignals() method. Using table.blockSignals(True) before updating the table and table.blockSignals(False) afterwards produces the desired effect! – flutefreak7 Dec 07 '12 at 23:06
  • Yeah, `blockSignals` is actually better than stashing their handlers (and +1 to ekhumoro for suggesting it), unless you're writing very odd code (where you have objects in the system that aren't real `QObject`s, or where you want to use the same code in Qt3 and Qt4, or…). Apologies for not suggesting that first, and glad you managed to find it anyway. – abarnert Dec 07 '12 at 23:16