I've a QTableView in a pyqt application. I continuously append rows to the underlying model. And what I want is the view to continuously scroll to the last, most recent row (is that behavior called "autoscrolling"?). But instead, the view does not scroll at all (automatically) and stays at its position.
Can I enable this autoscrolling behavior somehow or do I need to code something to achieve it?
Cheers,
Wolfgang
There is no default autoscrolling feature, but you can get the behavior relatively simple. Your model will emit rowsInserted when you insert/append rows. You can connect to that signal and call scrollToBottom on your view.
There is one problem though. View needs to adjust itself, because it won't put the item at the bottom immediately when rowsInserted fires. Calling scrollToBottom within a QTimer.singleShot solves this because QTimer will wait until there are no pending events (like update of the view).
Assuming the model is stored as self.model and view is self.view, this is how it'll look:
self.model.rowsInserted.connect(self.autoScroll)
and the autoScroll method:
def autoScroll(self):
QtCore.QTimer.singleShot(0, self.view.scrollToBottom)
Or if you prefer not having a seperate method for this:
self.model.rowsInserted.connect(lambda: QtCore.QTimer.singleShot(0, self.view.scrollToBottom))
Related
I have a simple task but am struggling with the proper way to implement it:
I want one shared QStandardItemModel containing a tree structure to be displayed in two different QTreeViews.
The first QTreeView shows the model as-is and allows the user to add, remove and rearrange the items. Clicking an item shows an item-associated QWidget within a QStackedWidget below the QTreeView. Some item types can also be renamed.
The second QTreeView shows the same tree model, however all items are greyed out by default. A Processor will then move along the tree structure and process each item. Whenever an item is processed, it should not longer be greyed out. Those items not greyed out should be clickable to display the item-corresponding processed image in a viewer. Whenever a change is made in the first QTreeView, the Processor starts processing from the changed position onwards again.
How do I implement the different look and functionality of the two QTreeViews? Do I assign custom QItemDelegates to each QTreeView? Should I use a QIdentityProxyModel for the second QTreeView?
It is only necessary to use a QStandardItemModel for both models since they store the same information so if you want them to look different then you must use delegates.
If you want some action when the view is clicked then that does not depend on the model but you have to use the view clicked signal that is independent of the other view.
For the second view if a delegate is required to change the color, the logic is to use a role to indicate if that item has been processed or not, and then change the color based on that role. When you say "a change in the first QTreeView" you should actually say "a change in the model" so it is only necessary to use the dataChanged signal, process the item by changing the role status before and after processing.
I am trying to get click events (ultimately left, right and double click) to "pass through" an editor widget to the underlying QListView so that selections can happen. Figured Event Filters were probably the way to go, but I am a little confused as to where the eventFilter(object,event) function and the installEventFilter() call need to go in order for that to happen.
In my case, I am using a custom delegate class to draw my data in my QListView and am using an editor to update the model based upon cursor positions. I want this to just be constantly active, so I made my QListView active the editor upon entering an item.
dataView=QListView(self)
dataView.setGeometry(self.rect())
dataView.setViewMode(1)
dataView.setMovement(0)
dataView.setSelectionMode(QAbstractItemView.ExtendedSelection)
dataView.setMouseTracking(True)
dataView.entered.connect(dataView.edit)
delegate=CustomDelegate(self)
dataModel=QStandardItemModel(self)
dataView.setModel(dataModel)
dataView.setItemDelegate(delegate)
The downside to this is that while your mouse is on an item, it is now covered by the editor widget which I believe is collecting mouse click data thus blocking clicks from selecting the items within the QListView.
In my delegate, I have it create the editor as so and connect signals to update my data (frames) in my model to change the way the delegate displays and to close the editor
def createEditor(self,parent,option,index):
editor=TestEditor(parent)
editor.editingFinished.connect(self.deleteEditor)
editor.updateFrame.connect(self.updateFrames)
return editor
Where would I create my event filter? In my main where I am generating my QListView? Within a custom QListView? Within my Delegate? Or within my editor widget? And then where would I call the installEventFilter()?
It turns out in my case, the QListView wasn't responding to selection clicks because when you call the edit() function, the state of the viewer changes to "QAbstractItemView.EditingState" and in this state, apparently selection is disabled. I just subclassed QListView and added a beginEdit function.
class CustomList(QListView):
def __init__(self,parent=None):
super().__init__(parent)
def beginEdit(self,index):
state=self.state()
self.edit(index)
self.setState(state)
And then just connected this instead of edit.
dataView=CustomList(self)
dataView.entered.connect(dataView.beginEdit)
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.
I have found several questions that looks similar to this such as:
Busy indication with PyQt progress bar
Progress bar with pyqt
Display installation progress in PyQt
But most of the examples and answers were on 1 GUI thread and 1 data processing thread.
Let me explain my scenario.
I have 3 classes:
class MainWindow (Inherited from QMainWindow class and act as main GUI)
class LogInWidget (Inherited from QWidget class.This class consists of all the username/password field + a progress bar, pls see the image below)
3.class DataTableWidget (Inherited from QWidget class.consist of a table that display a few hundreds thousands of data)
The program is designed so that once a user logged in to the program via LogInWidget, he will be able to see all the data displayed by the DataTableWidget.
I use .setCentralWidget method to set both LogInWidget and DataTableWidget.(ie.if the user password is correct, i use
table = DataTableWidget ()
self.setCentralWidget(table)
to display the table.
My problem is: When user logged in, there is a big GUI freeze before he can see the data table as there are so many data entry to display via the table.
So I would like to show a busy progress bar (on my LogInWidget) before the program finish loading the table.
I thought of using Qthread to accomplish it but since both tasks, displaying progress bar and creating DataTableWidget and setting as centralwidget is GUI tasks, how should i approach it?
You would have to connect your progress bar value to a signal from the DataTableWidget. But this in turn assumes that the DataTableWidget generates this signal many times while rendering it, which is not likely. Perhaps all you need is a way to show that the GUI is busy, like bar with a line on it that goes back and forth between the two ends of the bar. Then you don't need to exchange data between the the two widgets.
There is no straight forward way to do it. However, here is an idea:
As #schollii says, you need a signal to be emitted by DataTableWidget while it is rendering. Such a signal does not exist. And even if it did, it wouldn't be processed until the widget was rendered
However, if DataTableWidget has a model, you could override the data() method with your own which just calls the data() method of the base class and also updates a progress bar (though only update the progress bar when first rendering the table, not during subsequent calls to data()). As the view renders, the view will be calling the data() method many times. This way you get access to points in time during the rendering process.
Of course the progress bar will not redraw until control returns to the event loop (the rendering finishes), but you could probably work around that by calling QApplication.processEvents().
Note that this is quite hacky, in general bad practice, and completely destroys the model-view paradigm. I don't recommend doing it. But I can't think of anyway to achieve the same outcome. Perhaps you could just speed up the rendering of the DataTableWidget. For instance, QTableView.resizeColumnsToContents() is known to be slow. Are you calling that anywhere?
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.