As the title says, in a program I'm making I have a QGraphicsView that contains a lot of items, about half of which are pixmaps. Obviously this is resource intensive, and I can't expect the display to update at 60 fps like I would prefer. That's fine, but it's important that the rest of the widgets perform crisply. Is there a way to render a QGraphicsView in a separate thread, or otherwise update it synchronously so it doesn't block my other widgets?
EDIT - Screenshot:
http://imgur.com/VzE0jWp
Related
I read the following in the Qt Documentation.
Qt documentation on QPainter
The original question on SO, that I looked into.
So, I had two classes, with their own paint() functions. The paint functions would be called upon receiving their respective paint events, that were triggered on different and independent actions by the user. This worked fine.
Now for some reason, I need to show and update both the objects at the same time.
So simply, adding both of the items to the scene does not work. Only one of them is shown and updated. Refactoring the code is not an issue for me. I can re-arrange the two classes so that they are both drawn from one paint().
But this really makes me wonder then, and this is my question (for which I've googled a bit too), how are scenes with many dozens of objects then painted simultaneously (at least they give an illusion of concurrency)? Using threads somehow or through some time-based interleaving?
Maybe it's a silly question. I dunno.
It is indeed a silly question about an imaginary problem that doesn't not exist in reality. The graphics view will schedule consecutive draws for the items in the order needed to produce the desired result. Now if your code doesn't implement the desired result, that's a whole different subject. There is no concurrency, those are consecutive operations that only take place in the main thread.
If your drawing is very complex, draw using a secondary thread on a QImage and use the QImage as cache to draw your items in their respective paint functions.
Now for some reason, I need to show and update both the objects at the
same time.
What might that reason be? What does "at the same time" mean? In a single frame? Is a millisecond apart too much to qualify for "at the same time"?
Re QWidget painting: The paint events are delivered to individual widgets by the widget compositor. The way it works with the default raster back end is as follows: The topmost widget in the hierarchy is backed by a QImage. When any of the sub-widgets are to be repainted, the compositor delivers composite paint events to the widgets that overlay the area to be repainted. This is done sequentially as the compositor traverses the widget graph.
Re QGraphicsItem painting: The paint "events" are delivered to individual items by the scene. The items to be painted are selected basing on what area needs updating, what items were explicitly marked for update, etc. The painter is set up to correctly composite the item with the rest of the scene. The calls to paint are done sequentially as the scene traverses the item graph.
It would be, in general, impossible to do these in parallel due to data dependencies, and the fact that there are no requirements for the paintEvent or paint to be thread-safe.
Your problem is not directly related to this at all, you need to show a complete code example that reproduces your issue. Most likely your implementation of the item ignores some of the requirements for the item's behavior.
I'm having performance issues with a program I'm making using Qt. The problem stems from the large number of bitmaps I have updating every 16 ms; it takes about 300 ms to update them all. I'm not happy about this, but the bigger problem is the lag this creates in the rest of the UI. I would like to be able to reduce the priority of the updates so that the massive number of paintEvents don't block the event loop for the rest of the UI, but I'm having difficulty. Since update() and repaint() don't have a priority parameter, I tried using QCoreApplication::postEvent(), but it seems I'm not allowed to call paintEvent in this way because I get this error message:
QPainter::begin: Paint device returned engine == 0, type: 1
QPainter::setOpacity: Painter not active
QPainter::setFont: Painter not active
QWidget::paintEngine: Should no longer be called
Here is the source of my problems, an array of 240 QLabels that I update all at once every 16 ms:
if (ui->objectSlotTabs->currentIndex() == 1) {
for (int c = 0; c < 240; c++) {
QEvent* event = new QEvent(QEvent::Paint);
QCoreApplication::postEvent((*(ui->mArray))[c], event, -1);} }
EDIT: Here's an example image of what the program does and why the QLabels are so important (My program is the window on the left):
There are two solutions (among many):
While you update the labels, disable the updates on the parent widget of the labels. Re-enable the updates when you're done updating the labels.
Use a QGraphicsView and place QGraphicsPixmapItems within it, instead of labels.
Prioritizing the paint events won't help, since they are all for different widgets. If they were all for the same widget, then you wouldn't need to do anything, since the events are already coalesced and only one repaint for a given widget can ever exist in the event loop.
I am writing a central display widget, it contains many small cell widgets where each of them have some QLabels to display some text info.
I have about 100 QLabels in total. And each of them is updated in around 2 Hz.
Then my GUI thread becomes very lag...
What is the possible solutions out there?
The 100 QLabels are in a scroll area btw.
As you use a scroll area, I guess, you don't show all 100 labels at a time, right? Than you have to update only those labels that are visible. For that reason I would suggest using Qt Model-View-Controller (MVC) classes, such as QTableView, etc. That will ensure that only visible items (cells) will be processed, and performance wise this approach will be much efficient.
I'm trying to time an application to see how long it takes to load up some information, and paint a graph. My function loads up the data first, then draws the graph.
The timing is fairly simple, it calls an external function that gets msecs since some date.
The problem is even if I set t1 in the beginning and t2 right after I call the draw function, t2 will return before the QGraphicsView is actually updated. (I know, it makes sense why this should be asynchronous)
For instance when I load a large file, it will return with 700 msecs after I subtract the two values, but the actual rendering doesn't finish until a few seconds later.
I've looked all over the web and scoured the Qt documentation. I can find tons of information on updating widgets yourself, but nothing on any kind of signal or event that is fired off after rendering finishes.
Even the QGraphicsScene::changed signal appears to only be fired off when the scene changes underneath, not when rendering is done and the user can SEE the changes.
Any help on how to do this?
Does a signal or event exist for when a QGraphicsView or QWidget is done being painted or rendered?
As far as I know, it does not exist. (looked for something similar)
user can SEE the changes
As far as I know, Qt uses double buffering, so if painting is finished, it doesn't mean that user can see the changes.
Any help on how to do this?
If you want to know when painting has finished, then...
You can subclass QGraphicsScene and implement your own drawItems, drawBackground or drawForeground. This is NOT simple (because item painting algorithm is complicated), but you'll be able to tell when every item has finished painted.
You can fire/emit signals from within paintEvent (QWidget-based classes) or paint() (QGraphicsItem/QGraphicsObject-based classes). You'll need to use your own classes, obviously, and you'll have to subclass either QGraphicsView, or items you're drawing within view, or QGraphicsScene.
You could also create proxy QPainter class, and this way you'll be able to know what exactly is being paitned and when.
Having said that I suspect you're approaching your problem incorrectly.
If you're only trying to draw a graph, then there's no reason for you to know when painting is finished.
If painting is finished, it doesn't mean user can see the result.
Paint events might be called more than once.
Recommended approach:
Receive/read the data (you're drawing in your graph) from external source using threads or timer events (you'll need to read it in small chunks if you're using timer events, obviously), then update the graph from time to time, and let Qt handle repainting.
How exactly does this allow me to detect the amount of time it takes from when I choose to open a file to when all the data is loaded and the graph is drawn and is visible?
You can detect when paintEvent has finished painting by subclassing whatever widget you're using to paint Graph, overriding paintEvent and firing signal from within paintEvent, calling a subroutine or doing whatever you want.
There is no warranty that paintEvent will be called only once.
To measure how slow individual routine is and locate bottlenecks, use profilers. VerySleepy, AQTime, and so on.
Instead of measuring how long it takes to load AND display data, it will make much more sense to measure separately loading time and display time. This is a GUI application, not a game engine, so you do not control precisely when something is being drawn.
I have not testet it, but I think by subclassing QGraphicsScene and reimplementing the render method you can measure the render time.
#include <QGraphicsScene>
#include <QTime>
class MyGraphicsScene : public QGraphicsScene
{
Q_OBJECT
public:
void render ( QPainter * painter, const QRectF & target = QRectF(), const QRectF & source = QRectF(), Qt::AspectRatioMode aspectRatioMode = Qt::KeepAspectRatio ) {
QTime t;
t.start();
QGraphicsScene::render (painter, target, source, aspectRatioMode);
qDebug("render time %d msec", t.elapsed());
}
};
When using a QListWidget in batched layout mode, whenever more items are added than the batch size, the list widget blinks for a short time when switching from the old list to the new list. This means, the list widget shows no items, and the scroll bar handle is set to a seemingly random size.
Have you ever encountered this, can this be resolved somehow? I'm using Qt 4.7.4. I should probably add that I'm not using any hidden items.
I had this issue also and spent hours combing through the sea that is Qt widget rendering. Ultimately, like you, I traced the problem back to the batch processing of the QListView. It appears, that when batch processing is enabled, Qt fires off an internal timer to perform incremental layout adjustments of the underlying scroll view. During these incremental layouts, when the scroll bar is visible, the update region is not computed correctly (it's too big and does not account for the regions occupied by the scroll widget(s) themselves). The result is a bad update region that subsequently finds its way into the viewport update which has the unfortunate side-effect of clearing the entire client area without rendering any of the ListViewItems.
Once the batch processing is complete, the final viewport update correctly computes the layout geometry (with the scroll bar) and produces a valid update region; the visible elements in the list are then redrawn.
The behavior worsens as the number of items in the list grows (relative to the batch size). For example, if your list grows from 500 to 50000 items and a batch size of 50, there is a proportionate increase in the number of "bad repaint" events which are triggered causing the view to visibly flicker even more. :(
These incremental (and failed) viewport updates also appear to be cause the apparent spazmodic behavior in the scrollbar handle position that you describe.
The root of this issue appears related to this "hack" that was added to
QListView::doItemsLayout() as commented here:
// showing the scroll bars will trigger a resize event,
// so we set the state to expanding to avoid
// triggering another layout
QAbstractItemView::State oldState = state();
setState(ExpandingState);
I suppose you could override QListView::doItemsLayout() and provide your own batch processing which handles scroll bars properly, but personally I'm too old and lazy to be cleaning up someone else's poo. Switching to SinglePass eliminated the problem entirely. Seamless flicker-free rendering and the scroll bar behavior you've come to expect and love. Yay.