Turn off PyQt Event Loop While Editing Table - python

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!

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.

Signals can be temporarily blocked for any object that inherits QObject:
self.tableWidget.blockSignals(True)
# perform updates, etc
self.tableWidget.blockSignals(False)

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.

Related

How to know when an AbstractItemModel is finished updating in Qt Squish

I'm writing a unit test for a Qt application using the Squish framework. The squish scripting language is Python.
In order to make my test robust, I need to make sure that the GUI has completed an operation before checking the results.
In this GUI, I have a QTableView with an associated model. Certain operations will change the data in the model and the table will update. The update is sequenced internally by Qt.
My issue is that I need to know when the table has completed updating before I check to see whether it has the correct data.
I'm looking for ideas how to do this.
Squish not only views the QTableView GUI surface but will also access the underlying QAbstractItemModel. The data you see in your checks should therefore be live already.
But Qt GUIs are indeed full of asynchronous processing through timers, sometimes threads and signals. If you want to be notified on changes that are accompanied by a signal there's the installSignalHandler() function. If you want to wait for a particular state of to appear there's the waitFor() function. In the case of the table you may want to use
waitFor("mytable.model().rowCount() == 30")
for example.

wxpython: EVT_TREE_SEL_CHANGED event in treectrl in notebook when created

I am learning wxpython and have a question.
When I create a treectrl within framework and call framework.show(), the first item in the treectrl is automatically selected (i.e., EVT_TREE_SEL_CHANGED event is fired). However, when I create a treectrl in a panel, add the panel to a notebook and add the notebook to framework, the EVT_TREE_SEL_CHANGED event is not fired when the framework.show() is called. Instead, when I select an item in the treecontrol later after the initial rendering, two EVT_TREE_SEL_CHANGED are fired (one for the first item which is supposed to be fired during the initial rendering and the other one for the selected item).
panel.SetFocus() in the bottom of framework.__init__() fix this problem -- i.e., fires EVT_TREE_SEL_CHANGED to select the first item during the initial rendering. But, I wonder why this is happening. Does anybody know why EVT_TREE_SEL_CHANGED is blocked in the initial rendering when the tree control is contained in the panel of notebook?
I don't know why exactly does this happen but this looks like a bug in wxWidgets. In practice, this means that it you shouldn't rely on this behaviour because it might (and in fact I'm pretty sure it does) behave differently under other platforms and also could change in future wxWidgets versions.

The order of wxPython events is causing me problems

My GUI consists of a wx.ListCtrl on the left, with a list of objects to edit, and a set of wx.TextCtrls on the right, for editing the selected object.
My strategy for implementing this was:
On a textbox's wx.EVT_KILL_FOCUS , update the relevant attribute of the currently selected object
On the list's wx.EVT_LIST_ITEM_DESELECTED, either hide the textboxes or blank them out and disable them (needed for when the user clicks the blank space in the list control)
On the list's wx.EVT_LIST_ITEM_SELECTED, populate the text controls with the values of the selected object's attributes
With this setup, there are 3 use cases, and 2 of them work:
When the user is clicking/tabbing between textboxes, the correct wx.EVT_KILL_FOCUSs occur, and the attributes get updated.
When the user clicks from a textbox into the blank space in the list, that's OK as well: first wx.EVT_KILL_FOCUS causes the attribute to update, and then wx.EVT_LIST_ITEM_DESELECTED hides the textboxes.
The problem is when the user clicks directly from a textbox to another object in the list control. The order of events in this case is wx.EVT_LIST_ITEM_DESELECTED, wx.EVT_LIST_ITEM_SELECTED, and then finally wx.EVT_KILL_FOCUS. You can probably see the problem: by the time the method that updates attributes is called, a new object has already been selected and the textboxes have been populated with new values.
So I know exactly what the problem is, but I can't come up with a nice, clean way to fix it. Ideally I'd like to be able to change the order of the wx events (putting wx.EVT_KILL_FOCUS at the front), but I doubt that's possible. Is there some other obvious solution I'm missing?
wx.EVT_LIST_ITEM_DESELECTED will only fire when the user changes the selected object in the list box. This serves the same purpose as losing focus on the text box. Call the update routines from that event as well. To skip the subsequent wx.EVT_KILL_FOCUS from the text box set a "isDirty" attribute in the parent object after you update the attributes. You can check the isDirty value anytime to confirm there are changes to commit. This attribute would have to be reset when you populate the text boxes for the new selection and then set during other textbox events.
It looks to me like you are trying to re-implement from scratch the functionality of wxListbook. It seems like a lot of work, perhaps you can use wxListbook
to do what you need.
http://docs.wxwidgets.org/2.9.4/classwx_listbook.html

How to handle button states efficiently in Tkinter

I've done a few searches but I couldn't find anything about this topic. Perhaps because it is common programmer knowledge (I'm not a programmer, I've learned from necessity), or because I'm going about it the wrong way.
I would like ideas/suggestions on how to manage button states for a GUI. For example, if I have a program which allows the user to import and process data, then certain functions should be inaccessible until the data has been imported successfully, or if they want to graph certain data, they need to select which data to graph before hitting the 'graph' or 'export' button. Even in the simple programs I've built these relationships seems to get complicated quickly. It seems simple to say "User shouldn't be able to hit button 'A' until 'B' and 'C' have been completed, then 'A' should be disabled if button 'D' or the 'Cancel' button. But that's a lot to track for one button. Thus far, I've tried two things:
Changing/Checking button states in the callback functions for the button. So in the above example, I would have code in buttons B's and C's callback to check if A should be enabled. And in buttons D's and Cancel's callbacks I would have code to disable button A. This gets complicated quickly and is difficult to maintain as code changes.
Setting boolean variables in every buttons callback (or just checking the states later using cget()) and checking the variables in a polling function to determine which buttons should be enabled or disabled.
I'm just not sure about this. I would like to make code as short and easy to understand as possible (and easy to edit later), but I don't like the idea of polling all the button states every few hundred milliseconds just for button 'management'. You can extend the same idea to check boxes, menu items, etc... but I'd like to here what others have done and why they do it the way they do.
You are only changing button states based on events, right? There is no reason to 'poll' to see if a button state has changed. What you can do is build a function which does all of the calling for you, then call it with something like disable_buttons([okButton, graphButton, printButton]). When an event takes place that modifies the appropriate user interface options (such as importing data), have another function that turns them on: enable_buttons([graphButton]). You could do this with each object's methods, of course, but making a wrapper allows you to be consistent throughout your application.

wxpython: How do I examine dragged data in OnDragOver?

I'm a bit perplexed by drag and drop in wxPython (but perhaps this questions pertains to drag and drop in other GUI frameworks as well). The frameworks provides a couple of callbacks (OnEnter and OnDragOver) that purportedly allow me to inform the system whether the current mouse position is a valid place to drop whatever it is that is being dragged. From these methods I can return wx.DragNone, wx.DragCopy, etc. What baffles me is that from within these methods I am not allowed to call GetData, which means I am not allowed to examine the data that the user is dragging. If I cannot see the data, how am I supposed to know whether it is OK for the user to drop here?
One solution, which is a hack of limited usefulness, is when a drag is initiated, store the dragged data in a global or static reference somewhere. This way, in the OnEnter and OnDragOver handlers, it is possible to get a reference to the data being dragged. This is of course only useful for drags within the same application (the same instance of the application, actually).
There is no way to see dragged data in OnEnter and OnDragOver methods.
The only solution I found is to store the dragged item in some instance variable that is then readable inside these methods.

Categories