高效地在 QTableView 中高速更新数据

10
我正在使用 QTableView 和 QItemDelegate 的子类来控制表格视图单元格的外观和感觉。
每个单元格显示一个外部连接设备的名称和状态,最多可以连接100个设备。
每个设备的名称和类型基本上是静态的,很少更新(可能每小时一次),但是每个单元格需要显示设备输入的实时值,我目前每50毫秒轮询一次。这个值显示为由 TableView 提供给 Delegate::paint() 方法的 painter 绘制的基本条形图。
更新我的模型20次/秒的问题在于每次都会重新绘制整个表格,这非常低效。将 paint 方法限制为仅绘制条形图表明,大部分 CPU 时间用于绘制每个单元格上的名称、状态和相关图像,而不是绘制图表。
我需要找到一种定期更新每个单元格的图表而不重新绘制单元格的方法,但我无法想出如何做到这一点。
最有效的方法是什么?
编辑:附加图片以帮助理解。
图像表示 QTableView 中的 10 个传感器。数字、名称和状态几乎不变。旁边的条形图每50ms更新一次。我只想绘制这个条形图,而不是文本、状态和单元格背景。状态灯和背景是复杂的图像,所以比绘制和填充一个矩形需要更多的 CPU 时间。

状态需要与其他所有内容放在同一个小部件中吗?我的第一反应是在它旁边放置来自相同模型的 ListView。 - György Andrasek
是的,不幸的是它确实需要。每个设备都有一些需要放在图表旁边的参数。我曾考虑过使用两个视图,或许是重叠的,但这似乎是实现我想要的目标的一种非常混乱的方式,并且会使更改模型、编辑等变得更加困难。 - Dani
3个回答

6
自从您的QTableView继承了QWidget,您可以在其上调用以下内容:
setUpdatesEnabled(false);
changeAllYourData();
setUpdatesEnabled(true);

当setUpdatesEnabled为false时,对其进行的任何paint()或update()调用都没有效果。因此,您可以停止它的更新,更改所有数据,然后重新启用它,可能需要手动调用paint()或update()。我不确定这一部分。
以下是setUpdatesEnabled方法的文档。
QWidget updatesEnabled
希望这有所帮助。
编辑用户评论后:
您可以为QItemDelegate子类实现自己的setUpdatesEnabled(bool)(因为它不继承QWidget并且没有一个),通过在执行原始paint()或update()之前测试标志来完成。之后,您可以为您的QTableView的每个单元格(或行或列)指定它们是否必须更新或重绘。
通过这样做,您可以防止其他单元格(委托)重新绘制,除非手动更改您创建的setUpdatesEnabled标志,但保持包含图形的单元格的更新。
我必须说我从未测试过这样的东西,所以我希望它按照我想象的方式工作。
祝你好运

用户编辑后更新:

根据我的上一条评论,你可以为每个委托设置一个标志来绘制你的图形或整个图像,而不是为每个单元格设置一个标志(我原以为你的图表在单独的单元格中)。

希望这可以帮助到你,

编辑:

我偶然发现了Qt 4.7中的一个新功能(我不知道你是否可以使用它,但它可能会解决你的一些问题)。该功能是QStaticText。这是一个允许你缓存文本(字体和效果)并更快地绘制它们的类。请参见此链接here

希望它能解决你的问题。


1
我已经在做这件事情了,所以我每秒只更新模型20次,而不是20 x 设备数量。但这并不能解决问题,因为除了图表之外,我还要绘制背景图像、名称字符串、状态字符串和其他各种显示,它们本身的更新速度要慢得多。 - Dani
你想写的图表是在单个单元格中吗? - Live
不,它包含在其他信息中。我已经附加了一张图片到原始问题中以帮助可视化GUI界面。 - Dani

2
很少有情况下我会建议这种方法而不是委派,但在你的情况下可能值得一试。我会考虑制作自己的视图,该视图足够智能,只更新需要更新的屏幕部分。像这样的视图小部件显然比通常情况下更具特殊用途,但如果你真的需要效率,这是一种方法。
如果你需要提高效率,还有其他事情需要考虑,例如确保只标记实际更改的行(如果传感器值不经常变化,并且只被轮询那么多次),或者考虑在其之间添加滞后值,以便它实际上不会被重绘(如果传感器值变化不够快以使其无效)。

这是我所采用的方法;创建自己的视图。我采纳了Roku的建议并使用QGLWidget来处理渲染,结果是即使在比以前更短的间隔内刷新数百个设备,我也只看到3%的CPU使用率。 - Dani
@Dani 有点晚了,但是你学到的这种方法可以极大地受益于这个问题。 - UmNyobe

1

将背景图像(单元格背景图像、状态和名称)缓存到模型中作为QPixmap。仅在更改状态或名称时重新绘制该pixmap。通常情况下,您只需要在其上绘制缓存的QPixmap和传感器值。

编辑:

向数据类添加fullRepaintNeeded标志。当更改状态或名称时,将fullRepaintNeeded设置为true。

当委托绘制项目时,委托首先检查项目的fullRepaintNeeded标志。如果fullRepaintNeeded为true,则创建一个新的QPixmap,并将所有内容绘制到该QPixmap中,最后将其绘制到tableview中。然后使用模型的setData函数(但不调用dataChanged)将QPixmap缓存到模型(即您的数据类)中。现在将fullRepaintNeeded设置为false。

但是,如果委托的paint函数中的fullRepaintNeeded为false,则从模型中请求先前缓存的QPixmap,在tableview中绘制它,最后在其上绘制传感器值。


不错的想法,我之前没有想到过。 - Live
这是我想做的事情,但我该如何去做呢?只有一个绘制例程和一个dataChanged()插槽。理想情况下我需要两个,但我应该使用哪个QPainter对象来添加另一个呢? 我已经开始使用Live的标志方法来实现这个功能,看看效果如何。 - Dani
我会尝试并查看,但我相信一旦调用paint(),视口就会被重置以准备绘制。 - Dani
2
你是否需要使用 QTableView?如果你确实需要快速更新并且低 CPU 使用率,使用 QGLWidget 用 OpenGL 绘制所有内容。你可以轻松地以非常低的 CPU 使用率每秒绘制 20 次所有内容。但这将需要比使用 QTableView 更多的编码工作。 - user362638
1
在尝试了其他所有可能性之后,我采纳了您的建议并开始复制使用OpenGL渲染的QTableView。实际上,这比我想象的要花费更少的时间,并且从长远来看将更加灵活,因为我将完全控制它的外观和操作方式。 - Dani
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接