背景:
我有一个应用程序,它从 CAN 总线接收帧,在单独的线程中处理它们,然后通知主线程(使用
Qt::QueuedConnection
每块消息一个信号)。主线程更新表模型后,会触发dataChanged()
信号,从而导致视图的更新。
目前,我正在每 500 毫秒使用 20-25 条消息来测试此设置。它工作得很好,除了当表视图可见并且应用程序正在接收消息时,UI 由于表视图重新绘制/更新而变得滞后。
示例:
为了最大限度地减少影响结果、计时和主线程事件循环的变量数量,我创建了一个简单的表模型,由间隔为 500 毫秒的计时器触发,以模拟消息的周期性接收。
简单表格模型:
#include <QAbstractTableModel>
class SimpleTableModel : public QAbstractTableModel
{
Q_OBJECT
public:
explicit SimpleTableModel(QObject *parent = nullptr)
: QAbstractTableModel{parent}
{
}
void setRowCount(int rows)
{
beginResetModel();
m_rows = std::max(rows, 0);
endResetModel();
}
void setColumnCount(int columns)
{
beginResetModel();
m_columns = std::max(columns, 0);
endResetModel();
}
void triggerDataChanged()
{
emit dataChanged(index(0, 0), index(m_rows - 1, m_columns - 1));
}
int rowCount(const QModelIndex &parent = QModelIndex()) const override
{
return m_rows;
}
int columnCount(const QModelIndex &parent = QModelIndex()) const override
{
return m_columns;
}
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override
{
if (!index.isValid())
return {};
if (Qt::DisplayRole == role)
return QStringLiteral("(%1:%2)").arg(index.row() + 1).arg(index.column() + 1);
return {};
}
private:
int m_rows{};
int m_columns{};
};
然后,在我们的
MainWindow
中使用默认的QTableView
:
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
auto model = new SimpleTableModel(this);
auto timer = new QTimer(this);
connect(timer, &QTimer::timeout, this, [model](){ model->triggerDataChanged(); });
ui->tableView->setModel(model);
m_staticModel.setColumnCount(5);
m_staticModel.setRowCount(100);
}
我还使用以下方式添加了登录
QApplication::notify
:
class MyApplication : public QApplication
{
QElapsedTimer t;
public:
MyApplication(int& argc, char ** argv) : QApplication(argc, argv) { }
virtual ~MyApplication() { }
virtual bool notify(QObject* receiver, QEvent* event)
{
t.start();
bool ret = QApplication::notify(receiver, event);
if(t.elapsed() > 1)
qDebug("Processing event type %d for object %s took %dms",
(int)event->type(), receiver->objectName().toLocal8Bit().data(),
(int)t.elapsed());
return ret;
}
};
int main(int argc, char *argv[])
{
MyApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
我收到了以下日志:
...
Processing event type 12 for object qt_scrollarea_viewport took 98ms
Processing event type 77 for object MainWindow took 99ms
Processing event type 12 for object qt_scrollarea_viewport took 97ms
Processing event type 77 for object MainWindow took 98ms
Processing event type 12 for object qt_scrollarea_viewport took 93ms
Processing event type 77 for object MainWindow took 94ms
...
问题:
您应该在每种情况下使用正确(最有效)的变更通知方法:
dataChanged
,仅传递实际更改的范围和角色。通常,对模型的所有更改都是使用 setData
成员完成的。 (在您的示例中,永远不应该调用它)
begin/endResetModel
。仅当几乎所有数据都更改时,f.ex。当使用一些完全不同的数据重新初始化表时,重置模型是合适的。 (在您的示例中,永远不应该调用它)
begin/endInsertRows
,而不是重置模型。 尝试使用此方法实现setRowCount
。
begin/endInsertColumns
,而不是重置模型。 尝试使用此方法实现setColumnCount
。
使用带有正确参数的特定函数,允许视图最小化所需的更新。