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
侦听器和一个自定义上下文菜单,所以这些都不是一个选项。
我的系统:
- Python 3.7
- PYQT5 5.14.1
- Windows 10 64 位
您可以尝试在 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();
}
在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
侦听器和一个自定义上下文菜单,所以这些都不是一个选项。
我的系统:
- Python 3.7
- PYQT5 5.14.1
- Windows 10 64 位
您可以尝试在 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();
}