QTableWidget 上的 PYQT5 setCellWidget() 变慢 UI

PYQT5 setCellWidget() on QTableWidget slows down UI

在PyQT5上使用setCellWidget()QTableWidget()我运行陷入了性能问题。一旦我的 for 循环包含来自 SQL 数据库的大约 100 条记录,延迟就会变得很明显。大约 500 条记录时,延迟最多需要 3 秒。

我已禁用 setCellWidget() 部分并测试了 20.000 条记录,几乎没有延迟。因此执行和获取查询不会延迟代码。

self.queueTable 是一个 8 列的 QTableWidget() 和与存储在变量 tasks

中的查询返回的行一样多的行

这是我使用的代码:

    def buildQueueInUI(self):
        global userAccount
        .....
        tasks = Query(SQLconn, 'SQLITE', False).readParameterized(QueryStrings.myQueuedJobsList, [userAccount])
        for row in tasks:
            rowPosition = self.queueTable.rowCount()
            self.queueTable.insertRow(rowPosition)
            btt=QPushButton('DELETE')
            btt.clicked.connect(cancelTask)
            self.queueTable.setCellWidget(rowPosition, 0, btt)  ##turning this into a comment fixes the slowdown issue
            self.queueTable.setItem(rowPosition, 1, Tables.noEditTableWidget(self, str(row[0])))
            self.queueTable.setItem(rowPosition, 2, Tables.noEditTableWidget(self, str(row[2])))


         ....

我读到 QPushButton 是 'expensive'() ,但是在使用组合框等其他小部件时问题仍然存在(组合框的不实用代码示例: )

    def buildQueueInUI(self):
        global userAccount
        .....
        tasks = Query(SQLconn, 'SQLITE', False).readParameterized(QueryStrings.myQueuedJobsList, [userAccount])
        for row in tasks:
            rowPosition = self.queueTable.rowCount()
            self.queueTable.insertRow(rowPosition)
            combo = QComboBox()
            combo.addItem("keep")
            combo.addItem("remove")
            self.queueTable.setCellWidget(rowPosition, 0, combo)  ##turning this into a comment fixes the slowdown issue
            self.queueTable.setItem(rowPosition, 1, Tables.noEditTableWidget(self, str(row[0])))
            self.queueTable.setItem(rowPosition, 2, Tables.noEditTableWidget(self, str(row[2])))

         ....

只有不使用 QPushButton()QComboBox()QTableWidget 执行 setCellWidget() 调用,我才能立即渲染 table。

