views:

138

answers:

3

I'm using a QTableView with a subclass of QItemDelegate to control the look and feel of the tableview's cells.

Each cell displays the name and status of a of an externally connected device, and as many as 100 devices may be connected at once.

The name and type of each device is essentially static, updating very rarely (perhaps once an hour), but each cell needs to display a real time value of the device's input, which I currently poll for every 50 milliseconds. This value is displayed as a basic bar graph drawn by the painter provided to the Delegate::paint() method by the TableView.

The problem with updating my model 20 times per second is that the entire table is redrawn each time, which is highly inefficient. Limiting the paint method to only drawing the bar graph shows that the majority of CPU time is dedicated to drawing the name, status and associated image on each cell, rather than the graph.

What I need to find is a way to update the graph for each cell regularly without redrawing the cell, but I can't work out how to do it.

What is the most efficient way of achieving this?

Edit: Image attached to help.

Image represents 10 sensors in a QTableView. The Number, Name and Status are virtually static, almost never updating. The bar graph next to the 'Sensor Value' text updates every 50ms. I only want to paint this bar, rather than the text, status and the cell background. The status lights and background are complex images, so take much more CPU time than simply drawing and filling a rect.

alt text

+2  A: 

Hi, since your QTableView inherits QWidget, you can call the following on it:

setUpdatesEnabled(false);
changeAllYourData();
setUpdatesEnabled(true);

When setUpdatesEnabled is false, any paint() or update() call on it has no effect. So, you could stop it from updating, change all your data and then reenable it, possibly by manually calling paint() or update() manually on it, I'm not sure about this part.

Here is the doc for the setUpdatesEnabled method.

QWidget updatesEnabled

Hope this helps.

EDIT after comment from user:

You could implement your own setUpdatesEnabled(bool) for your QItemDelegate subclass (since it does not inherit QWidget and does not have one) by testing a flag before executing your original paint() or update(). After that, you could specify for every cell (or row or column) of your QTableView if they must be updated or repainted.

By doing this, you could stop your other cells (delegates) from repainting, unless you change the setUpdatesEnabled flag you created manually, but keep the updates on your cells containing a graph.

I must say I never tested this or anything like this, so I hope it works the way I think it does.

Best of luck

EDIT after edit from user:

Following my previous comment, instead of setting a flag for every cell (I thought your graph was in a separate cell), you could set a flag for every delegate to paint only your graph or the whole image.

Hope this helps,

EDIT:

I stumbled upon a new feature in Qt 4.7 (I do not know if it's possible for you to use it, but it could solve some of your problems.) The feature is QStaticText. It is a class that allows you to cache text (font and effects) and paint them faster. See the link here.

Hope it could solve your problem.

Live
I'm actually already doing this, so that I'm only only updating the model 20 times per second, rather than 20 x numberOfDevices, but it doesn't solve the problem that as well as the graphs, I'm drawing a background image, a name string, a status string and various other displays which themselves only update at a much lower rate.
Dan
Is the graph you want to write alone in a cell?
Live
No, it's included with other information. I've attached an image to the original question to help visualize the GUI.
Dan
I've edited my post, see if it could work now (I hope).
Live
+1  A: 

Cache the background image (cell background image, status and name) to the model as a QPixmap. Redraw that pixmap only when the status or the name are changed. In the common case you will only need to draw the cached QPixmap and the sensor value on top of that.

Edit:

Add fullRepaintNeeded flag to your data class. When status or name is changed, fullRepaintNeeded is set to true.

When the delegate is painting the item, the delegate first checks the item's fullRepaintNeeded flag. If fullRepaintNeeded is true, then a new QPixmap is created and everything is painted to that QPixmap which is finally painted to the tableview. The QPixmap is then cached to the model (which means to your data class) with model's setData-function (but dataChanged is not called). fullRepaintNeeded is now set to false.

But if fullRepaintNeeded is false in the delegate's paint function, a previously cached QPixmap is asked from the model, drawn to the tableview and the finally sensor value is drawn on top of that.

Roku
Nice idea too, hadn't thought of that.
Live
This is what I want to do, but how do I go about doing it? There's only one paint routine, and only one dataChanged() slot. Ideally I need two, but which QPainter object do I use for the additional one?I've started to implement this using Live's flag method, I'll see how it goes.
Dan
I've just tried this. The paint routine works exactly as you describe, the result: When the flag is set the cell is wiped clear then the graph is painted. On updating the other values (or changing the selection) it's only max 50ms until the next graph update, which then clears the cells and paints only the bar again.
Dan
I was wrong in my first comment, see Roku's post: you either update the pixmap, paint it and paint your graph or you paint the pixmap and paint the graph.
Live
Roku and Live: The issue with this answer is that I'm still having to draw a pixmap to the screen 20 times per second, which is inefficient. I need a way of leaving the pixmap untouched unless it is updated and just drawing the graphs every 50ms.
Dan
Can't you define a paint area smaller than the delegate size and leave the rest untouched?
Live
I'll try and see, but I believe as soon as paint() is called the viewport is reset in preparation to be drawn to.
Dan
Is it necessary for you to use the QTableView? If you really need to update fast and with low CPU usage, draw everything with OpenGL using QGLWidget. You could easily draw everyting 20 times per second with very low CPU usage. But that will take a lot more coding than using the QTableView.
Roku
Having exhausted every other possibility, I've taken your advice and started to reproduce a QTableView that uses OpenGL rendering. It has actually taken a lot less time than I thought, and will be more flexible in the long run, since I'll have complete control over how it looks and operates.
Dan
+1  A: 

It's rare that I suggest this path rather than delegates, but it appears in your situation it might be worth it. I'd consider making my own view, which is aware enough to only update the portions of the screen that need to be updated. A view widget like this is obviously a bit more special-purpose than would usually be the case, but if you really need the efficiency, it's one way to go.

Other things to consider if you need a small boost in efficiency are making sure you only mark the rows changed that actually change (if the sensor values don't change that often, and are only polled that often) or consider adding a hysteresis value between which it isn't actually redrawn (if the sensor values don't change quickly enough to negate this).

Caleb Huitt - cjhuitt
This is the route that I've gone for; creating my own view. I've taken Roku's advice and used QGLWidget to handle the rendering, and as a result I'm seeing 3% CPU usage for hundreds of devices refreshing at even shorter intervals than before.
Dan