太耗時(shí))
我有4個(gè)表格每個(gè)都是32行22列。每個(gè)單元格里裝填的都是自定義QWidget我使用QTableWidget來(lái)顯示表格。效果為了顯示該窗體感覺(jué)花費(fèi)了很多的時(shí)間。不符合我的預(yù)期。核心原因在于setCellWidget()會(huì)為每個(gè)單元格創(chuàng)建一個(gè)真實(shí)的、獨(dú)立的窗口對(duì)象。你一共有4 * 32 * 22 2816個(gè)單元格這意味著要?jiǎng)?chuàng)建近3000個(gè)獨(dú)立的QWidget及其子控件。每個(gè)控件都擁有自己的窗口句柄、事件循環(huán)和繪制上下文內(nèi)存占用和繪制開(kāi)銷(xiāo)極其龐大最終導(dǎo)致你感受到的嚴(yán)重耗時(shí)和卡頓。要解決這個(gè)問(wèn)題最根本的方法是從“重量級(jí)的控件”轉(zhuǎn)向“輕量級(jí)的繪制”。核心解決方案擁抱 Model/View 框架這是Qt官方推薦的、也是解決此類(lèi)性能問(wèn)題的標(biāo)準(zhǔn)做法。遷移到QTableViewQStandardItemModel放棄QTableWidget改用QTableView配合QStandardItemModel來(lái)存儲(chǔ)數(shù)據(jù)。QTableWidget本身是QTableView的便捷子類(lèi)但其“一單元格一對(duì)象”的設(shè)計(jì)在數(shù)據(jù)量大時(shí)是性能瓶頸。使用QStyledItemDelegate進(jìn)行繪制這是最關(guān)鍵的一步。不要用setCellWidget()而是為QTableView設(shè)置一個(gè)自定義的QStyledItemDelegate。原理委托不會(huì)為每個(gè)單元格創(chuàng)建真正的控件而是根據(jù)數(shù)據(jù)模型中的內(nèi)容在單元格需要被繪制時(shí)“畫(huà)”出你想要的界面。優(yōu)勢(shì)這種方式極大地減少了內(nèi)存占用和對(duì)象數(shù)量繪制效率極高滾動(dòng)和刷新會(huì)變得非常流暢。如果需要有按鈕的交互效果可以在委托的paint()方法中繪制按鈕的樣式并在editorEvent()中處理點(diǎn)擊事件??焖倬徑饧记芍螛?biāo)不治本如果短期內(nèi)無(wú)法重構(gòu)可以嘗試以下方法可能會(huì)有輕微改善但無(wú)法解決根本問(wèn)題。暫停界面更新在批量設(shè)置單元格控件時(shí)使用setUpdatesEnabled(false)暫停繪制完成后再恢復(fù)。tableWidget-setUpdatesEnabled(false); // ... 你的 setCellWidget 循環(huán) ... tableWidget-setUpdatesEnabled(true);阻止信號(hào)觸發(fā)在批量操作前調(diào)用blockSignals(true)阻止表格因每個(gè)單元格變化而觸發(fā)不必要的信號(hào)和重繪。固定行高如果行高都相同可以開(kāi)啟setUniformRowHeights(true)來(lái)加速布局計(jì)算。固定列寬將列寬模式設(shè)為Fixed避免在插入數(shù)據(jù)時(shí)頻繁重新計(jì)算列寬??偨Y(jié)與建議根本問(wèn)題在于選錯(cuò)了工具。setCellWidget()是為少量、交互復(fù)雜的單元格設(shè)計(jì)的便捷方法。對(duì)于你這種包含近3000個(gè)自定義控件的場(chǎng)景它已經(jīng)不堪重負(fù)。因此最推薦、也最徹底的解決方案是重構(gòu)代碼采用QTableViewQStandardItemModelQStyledItemDelegate的經(jīng)典 MVC 模式。雖然這需要一些學(xué)習(xí)和重構(gòu)工作但這是一次投入、長(zhǎng)期受益的優(yōu)化能讓你的表格應(yīng)用在面對(duì)更多數(shù)據(jù)時(shí)依然保持流暢。采用QTableViewQStandardItemModelQStyledItemDelegate#include QApplication #include QWidget // 改為 QWidget #include QVBoxLayout // 新增布局頭文件 #include QTableView #include QStandardItemModel #include QStyledItemDelegate #include QPainter #include QPen #include QColor #include QHeaderView class DotDelegate : public QStyledItemDelegate { public: using QStyledItemDelegate::QStyledItemDelegate; void paint(QPainter *painter, const QStyleOptionViewItem option, const QModelIndex index) const override { painter-save(); painter-setRenderHint(QPainter::Antialiasing, true); painter-setRenderHint(QPainter::TextAntialiasing, true); // 1. 填充深藍(lán)色背景 painter-fillRect(option.rect, QColor(#214a6b)); // 2. 繪制淺藍(lán)色網(wǎng)格線 painter-setPen(QPen(QColor(#4d6a8f), 1)); painter-drawRect(option.rect); // 3. 計(jì)算左側(cè)圓圈的尺寸和位置 qreal h option.rect.height(); qreal diameter h * 0.6; qreal leftMargin h * 0.1; QRectF circleRect(option.rect.left() leftMargin, option.rect.center().y() - diameter / 2.0, diameter, diameter); // 4. 繪制左側(cè)圓圈 painter-setPen(Qt::NoPen); painter-setBrush(QColor(#8b8b8b)); painter-drawEllipse(circleRect); // 5. 繪制右側(cè)文本 QString text index.data(Qt::DisplayRole).toString(); if (!text.isEmpty()) { painter-setPen(QColor(Qt::white)); QFont font option.font; painter-setFont(font); qreal textSpacing h * 0.1; QRectF textRect(option.rect.left() leftMargin diameter textSpacing, option.rect.top(), option.rect.width() - (leftMargin diameter textSpacing), option.rect.height()); painter-drawText(textRect, Qt::AlignVCenter | Qt::AlignLeft, text); } painter-restore(); } }; // 這里將 MainWindow 改為了 externalMonitorWidget 并繼承 QWidget class externalMonitorWidget : public QWidget { public: // 添加了標(biāo)準(zhǔn)的 parent 參數(shù) explicit externalMonitorWidget(QWidget *parent nullptr) : QWidget(parent) { setWindowTitle(圓點(diǎn)表格效果 (Qt5 C)); resize(1000, 600); // 1. 創(chuàng)建視圖和模型 QTableView *tableView new QTableView(this); int row 32; int col 22; QStandardItemModel *model new QStandardItemModel(row, col, this); // 2. 設(shè)置表頭文字 QStringList horizontalHeaders; QStringList verticalHeaders; for (int i 1; i col; i) horizontalHeaders QString::number(i); for (int i 1; i row; i) verticalHeaders QString::number(i); model-setHorizontalHeaderLabels(horizontalHeaders); model-setVerticalHeaderLabels(verticalHeaders); for (int i 1; i row; i) { for (int j 1; j col; j) { QStandardItem *item new QStandardItem(-179.1); model-setItem(i - 1, j - 1, item); } } // 3. 設(shè)置視圖屬性 tableView-setModel(model); tableView-setShowGrid(false); tableView-setSelectionMode(QAbstractItemView::NoSelection); tableView-setEditTriggers(QAbstractItemView::NoEditTriggers); // 設(shè)置表頭樣式 QString headerStyle R( QHeaderView::section { background-color: #214a6b; color: white; border: 1px solid #4d6a8f; } ); tableView-horizontalHeader()-setStyleSheet(headerStyle); tableView-verticalHeader()-setStyleSheet(headerStyle); // 調(diào)整行高和列寬 tableView-horizontalHeader()-setDefaultSectionSize(65); tableView-verticalHeader()-setDefaultSectionSize(30); // 4. 應(yīng)用自定義代理 tableView-setItemDelegate(new DotDelegate(tableView)); // 5. 使用布局將 tableView 放入 QWidget 中替代 setCentralWidget QVBoxLayout *layout new QVBoxLayout(this); layout-setContentsMargins(0, 0, 0, 0); // 讓表格完全填滿 Widget不留邊距 layout-addWidget(tableView); } }; int main(int argc, char *argv[]) { QApplication a(argc, argv); // 將原來(lái)的 MainWindow 改為 externalMonitorWidget externalMonitorWidget w; w.show(); return a.exec(); }進(jìn)一步優(yōu)化封裝函數(shù)接口#include QApplication #include QWidget #include QVBoxLayout #include QTableView #include QStandardItemModel #include QStyledItemDelegate #include QPainter #include QPen #include QColor #include QHeaderView class DotDelegate : public QStyledItemDelegate { public: using QStyledItemDelegate::QStyledItemDelegate; void paint(QPainter *painter, const QStyleOptionViewItem option, const QModelIndex index) const override { painter-save(); painter-setRenderHint(QPainter::Antialiasing, true); painter-setRenderHint(QPainter::TextAntialiasing, true); // 1. 填充深藍(lán)色背景 painter-fillRect(option.rect, QColor(#214a6b)); // 2. 繪制淺藍(lán)色網(wǎng)格線 painter-setPen(QPen(QColor(#4d6a8f), 1)); painter-drawRect(option.rect); // 3. 計(jì)算左側(cè)圓圈的尺寸和位置 qreal h option.rect.height(); qreal diameter h * 0.6; qreal leftMargin h * 0.1; QRectF circleRect(option.rect.left() leftMargin, option.rect.center().y() - diameter / 2.0, diameter, diameter); // 4. 獲取動(dòng)態(tài)顏色并繪制左側(cè)圓圈 QColor circleColor QColor(#8b8b8b); // 默認(rèn)灰色 // 讀取我們存在 UserRole 里的顏色 QVariant colorVariant index.data(Qt::UserRole); if (colorVariant.isValid() colorVariant.canConvertQColor()) { circleColor colorVariant.valueQColor(); } painter-setPen(Qt::NoPen); painter-setBrush(circleColor); painter-drawEllipse(circleRect); // 5. 繪制右側(cè)文本 QString text index.data(Qt::DisplayRole).toString(); if (!text.isEmpty()) { painter-setPen(QColor(Qt::white)); QFont font option.font; painter-setFont(font); qreal textSpacing h * 0.1; QRectF textRect(option.rect.left() leftMargin diameter textSpacing, option.rect.top(), option.rect.width() - (leftMargin diameter textSpacing), option.rect.height()); painter-drawText(textRect, Qt::AlignVCenter | Qt::AlignLeft, text); } painter-restore(); } }; class externalMonitorWidget : public QWidget { public: explicit externalMonitorWidget(QWidget *parent nullptr) : QWidget(parent) { setWindowTitle(圓點(diǎn)表格效果 (Qt5 C)); resize(1000, 600); // 1. 創(chuàng)建視圖和模型 tableView new QTableView(this); int row 32; int col 22; model new QStandardItemModel(row, col, this); // 2. 設(shè)置表頭文字 QStringList horizontalHeaders; QStringList verticalHeaders; for (int i 1; i col; i) horizontalHeaders QString::number(i); for (int i 1; i row; i) verticalHeaders QString::number(i); model-setHorizontalHeaderLabels(horizontalHeaders); model-setVerticalHeaderLabels(verticalHeaders); // 初始化填充數(shù)據(jù)默認(rèn)為灰色和 -179.1 for (int i 1; i row; i) { for (int j 1; j col; j) { QStandardItem *item new QStandardItem(-179.1); model-setItem(i - 1, j - 1, item); } } // 3. 設(shè)置視圖屬性 tableView-setModel(model); tableView-setShowGrid(false); tableView-setSelectionMode(QAbstractItemView::NoSelection); tableView-setEditTriggers(QAbstractItemView::NoEditTriggers); // 設(shè)置表頭樣式 QString headerStyle R( QHeaderView::section { background-color: #214a6b; color: white; border: 1px solid #4d6a8f; } ); tableView-horizontalHeader()-setStyleSheet(headerStyle); tableView-verticalHeader()-setStyleSheet(headerStyle); tableView-horizontalHeader()-setDefaultSectionSize(65); tableView-verticalHeader()-setDefaultSectionSize(30); // 4. 應(yīng)用自定義代理 tableView-setItemDelegate(new DotDelegate(tableView)); // 5. 布局 QVBoxLayout *layout new QVBoxLayout(this); layout-setContentsMargins(0, 0, 0, 0); layout-addWidget(tableView); // // 展示如何調(diào)用接口測(cè)試代碼實(shí)際使用時(shí)可刪除 // 將第一列設(shè)置為綠色第二列紅色第三列灰色 for (int i 0; i row; i) { setItemGreen(i, 0); // 第一列變綠 setItemRed(i, 1); // 第二列變紅 setItemGray(i, 2); // 第三列變灰 setItemText(i, 3, OK); // 第四列修改文字 setItemText(i, 4, ERR); // 第五列修改文字 } // } // 封裝的接口 // 通用接口修改指定行、列的圓圈顏色 // 注意row 和 col 都是從 0 開(kāi)始計(jì)數(shù)的 void setItemColor(int row, int col, const QColor color) { QModelIndex index model-index(row, col); if (index.isValid()) { model-setData(index, color, Qt::UserRole); } } // 變綠 void setItemGreen(int row, int col) { setItemColor(row, col, QColor(#00FF00)); // 或者 QColor(Qt::green) } // 變紅 void setItemRed(int row, int col) { setItemColor(row, col, QColor(#FF0000)); // 或者 QColor(Qt::red) } // 變灰 void setItemGray(int row, int col) { setItemColor(row, col, QColor(#8b8b8b)); // 之前的默認(rèn)灰色 } // 修改顯示的數(shù)字 void setItemText(int row, int col, const QString text) { QModelIndex index model-index(row, col); if (index.isValid()) { model-setData(index, text, Qt::DisplayRole); } } // 獲取當(dāng)前TableView的指針如果需要外部徹底接管可以開(kāi)放這個(gè) QTableView* getTableView() { return tableView; } private: QTableView *tableView; QStandardItemModel *model; }; int main(int argc, char *argv[]) { QApplication a(argc, argv); externalMonitorWidget w; w.show(); return a.exec(); }再進(jìn)一步拆分把類(lèi)以.h,.cpp的形式來(lái)寫(xiě)。#ifndef DOTDELEGATE_H #define DOTDELEGATE_H #include QStyledItemDelegate class DotDelegate : public QStyledItemDelegate { Q_OBJECT public: explicit DotDelegate(QObject *parent nullptr); void paint(QPainter *painter, const QStyleOptionViewItem option, const QModelIndex index) const override; }; #endif // DOTDELEGATE_H#include dotdelegate.h #include QPainter #include QPen #include QColor #include QVariant DotDelegate::DotDelegate(QObject *parent) : QStyledItemDelegate(parent) {} void DotDelegate::paint(QPainter *painter, const QStyleOptionViewItem option, const QModelIndex index) const { painter-save(); painter-setRenderHint(QPainter::Antialiasing, true); painter-setRenderHint(QPainter::TextAntialiasing, true); // 1. 填充深藍(lán)色背景 painter-fillRect(option.rect, QColor(#214a6b)); // 2. 繪制淺藍(lán)色網(wǎng)格線 painter-setPen(QPen(QColor(#4d6a8f), 1)); painter-drawRect(option.rect); // 3. 計(jì)算左側(cè)圓圈的尺寸和位置 qreal h option.rect.height(); qreal diameter h * 0.6; qreal leftMargin h * 0.1; QRectF circleRect(option.rect.left() leftMargin, option.rect.center().y() - diameter / 2.0, diameter, diameter); // 4. 獲取動(dòng)態(tài)顏色并繪制左側(cè)圓圈 QColor circleColor QColor(#8b8b8b); // 默認(rèn)灰色 QVariant colorVariant index.data(Qt::UserRole); if (colorVariant.isValid() colorVariant.canConvertQColor()) { circleColor colorVariant.valueQColor(); } painter-setPen(Qt::NoPen); painter-setBrush(circleColor); painter-drawEllipse(circleRect); // 5. 繪制右側(cè)文本 QString text index.data(Qt::DisplayRole).toString(); if (!text.isEmpty()) { painter-setPen(QColor(Qt::white)); QFont font option.font; painter-setFont(font); qreal textSpacing h * 0.1; QRectF textRect(option.rect.left() leftMargin diameter textSpacing, option.rect.top(), option.rect.width() - (leftMargin diameter textSpacing), option.rect.height()); painter-drawText(textRect, Qt::AlignVCenter | Qt::AlignLeft, text); } painter-restore(); }#ifndef EXTERNALMONITORWIDGET_H #define EXTERNALMONITORWIDGET_H #include QWidget #include QColor #include QString class QTableView; class QStandardItemModel; class externalMonitorWidget : public QWidget { Q_OBJECT public: explicit externalMonitorWidget(QWidget *parent nullptr); // 封裝的接口 void setItemColor(int row, int col, const QColor color); void setItemGreen(int row, int col); void setItemRed(int row, int col); void setItemGray(int row, int col); void setItemText(int row, int col, const QString text); QTableView* getTableView() const; private: QTableView *tableView; QStandardItemModel *model; }; #endif // EXTERNALMONITORWIDGET_H#include externalmonitorwidget.h #include dotdelegate.h #include QTableView #include QStandardItemModel #include QHeaderView #include QVBoxLayout externalMonitorWidget::externalMonitorWidget(QWidget *parent) : QWidget(parent) { setWindowTitle(圓點(diǎn)表格效果 (Qt5 C)); resize(1000, 600); // 1. 創(chuàng)建視圖和模型 tableView new QTableView(this); int row 32; int col 22; model new QStandardItemModel(row, col, this); // 2. 設(shè)置表頭文字 QStringList horizontalHeaders; QStringList verticalHeaders; for (int i 1; i col; i) horizontalHeaders QString::number(i); for (int i 1; i row; i) verticalHeaders QString::number(i); model-setHorizontalHeaderLabels(horizontalHeaders); model-setVerticalHeaderLabels(verticalHeaders); // 初始化填充數(shù)據(jù)默認(rèn)為灰色和 -179.1 for (int i 0; i row; i) { for (int j 0; j col; j) { QStandardItem *item new QStandardItem(-179.1); model-setItem(i, j, item); } } // 3. 設(shè)置視圖屬性 tableView-setModel(model); tableView-setShowGrid(false); tableView-setSelectionMode(QAbstractItemView::NoSelection); tableView-setEditTriggers(QAbstractItemView::NoEditTriggers); // 設(shè)置表頭樣式 QString headerStyle R( QHeaderView::section { background-color: #214a6b; color: white; border: 1px solid #4d6a8f; } ); tableView-horizontalHeader()-setStyleSheet(headerStyle); tableView-verticalHeader()-setStyleSheet(headerStyle); tableView-horizontalHeader()-setDefaultSectionSize(65); tableView-verticalHeader()-setDefaultSectionSize(30); // 4. 應(yīng)用自定義代理 tableView-setItemDelegate(new DotDelegate(tableView)); // 5. 布局 QVBoxLayout *layout new QVBoxLayout(this); layout-setContentsMargins(0, 0, 0, 0); layout-addWidget(tableView); } // 封裝的接口實(shí)現(xiàn) void externalMonitorWidget::setItemColor(int row, int col, const QColor color) { QModelIndex index model-index(row, col); if (index.isValid()) { model-setData(index, color, Qt::UserRole); } } void externalMonitorWidget::setItemGreen(int row, int col) { setItemColor(row, col, QColor(#00FF00)); } void externalMonitorWidget::setItemRed(int row, int col) { setItemColor(row, col, QColor(#FF0000)); } void externalMonitorWidget::setItemGray(int row, int col) { setItemColor(row, col, QColor(#8b8b8b)); } void externalMonitorWidget::setItemText(int row, int col, const QString text) { QModelIndex index model-index(row, col); if (index.isValid()) { model-setData(index, text, Qt::DisplayRole); } } QTableView* externalMonitorWidget::getTableView() const { return tableView; }#include QApplication #include externalmonitorwidget.h int main(int argc, char *argv[]) { QApplication a(argc, argv); externalMonitorWidget w; w.show(); // 演示接口調(diào)用 // 將第1列變綠第2列變紅第3列變灰第4列修改文字 for (int i 0; i 32; i) { w.setItemGreen(i, 0); w.setItemRed(i, 1); w.setItemGray(i, 2); w.setItemText(i, 3, OK); w.setItemText(i, 4, ERR); } return a.exec(); }