Set Delegate for each cell in a QTableWidget?
-
@Christian-Ehrlicher I don't understand, how do I do that? I want each cell having an individual delegate item, like
setCellWidget
. This is my delegate class:#ifndef DELEGATEBUTTON_H #define DELEGATEBUTTON_H #include <QApplication> #include <QStyledItemDelegate> #include <QPushButton> #include <QMouseEvent> #include <QToolTip> #include <QPainter> #include <QPalette> #include <QTableWidget> class DelegateButton : public QStyledItemDelegate { Q_OBJECT #if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0) Q_DISABLE_COPY_MOVE(DelegateButton) #else Q_DISABLE_COPY(TailButtonDelegate) #endif public: explicit DelegateButton(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 override { Q_ASSERT(index.isValid()); 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); } QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const 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 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()); QPushButton* myButton = new QPushButton(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, &QPushButton::clicked, this, &DelegateButton::clickedHelper); return result; } void setEditorData(QWidget *editor, const QModelIndex &index) const 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 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 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; 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)) { if (event->type() == QEvent::MouseButtonRelease) { QStyleOptionViewItem viewOpt(option); initStyleOption(&viewOpt, index); const QRect butRect = buttonRect(viewOpt); QMouseEvent *me = static_cast<QMouseEvent*>(event); if (me->button() == Qt::LeftButton && butRect.contains(me->pos())) { mIsChecked = ! mIsChecked; currentIndex = index; clickedHelper(); } } } 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)); 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 : 20; 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); } }; #endif // DELEGATEBUTTON_H
-
@hbatalha
In the example at https://doc.qt.io/qt-5/qabstractitemdelegate.html#details, you seevoid WidgetDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { if (index.column() == 1) { ... } else QStyledItemDelegate::paint(painter, option, index); }
WidgetDelegate
has been assigned as the item delegate (probably throughsetItemDelegate()
) for all cells. The first thing it does inpaint()
is to look at the column/row of the passed-inindex
to see which cell it is being called on, and acts accordingly. This is how you apply a delegate to certain cells only: you have to assign the delegate to all cells and then have it check at runtime what it does/does not do for each cell. -
-
@JonB Maybe I am not understanding how to do by your proposed solution, this is the code without delegate:
for(int i = 0, len = titlesList.size(); i < len; ++i) // len == 200 starts causing some lumpiness (at leat leat in my pc) { const int row = ui->table->rowCount(); ui->table->setRowCount(row + 1); QPushButton* button1= new QPushButton(this); QPushButton* button2= new QPushButton(this); ui->table->setItem(row, 0, new QTableWidgetItem(title)); ui->table->setItem(row, 1, new QTableWidgetItem("-")); ui->table->setItem(row, 2, new QTableWidgetItem("-")); ui->table->setItem(row, 3, new QTableWidgetItem("-")); ui->table->setItem(row, 4, new QTableWidgetItem("-")); ui->table->setCellWidget(row, 5, button1); ui->table->setCellWidget(row, 6, button2); for(int k = 0; k < 5; ++k) { QTableWidgetItem* item = ui->table->item(row, k); item->setFlags(item->flags() & ~ Qt::ItemIsEditable); if(k != 0) { item->setTextAlignment(Qt::AlignmentFlag::AlignCenter); } } Record* record = new Record(title, destinationFolder, format, qualityText, mRecordType); record->button1(button1); record->button2(button2); allRecords.append(record); activeRecords.append(record); database.add(record); }
Each button click causes an action to its row. How would I do that with the delegate class above?
-
@hbatalha said in Set Delegate for each cell in a QTableWidget?:
How would I do that with the delegate class above?
DelegateButton *delegate1 = new DelegateButton(this); DelegateButton *delegate2 = new DelegateButton(this); view->setItemDelegateForColumn(5,delegate1); view->setItemDelegateForColumn(6,delegate2);
-
@VRonin That is how I did, but the problem was that, say that I want to insert 30 rows, only the last
delegate2
will be valid for the tablewidget, when I click one button it is supposed to change its icon, so with delegate button, when I click a button in a row, all the others delegate buttons in the other rows will be affected because ultimately they are the same and only last row will be affected. Every time a new row is inserted the new delegate button created will replace the last one withsetItemDelegateForColumn
.
That is how it behaved. -
Again: change your delegate to check for the index and do the painting accordingly...
-
@hbatalha said in Set Delegate for each cell in a QTableWidget?:
when I click one button it is supposed to change its icon, so with delegate button, when I click a button in a row, all the others delegate buttons in the other rows will be affected because ultimately they are the same and only last row will be affected. Every time a new row is inserted the new delegate button created will replace the last one with setItemDelegateForColumn.
What I read in this is that you are storing data in the delegate. that should not happen.
The process should be:
click the delegate➡the delegate emits a signal (clicked(const QModelIndex &index)
)➡in the slot connected to that signal you store data in a custom role of the model (e.g.model->setData(index, index.data(Qt::UserRole).toBool() ? QVariant() : true, Qt::UserRole);
)➡the delegate will use the data stored in this role to decide how to paint/create the button -
@VRonin @Christian-Ehrlicher There's a disconnect here, I am not quite understanding what you are telling me. This is how my table is behaving:
The last row is being changed because that is the last delegate button that was created. Each
record
takes the two buttons and they areconnect
ed and in turn eachrecord
is connected to a table row. So when I click a button it changes the status of therecord
it's connected to and then the row which therecord
is connected to is updated.I need each row to have its own buttons. That's why I said that I need to modify the delegate so it paints two buttons instead like here and then use
setItemDelegateForRow
. -
@VRonin Here are the
connect
's with QPushButton:connect(pause_button, &QPushButton::clicked, record, &Record::pauseResume); // `pause_button` is `button1` in the code above connect(record, &Record::progressUpdated, this, &MainWindow::onProgressUpdated);
The slot:
void MainWindow::onProgressUpdated(const QString& command, QString status) { const int row = commands_rows.key(command); // QMap QTableWidgetItem* item = ui->table->item(row, 2); bool isPercentage = false; int progressPercentInt = status.toInt(&isPercentage); if (progressPercentInt < 0) { status = tr("Error"); isPercentage = false; } if(isPercentage) { item->setText(status + "%"); } else { item->setText(status); } }
-
void Recod::pauseResume() { if(isInQueue() || isToResume()) { emit pogressUpdated(command, tr("Paused")); if(hasStarted) { process->kill(); } status_ = Status::PAUSED; hasStarted = false; } else { emit progressUpdated(command, tr("In Queue")); status_ = Status::TO_RESUME; emit statusChanged(getArgsStr(), status()); } }
-
@hbatalha said in Set Delegate for each cell in a QTableWidget?:
status_ = Status::PAUSED;
Yeah, see, you are using a global state to manage data that should live in the model.
pauseResume
should look more like:enum{ StatusRole = Qt::UserRole }; void Recod::pauseResume(const QModelIndex& index){ if (index.data(StatusRole).toInt() == Status::PAUSED) model->setData(index,Status::TO_RESUME,StatusRole); else model->setData(index,Status::PAUSED,StatusRole); }
This stores the state of the single item into the model. Signaling the change is handled by the model. You can then retrieve that state inside the delegate and paint accordingly
-
@VRonin How I get this part, but since I will be having multiple rows how will I differentiate each delegate button with its row? In my
for
loop when I insert rows if I callsetItemDelegateForColumn
only once still all the rows that are inserted after will have the delegate buttons. Shouldn't I be usingsetItemDelegateForRow
instead? Eachrecord
takes its buttons but only the last delegate buttons created in thefor
loop will be set into the columns. This is the part I am not getting.Sorry it's taking so much time but it's proving to be trickier for me to understand delegates fully. This is my first hands-on experience with them.
-
@hbatalha said in Set Delegate for each cell in a QTableWidget?:
Each record takes its buttons but only the last delegate buttons created in the for loop will be set into the columns.
Not sure I understand what you mean.
If showing/not showing the buttons depend on a state of the item then we are back to the case above. Save such state in a role of the model and use it in the delegate.
Maybe if you can formulate a simple concrete example we can get to the root of the problem
-
@VRonin said in Set Delegate for each cell in a QTableWidget?:
Not sure I understand what you mean.
Ok, try having a
QTableWidget
with multiple rows and usesetItemDelegateForColumn
to add the delegate buttons like in my code above, then click a button in a row and have only that delegate button in that row be repainted (change icon). This is what I am unable to do.If you see the image below I click one button and all the other buttons change icon as well. I want to click one button and only that button to change its icon.
Maybe if you can formulate a simple concrete example we can get to the root of the problem
I will try to create one.
-
Simplified example:
buttondelegate.h
#ifndef BUTTONDELEGATE_H #define BUTTONDELEGATE_H #include <QApplication> #include <QStyledItemDelegate> #include <QPushButton> #include <QPainter> 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) { } void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const Q_DECL_OVERRIDE { Q_ASSERT(index.isValid()); QStyleOptionViewItem opt = option; initStyleOption(&opt, index); const QWidget *widget = option.widget; QStyle *style = widget ? widget->style() : QApplication::style(); QStyleOptionButton buttonOption = buttonOptions(opt,index); style->drawControl(QStyle::CE_PushButton, &buttonOption, painter, widget); } QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const Q_DECL_OVERRIDE { QStyleOptionViewItem opt = option; initStyleOption(&opt, index); return buttonRect(opt).size(); } QWidget* createEditor(QWidget *parent, const QStyleOptionViewItem &, const QModelIndex &) const Q_DECL_OVERRIDE { return new QWidget(parent); } void setEditorData(QWidget *, const QModelIndex &) const Q_DECL_OVERRIDE { } void setModelData(QWidget *, QAbstractItemModel *, const QModelIndex &) const Q_DECL_OVERRIDE { } 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()); } Q_SIGNALS: void clicked(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) { currentIndex = index; clickedHelper(); } } break; default: break; } } return QStyledItemDelegate::editorEvent(event,model,option,index); } virtual QStyleOptionButton buttonOptions(const QStyleOptionViewItem &option, const QModelIndex &index, bool skipRct=false) const { QStyleOptionButton buttonOption; buttonOption.icon = m_buttonIcon; if(index.data(Qt::UserRole).toBool()) buttonOption.text = QString(QChar(0x23F8)); //pause button emoji else buttonOption.text = QString(QChar(0x25B6)); //play button emoji 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; return buttonOption; } virtual QRect buttonRect(const QStyleOptionViewItem &option) const { return option.rect; } private: mutable QModelIndex currentIndex; QString m_buttonText; QIcon m_buttonIcon; void clickedHelper() { clicked(currentIndex); } }; #endif // BUTTONDELEGATE_H
main.cpp
#include <QApplication> #include <QTableWidget> #include "buttondelegate.h" int main(int argc, char *argv[]) { QApplication app(argc,argv); QTableWidget wid(10,2); for(int i=0;i<10;++i){ wid.setItem(i,0,new QTableWidgetItem(QStringLiteral("Item %1").arg(i+1))); } ButtonDelegate* buttonDelegate = new ButtonDelegate(&wid); QObject::connect(buttonDelegate,&ButtonDelegate::clicked,&wid,[&wid](const QModelIndex& index){ Q_ASSERT(index.model()==wid.model()); wid.model()->setData(index,index.data(Qt::UserRole).toBool() ? QVariant() : true,Qt::UserRole); }); wid.setItemDelegateForColumn(1, buttonDelegate); wid.show(); return app.exec(); }
-
@VRonin This one seems to be the solution I have been looking for. I will, however, have to hack my current code to using this solution. In case of any question I will reach out to you.
Thank you for taking so much time and having so much patience with me. I appreciate it.