30+ QGraphicsItems's update() failed, how to solve it?
-
I have a
qgraphicsscene
, i will at least have 100+ items in it, and about 30+ items are signal lights. These signal lights will keep changing their signal states in every 50ms byQTimer
.But, the actual result is as the below gif,
(The forum's server maybe has some problems, the picture can't show, you can go to github to see it.https://github.com/Hapoa/_Repository/blob/master/images/2.gif)
I guess the problem is the function
update()
.void QGraphicsItem::update(const QRectF &rect = QRectF())
Schedules a redraw of the area covered by rect in this item. You can call this function whenever your item needs to be redrawn, such as if it changes appearance or size.
This function does not cause an immediate paint; instead it schedules a paint request that is processed by QGraphicsView after control reaches the event loop. The item will only be redrawn if it is visible in any associated view.
As a side effect of the item being repainted, other items that overlap the area rect may also be repainted.
If the item is invisible (i.e., isVisible() returns false), this function does nothing.Can someone give me some advice?
Below are the most codes
/* pl_item.h */ #ifndef PL_ITEM_H #define PL_ITEM_H #include <QGraphicsItem> #include <QPainter> #include <QPixmap> #include <QTimer> #include <QDebug> class PLItem : public QObject, public QGraphicsItem { Q_OBJECT public: PLItem(QGraphicsItem* parent = 0) : QGraphicsItem(parent) { m_interval = 50; m_pl1Pixmap.load(":/pl1.png"); m_pl1Pixmap = m_pl1Pixmap.scaled(60, 60); m_pl2Pixmap.load(":/pl2.png"); m_pl2Pixmap = m_pl2Pixmap.scaled(60, 60); m_is = true; m_timer = new QTimer(this); m_timer->setInterval(m_interval); connect(m_timer, SIGNAL(timeout()), this, SLOT(stateChange())); startTimer(); } virtual ~PLItem(){} void setInterval(int interval) { m_timer->stop(); m_interval = interval; m_timer->setInterval(m_interval); m_timer->start(); } void startTimer() { m_timer->start(); } void stopTimer() { m_timer->stop(); } virtual QRectF boundingRect() const { qreal adjust = 2; return QRectF(-m_pl1Pixmap.width() / 2 - adjust, -m_pl1Pixmap.height() / 2 - adjust, m_pl1Pixmap.width() + adjust * 2, m_pl1Pixmap.height() + adjust * 2); } public slots: void stateChange() { update(); } protected: virtual void paint(QPainter* painter, const QStyleOptionGraphicsItem*, QWidget*) { QPointF tl = boundingRect().topLeft(); if (m_is) painter->drawPixmap(tl, m_pl1Pixmap); else painter->drawPixmap(tl, m_pl2Pixmap); //qDebug() << "pl paint"; m_is = !m_is; } private: QPixmap m_pl1Pixmap; QPixmap m_pl2Pixmap; QTimer* m_timer; bool m_is; int m_interval; }; #endif // PL_ITEM_H
/* scene.h */ #ifndef SCENE_H #define SCENE_H #include <QGraphicsScene> #include <QGraphicsSimpleTextItem> #include "pl_item.h" class Scene : public QGraphicsScene { Q_OBJECT public: Scene(QObject* parent = 0) : QGraphicsScene(parent) { this->setSceneRect(-400, -240, 800, 480); m_plItem = new PLItem[31]; for (int i = 0; i < 5; i++) { this->addItem(m_plItem + i); m_plItem[i].setPos(125 + 60 * i, -140); } for (int i = 5; i < 18; i++) { this->addItem(m_plItem + i); m_plItem[i].setPos(-360 + 60 * (i - 5), -80); } for (int i = 18; i < 31; i++) { this->addItem(m_plItem + i); m_plItem[i].setPos(-360 + 60 * (i - 18), -20); } } virtual ~Scene(){} private: PLItem* m_plItem; }; #endif // SCENE_H ```![0_1531125818720_2855448651635.gif](正在上传 100%)
-
Is the "problem" that the lights update in an irregular fashion, or that some of the lights don't blink at all?
With a 50ms timer, I can easily imagine that the asynchronous update leads to undesired effects.
I suggest trying a different approach: Instead having a separate timer in each item, control the blink frequency by reimplementing QGraphicsItem::advance(int) and calling QGraphicsScene::advance() from a single timer. -
Hi!
@Asperamanca is correct. Use advance or manage the states of your items in the scene timer. I update 80K+ items very swiftly and each has their own state but the timing is managed at 20Hz and the flashing is managed at 5Hz. No advance is done, just a scene update whenever the state changes (the flash is also a state).By avoiding deriving each item from QObject, you also have lighter weight items. Use QGraphicsPixmapItem and have your state setting method call setPixmap. Then, all the bounding rects, drawing, etc. are managed for you.
If you are still having trouble, please play with setViewportUpdateMode, setCacheMode in your QGraphicsView. Sometimes the smarter modes do not yield the desired results. For example, my settings are:
FullViewportUpdate and CacheNoneFor my scene I use setItemIndexMethod to BspTreeIndex and I also set the ZValue for my items based on which layer order I would like to draw.
Even using the best quality render hints I still use about 10% of my atom processor during full 20Hz all entity updates so don't be afraid to do full updates.
-
@Buckwheat @Asperamanca Thanks for your warm answers.
But using one timer in scene to control all items is not suitable for me.
Because each signal light will bind with a PLC, their frequency is controled by PLC.
The frequency of signal lights range from 30ms to 1000ms, and the number is less than 100(at most 100).
-
@Limer
So each light would blink in a different but relatively high frequency? I would add an epilepsy warning ;-)This might be tricky to do. First, check whether your current solution already fully uses one CPU core. If it does, chances are you won't get fast enough update. The problem is, if you have 100 lights blinking at different intervals ranging from 30 to 100 ms, at worst the scene would need to redraw some parts over 1000 times a second. Because while each individual light has a minimum interval of about 30 ms, less than 1 ms later, another light might need to be redrawn. The asynchronous painting will lump these together, so you will have an irregular painting result.
The best solution I can think of is to repaint the scene at a fixed rate that your CPU can manage (hopefully something like every 5 ms). Lumping together light changes which happen within 5 ms is no issue, because human eyes cannot recognize changes in such a short time span.
EDIT: If you want to make sure the repaint gets done synchronously, you can experiment with
QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents | QEventLoop::ExcludeSocketNotifiers)
It's not pretty, and I normally discourage use, but it might be what you need in this case
-
@Asperamanca Thanks for your warm response.
I found a solution, allocate a qtimer pointer array to control all items. One controls one ( or serveral).