在典型的用例中,大约有 500 - 750 个排队任务。我怎样才能拥有 QPushButton() 而没有 setCellWidget() 造成的延迟?我已经在 table` 上有一个 cellDoubleClicked.connect 侦听器和一个自定义上下文菜单,所以这些都不是一个选项。

我的系统:

您可以尝试在 for 循环之前设置行数,而不是一次添加一行。例如考虑以下示例

from PyQt5 import QtWidgets, QtCore
from PyQt5.QtWidgets import QApplication, QTableWidgetItem
from time import time

class CreateTable(QtWidgets.QWidget):

    def __init__(self, parent = None):
        super().__init__(parent)
        fill_button_1 = QtWidgets.QPushButton('fill table - set row count')
        fill_button_1.clicked.connect(self.buildQueueInUI_1)

        fill_button_2 = QtWidgets.QPushButton('fill table - insert rows')
        fill_button_2.clicked.connect(self.buildQueueInUI_2)

        hlayout = QtWidgets.QHBoxLayout()
        hlayout.addWidget(fill_button_1)
        hlayout.addWidget(fill_button_2)

        self.table = QtWidgets.QTableWidget(self)
        self.table.setColumnCount(2)

        layout = QtWidgets.QVBoxLayout(self)
        layout.addLayout(hlayout)
        layout.addWidget(self.table)

    def buildQueueInUI_1(self):
        nrows = 500
        self.table.setRowCount(0)
        t0 = time()
        last_row = self.table.rowCount()
        self.table.setRowCount(nrows+self.table.rowCount())
        for i in range(500):
            row = last_row+i
            button = QtWidgets.QPushButton('Click', self)
            button.clicked.connect(lambda _, x=row+1: print('button', x))
            self.table.setCellWidget(row, 0, button)
            self.table.setItem(row, 1, QTableWidgetItem(f'item {row}'))
        print(f'set row count: {time()-t0:.4f} seconds')

    def buildQueueInUI_2(self):
        nrows = 500
        self.table.setRowCount(0)
        t0 = time()
        for i in range(nrows):
            row = self.table.rowCount()
            self.table.insertRow(row)
            button = QtWidgets.QPushButton('Click', self)
            button.clicked.connect(lambda _, x=row+1: print('button', x))
            self.table.setCellWidget(row, 0, button)
            self.table.setItem(row, 1, QTableWidgetItem(f'item {row}'))
        print(f'insert rows: {time() - t0:.4f} seconds')

if __name__ == "__main__":
    app = QApplication([])
    win = CreateTable()
    win.show()
    app.exec_()

输出

set row count: 0.0359 seconds
insert rows: 1.0572 seconds

setCellWidget 总是非常低效,只有当你遇到“one-of”这种情况时才应该使用,对于在每个 cell/row/column 中重复的项目你应该使用QStyledItemDelegate 的子类。 要添加按钮,您可以使用类似下面的内容(来源:https://forum.qt.io/topic/131266/add-widget-right-aligned-to-a-qtablewidget-cell)。抱歉,它是用 C++ 而不是 python,但翻译它非常简单

tablewidgetdelegate.h

#ifndef TABLEWIDGETDELEGATE_H
#define TABLEWIDGETDELEGATE_H

#include <QTableWidget>
#include <QEvent>
#include <QModelIndex>
#include <QStyleOptionViewItem>
#include <QHoverEvent>

class TableWidgetDelegate : public QTableWidget
{
    Q_OBJECT
#if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)
    Q_DISABLE_COPY_MOVE(TableWidgetDelegate)
#else
    Q_DISABLE_COPY(TableWidgetDelegate)
#endif
public:
    explicit TableWidgetDelegate(QWidget *parent = Q_NULLPTR)
        : QTableWidget(parent)
    {
        viewport()->setAttribute(Qt::WA_Hover,true);
    }
protected:
    bool viewportEvent(QEvent *event) Q_DECL_OVERRIDE
    {
        switch (event->type())
        {
        case QEvent::HoverMove:
        case QEvent::HoverEnter:
        case QEvent::HoverLeave:
        {
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
            QModelIndex index = indexAt(static_cast<QHoverEvent*>(event)->position().toPoint());
            QStyleOptionViewItem options;
            initViewItemOption(&options);
            QAbstractItemDelegate *delegate = itemDelegateForIndex(index);
#else
            QModelIndex index = indexAt(static_cast<QHoverEvent*>(event)->pos());
            QStyleOptionViewItem options = viewOptions();
            QAbstractItemDelegate *delegate = itemDelegate(index);
#endif
            if(delegate){
                QModelIndex buddy = model()->buddy(index);
                options.rect = visualRect(buddy);
                options.state |= (buddy == currentIndex() ? QStyle::State_HasFocus : QStyle::State_None);
                delegate->editorEvent(event, model(), options, buddy);
            }
            break;
        }
        default:
            break;
        }
        return QAbstractItemView::viewportEvent(event);
    }
};
#endif // TABLEWIDGETDELEGATE_H

buttondelegate.h

#ifndef BUTTONDELEGATE_H
#define BUTTONDELEGATE_H

#include <QApplication>
#include <QStyledItemDelegate>
#include <QPushButton>
#include <QMouseEvent>
#include <QToolTip>
#include <QPainter>
#include <QPalette>
#include <QTableWidget>
#include "mybutton.h"

class ButtonDelegate : public QStyledItemDelegate
{
    Q_OBJECT
#if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)
    Q_DISABLE_COPY_MOVE(ButtonDelegate)
#else
    Q_DISABLE_COPY(ButtonDelegate)
#endif
public:
    explicit ButtonDelegate(QObject* parent)
        :QStyledItemDelegate(parent),
         tableWidget(qobject_cast<QTableWidget*>(parent))
    {
        mIsChecked = false;
        isDetailsButton = false;
        isEnabled = true;
        isHidden = false;
    }
    void update()
    {
        tableWidget->viewport()->update();
    }
    void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const Q_DECL_OVERRIDE
    {
        Q_ASSERT(index.isValid());

        bool shouldPaint = false;
        if(isDetailsButton)
        {
            if(index.column() == 0)
                shouldPaint = true;
        }
        else
            shouldPaint = true;

        if(shouldPaint)
        {
            QStyleOptionViewItem opt = option;
            initStyleOption(&opt, index);
            const QWidget *widget = option.widget;

            QStyle *style = widget ? widget->style() : QApplication::style();
            style->drawControl(QStyle::CE_ItemViewItem, &opt, painter, widget);
            QStyleOptionButton buttonOption = buttonOptions(opt);

            if(! isHidden)
            {
                style->drawControl(QStyle::CE_PushButton, &buttonOption, painter, widget);
            }
        }
        else
            QStyledItemDelegate::paint(painter, option, index);
    }
    QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const Q_DECL_OVERRIDE
    {
        QStyleOptionViewItem opt = option;
        initStyleOption(&opt, index);
        const QSize baseSize = QStyledItemDelegate::sizeHint(option,index);
        const QRect butRect = buttonRect(opt);
        return QSize(baseSize.width()+butRect.width(),qMax(butRect.height(),baseSize.height()));
    }
    QWidget* createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const Q_DECL_OVERRIDE
    {
        QWidget* result = new QWidget(parent);
        result->setGeometry(option.rect);
        QWidget* baseEditor = QStyledItemDelegate::createEditor(result,option,index);
        result->setFocusProxy(baseEditor);
        QStyleOptionViewItem opt = option;
        initStyleOption(&opt, index);
        const QRect butRect = buttonRect(opt);
        baseEditor->setObjectName("baseEditor");
        baseEditor->setGeometry(0,0,opt.rect.width()-butRect.width(),opt.rect.height());
        MyButton* myButton = new MyButton(result);
        myButton->setObjectName("myButton");
        myButton->setText(m_buttonText);
        myButton->setIcon(m_buttonIcon);
        myButton->setEnabled(false);
        myButton->setGeometry(opt.rect.width()-butRect.width(), 0, butRect.width(),butRect.height());
        connect(myButton, &MyButton::clicked, this, &ButtonDelegate::clickedHelper);
        connect(myButton, &MyButton::mouseIn, this, &ButtonDelegate::mouseInHelper);
        return result;
    }
    void setEditorData(QWidget *editor, const QModelIndex &index) const Q_DECL_OVERRIDE
    {
        currentIndex = index;
        QWidget* baseEditor = editor->findChild<QWidget*>("baseEditor");
        Q_ASSERT(baseEditor);
        QStyledItemDelegate::setEditorData(baseEditor,index);
    }
    void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const Q_DECL_OVERRIDE
    {
        QWidget* baseEditor = editor->findChild<QWidget*>("baseEditor");
        Q_ASSERT(baseEditor);
        QStyledItemDelegate::setModelData(baseEditor,model,index);
    }
    void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const Q_DECL_OVERRIDE
    {
        QStyleOptionViewItem opt = option;
        initStyleOption(&opt, index);
        editor->setGeometry(opt.rect);
        const QRect butRect = buttonRect(opt);
        QWidget* baseEditor = editor->findChild<QWidget*>("baseEditor");
        Q_ASSERT(baseEditor);
        baseEditor->setGeometry(0,0,opt.rect.width()-butRect.width(),opt.rect.height());
        QWidget* myButton = editor->findChild<QWidget*>("myButton");
        Q_ASSERT(myButton);
        myButton->setGeometry(opt.rect.width()-butRect.width(), 0, butRect.width(),butRect.height());
    }
    const QString text() const
    {
        return m_buttonText;
    }
    void setText(const QString &newButtonText)
    {
        m_buttonText = newButtonText;
        update();
    }
    const QIcon &icon() const
    {
        return m_buttonIcon;
    }
    void setIcon(const QIcon &newButtonIcon)
    {
        m_buttonIcon = newButtonIcon;
        update();
    }
    void setChecked(bool checked)
    {
        mIsChecked = checked;
    }
    bool isChecked()
    {
        return mIsChecked;
    }
    void setToolTip(QString tooltip)
    {
        tooltipText = tooltip;
    }
    void setDetailsButton(bool idb)
    {
        isDetailsButton = idb;
        update();
    }
    void setEnabled(bool enabled)
    {
        isEnabled = enabled;
        update();
    }
    void setHidden(bool hide)
    {
        isHidden = hide;
        isEnabled = ! hide;
        update();
    }
    void click()
    {
        mIsChecked = ! mIsChecked;
        clickedHelper();
    }
Q_SIGNALS:
    void clicked(const QModelIndex &index);
    void mouseIn(const QModelIndex &index);
protected:
    bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index) override
    {
        Q_ASSERT(event);
        Q_ASSERT(model);
        Qt::ItemFlags flags = model->flags(index);
        if ((option.state & QStyle::State_Enabled) && (flags & Qt::ItemIsEnabled))
        {
            switch (event->type()){
            case QEvent::MouseButtonRelease:{
                QStyleOptionViewItem viewOpt(option);
                initStyleOption(&viewOpt, index);
                QMouseEvent *me = static_cast<QMouseEvent*>(event);
                if (me->button() == Qt::LeftButton)
                {
                    mIsChecked = ! mIsChecked;
                    currentIndex = index;
                    clickedHelper();
                }
            }
                break;
            case QEvent::HoverMove:
            case QEvent::HoverEnter:
            case QEvent::HoverLeave:
                if(index!=currentIndex){
                    currentIndex = index;
                    if(index.isValid())
                        mouseInHelper();
                }
                break;
            default:
                break;
            }
        }
        return QStyledItemDelegate::editorEvent(event,model,option,index);
    }
    virtual QStyleOptionButton buttonOptions(const QStyleOptionViewItem &option, bool skipRct=false) const
    {
        const QWidget *widget = option.widget;
        QStyle *style = widget ? widget->style() : QApplication::style();
        int buttonIconSize = style->pixelMetric(QStyle::PM_ButtonIconSize, 0, widget);
        QStyleOptionButton buttonOption;
        buttonOption.text = m_buttonText;
        buttonOption.icon = m_buttonIcon;
        buttonOption.iconSize = (QSize(buttonIconSize,buttonIconSize));
        buttonOption.rect = skipRct ? QRect() : buttonRect(option);
        buttonOption.features = QStyleOptionButton::None;
        buttonOption.direction = option.direction;
        buttonOption.fontMetrics = option.fontMetrics;
        buttonOption.palette = option.palette;
        buttonOption.styleObject = option.styleObject;

        if(isEnabled)
            buttonOption.state = QStyle::State_Enabled;
        else
            buttonOption.state &= ~QStyle::State_Enabled;

        return buttonOption;
    }
    virtual QRect buttonRect(const QStyleOptionViewItem &option) const
    {
        const QStyleOptionButton buttonOption = buttonOptions(option, true);
        const QWidget *widget = option.widget;
        QStyle *style = widget ? widget->style() : QApplication::style();
        QSize buttonSize = style->sizeFromContents(QStyle::CT_PushButton, &buttonOption, QSize(), widget);
        buttonSize.setWidth(qMin(buttonSize.width(),option.rect.width()/2));
        return QRect(option.rect.left()+option.rect.width()-buttonSize.width(),option.rect.top(),buttonSize.width(),qMax(buttonSize.height(),option.rect.height()));
/*
        QRect r = option.rect;
        int x = isDetailsButton ? (r.left()+ r.width() - 10) : (r.center().x() - 6);
        int y = isDetailsButton ? r.top() : r.top() + 10;
        int s = isDetailsButton ? 10 : 1;

        return QRect(x, y, s, s);
        */
    }
    virtual bool helpEvent(QHelpEvent *event, QAbstractItemView *view, const QStyleOptionViewItem &option, const QModelIndex &index) override
    {
        if( !event || !view )
            return false;

        if( event->type() == QEvent::ToolTip )
        {
            QVariant tooltip = index.data( Qt::DisplayRole );
            if( QApplication::keyboardModifiers() == Qt::AltModifier )
            {
                QToolTip::showText( event->globalPos(), tooltipText);
            }
            else
            {
                QToolTip::showText( event->globalPos(), tooltipText);
            }

            if( !QStyledItemDelegate::helpEvent( event, view, option, index ) )
                QToolTip::hideText();
            return true;
        }

        return QStyledItemDelegate::helpEvent( event, view, option, index );
    }
private:
    mutable QModelIndex currentIndex;
    QPainter* mPainter;
    QString m_buttonText;
    QIcon m_buttonIcon;
    bool mIsChecked;
    bool isDetailsButton;
    bool isEnabled;
    bool isHidden;
    QString tooltipText;
    QTableWidget* tableWidget;

    void clickedHelper()
    {
        clicked(currentIndex);
    }
    void mouseInHelper()
    {
        mouseIn(currentIndex);
    }
};
#endif // BUTTONDELEGATE_H

mybutton.h

#ifndef MYBUTTON_H
#define MYBUTTON_H
#include <QPushButton>
class MyButton : public QPushButton
{
    Q_OBJECT
#if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0)
    Q_DISABLE_COPY_MOVE(MyButton)
#else
    Q_DISABLE_COPY(MyButton)
#endif
public:
    MyButton(QWidget* parent = 0) : QPushButton(parent) {}
    ~MyButton() {};

signals:
    void mouseIn();

protected:
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
    void enterEvent(QEnterEvent*) Q_DECL_OVERRIDE
#else
    void enterEvent(QEvent*) Q_DECL_OVERRIDE
#endif
    {
        emit mouseIn();
    }
};
#endif // MYBUTTON_H

main.cpp

#include <QApplication>
#include "buttondelegate.h"
#include <QModelIndex>
#include "tablewidgetdelegate.h"
#include <QDebug>

int main(int argc, char *argv[])
{
    QApplication app(argc,argv);
    TableWidgetDelegate wid;
    wid.setColumnCount(2);
    wid.setRowCount(2);
    ButtonDelegate *butDelegate = new ButtonDelegate(&wid);
    butDelegate->setText("Test");
    QPixmap bluePixmap(20,20);
    bluePixmap.fill(Qt::blue);
    QIcon blueIcon;
    blueIcon.addPixmap(bluePixmap);
    butDelegate->setIcon(blueIcon);
    QObject::connect(butDelegate,&ButtonDelegate::clicked,[](const QModelIndex& index){qDebug() << "Clicked " << index;});
    QObject::connect(butDelegate,&ButtonDelegate::mouseIn,[](const QModelIndex& index){qDebug() << "MouseIn " << index;});
    wid.setItemDelegate(butDelegate);
    wid.show();
    return app.exec();
}