Using setData(Qt::EditRole, xxx) with QTableWidgetItem
-
@VRonin Thanks for your help. Now I have another problem. Assume I double press one cell, and a spinbox appear and I change it's value to 2.00, but after the editing, the text showed in the cell change to "2", it abandoned the trailing zeros. How to make it preserve the trailing zeros?
-
class DecimalsDelegate : public QStyledItemDelegate{ Q_OBJECT Q_PROPERTY(unsigned int decimals READ decimals WRITE setDecimals NOTIFY decimalsChanged) public: explicit DecimalsDelegate(QObject* parent = nullptr) :QStyledItemDelegate(parent) ,m_decimals(2U) {} virtual ~DecimalsDelegate() = default; DecimalsDelegate(const DecimalsDelegate&) = delete; DecimalsDelegate& operator=(const DecimalsDelegate&) = delete; void setDecimals(unsigned int dec){ if(m_decimals!=dec){ m_decimals=dec; emit decimalsChanged(); } } unsigned int decimals() const { return m_decimals;} virtual QString displayText(const QVariant &value, const QLocale &locale) const override{ switch(value.type()){ case QMetaType::Double: case QMetaType::Float: return locale.toString(value.toDouble(),'f',m_decimals); default: return QStyledItemDelegate::displayText(value,locale); } } signals: void decimalsChanged(); private: unsigned int m_decimals; }
then use this class instead of the plain
QStyledItemDelegate
usingsetDecimals()
to decide how many decimals you want to show -
@VRonin Great. This almost achieve my goal. I can set the decimals by column or row now. But any way to set them by cell. Now I have to show nums with different decimals in one column :(.
BTW, anyway to know which cell the 'value' from in the displayText() function? If I can get the cell position from displayText(), then I can format the text with different decimals by the cell.
-
There are different approaches here. Do you need a deterministic number of decimals or do you need as many decimals as the number has?
e.g.
data: 12.34500do you have a 4 digits fixed so 12.3450 or as little digits as necessary to represent the number in full 12.345?
-
@VRonin I set the data with Qt::EditRole, it's double. When I set the data, the needed decimal count is determined, too. If the data is 12.345, and I need 4 decimal, it should displayed as '12.3450'. If I only need 2 decimal, then it should displayed as '12.35'.
Them problem now is, I want some cells show 4, some cells show 3, and some cells show 2, etc.
-
Just use a role
class DecimalsDelegate : public QStyledItemDelegate{ Q_OBJECT public: enum { NumOfDecimalsRole = Qt::UserRole + 888 }; /* 888 is just a random number, you can use whatever you want if it conflicts */ explicit DecimalsDelegate(QObject* parent = nullptr) :QStyledItemDelegate(parent) {} virtual ~DecimalsDelegate() = default; DecimalsDelegate(const DecimalsDelegate&) = delete; DecimalsDelegate& operator=(const DecimalsDelegate&) = delete; virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override { Q_ASSERT(index.isValid()); QStyleOptionViewItem opt = option; initStyleOption(&opt, index); const QVariant numOfDecimals = index.data(NumOfDecimalsRole ); if (numOfDecimals.isValid() && !numOfDecimals.isNull() && numOfDecimals.canConvert(QMetaType::Int) && ( opt.features & QStyleOptionViewItem::HasDisplay) ) { const QVariant value = index.data(Qt::DisplayRole); if(value.canConvert(QMetaType::Double)) opt.text = opt.locale.toString(value.toDouble(),'f',numOfDecimals.toInt()); } const QWidget *widget = option.widget; const QStyle* const style = widget ? widget->style() : QApplication::style(); style->drawControl(QStyle::CE_ItemViewItem, &opt, painter, widget); } }
now after you set the Qt::EditRole just set NumOfDecimalsRole to the number of decimals you need
Edit: fixed copy/paste error
-
@VRonin Great, it work.
I have another idea. Because I can't know the current index of the model in the displayText function. So, I can't change the decimal count by the 'value' 's 'index'. But in 'setEditorData' and 'setModelData', and 'createEditor', I can know the 'index'. So, when I set the widget item, I use '
item->setData(Qt::EditRole, QString::number(ratio_error * 1000, 'f', 2)); ui.testTableWidget->setItem(row, 7, item);
And my derived class is as below:
virtual QWidget *createEditor(QWidget *parent, QStyleOptionViewItem const &option, QModelIndex const &index) const override { if (index.data(Qt::EditRole).isValid() && ((index.column() == 6) || (index.column() == 7))) { QDoubleSpinBox *spinbox = new QDoubleSpinBox(parent); QTableWidget *table = qobject_cast<QTableWidget *>(this->parent()); if (index.column() == 6) { spinbox->setDecimals(2); } else if (index.column() == 7) { spinbox->setDecimals(3); } return spinbox; } else { return QStyledItemDelegate::createEditor(parent, option, index); } } virtual void iStyledItemDelegate::updateEditorGeometry(QWidget *editor, QStyleOptionViewItem const &option, QModelIndex const &index) const override { editor->setGeometry(option.rect); } virtual void iStyledItemDelegate::setEditorData(QWidget *editor, QModelIndex const &index) const override { if (index.data(Qt::EditRole).isValid() && ((index.column() == 6) || (index.column() == 7))) { double value = index.model()->data(index, Qt::EditRole).toString().toDouble(); QDoubleSpinBox *spinBox = qobject_cast<QDoubleSpinBox*>(editor); spinBox->setValue(value); } else { QStyledItemDelegate::setEditorData(editor, index); } } virtual void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override { if (index.data(Qt::EditRole).isValid() && ((index.column() == 6) || (index.column() == 7))) { QDoubleSpinBox *spin = qobject_cast<QDoubleSpinBox *>(editor); spin->interpretText(); double value = spin->value(); int decimals = spin->decimals(); QString text = QString::number(value, 'f', decimals); model->setData(index, text, Qt::EditRole); } else { QStyledItemDelegate::setModelData(editor, model, index); } }
For the column 7 (the last column), it works as expected, but for column 6, when I double click the cell, the value goes to "0.00", and I debuged it, find for column 6, the invoke sequence is:
createEditor -> setEditorData -> setModelData, when invoke the 'setData' in this function, it invoke 'setEditorData' once more. It's right?
-
@VRonin Finally, I found the reason, I forgot set the minimum and maximum of the spinbox, it seem it's default minimum value is "0", and I just need show a minus float number in column 6. So it is "rounded" to zero.
Can you check my code below, if it's a 'right' usage case?
class iStyledItemDelegate : public QStyledItemDelegate { public: explicit iStyledItemDelegate(QObject *parent = nullptr) : QStyledItemDelegate(parent) {} virtual ~iStyledItemDelegate() = default; virtual QWidget *createEditor(QWidget *parent, QStyleOptionViewItem const &option, QModelIndex const &index) const override { if ((index.column() == 6) || (index.column() == 7)) { QDoubleSpinBox *spinbox = new QDoubleSpinBox(parent); spinbox->installEventFilter(const_cast<iStyledItemDelegate*>(this)); QTableWidget *table = qobject_cast<QTableWidget *>(this->parent()); spinbox->setFrame(false); spinbox->setMinimum(-DBL_MAX); spinbox->setMaximum(DBL_MAX); if (index.column() == 6) { spinbox->setDecimals(2); } else if (index.column() == 7) { spinbox->setDecimals(3); } return spinbox; } else { return QStyledItemDelegate::createEditor(parent, option, index); } } virtual void updateEditorGeometry(QWidget *editor, QStyleOptionViewItem const &option, QModelIndex const &index) const override { editor->setGeometry(option.rect); } virtual void setEditorData(QWidget *editor, QModelIndex const &index) const override { QDoubleSpinBox *spinBox = qobject_cast<QDoubleSpinBox*>(editor); if (spinBox && ((index.column() == 6) || (index.column() == 7))) { double value = index.data(Qt::EditRole).toString().toDouble(); spinBox->setValue(value); } else { QStyledItemDelegate::setEditorData(editor, index); } } virtual void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override { QDoubleSpinBox *spin = qobject_cast<QDoubleSpinBox *>(editor); if (spin && ((index.column() == 6) || (index.column() == 7))) { spin->interpretText(); model->setData(index, spin->text(), Qt::EditRole); } else { QStyledItemDelegate::setModelData(editor, model, index); } } };
-
@VRonin Yes, I know it's not 'good' for 're-use'. But I can't find a method to set delegate to individual cells. In my case, take for example column 6. Some rows may need show 3, some rows may need show 2 decimals, depending on the column 2's contents. So I make this class dedicated to this. Any suggestions to implement my goal but no depending to 'index' in the derived class?
-
https://forum.qt.io/topic/71692/using-setdata-qt-editrole-xxx-with-qtablewidgetitem/10
Then set the NumOfDecimalsRole in a slot connected to the dataChanged of the model
Btw, looking at the code above it looks like you need 2 decimals in column 6 and 3 decimals in column 7 and this can be achieved with 2 delegates set on 2 columns with
setItemDelegeateForColumn
P.S.
model->setData(index, spin->text(), Qt::EditRole);
this will set a string, not a number so the decimals in a string are meaningless
-
@VRonin :),
- In my previous example, I simplified my usage case.
- I use strings instead of numbers, because the displayText function won't abandon the trailing zeros of the number strings. I think if I use 'model->setData(index, spin->value(), Qt::EditRole);' , then the displayText() will format the numbers for me, and I can't access 'index' in it, so it will always abandon the trailing zeros.
- So, my solution is actually avoid using numbers, but use text, and when create editor, I format the text to number, when user finish the editing, I format the values back to text and store it in the model.
-
It turns out to be a very good method which I seeks for a really long time. But now I use Qt 6.0.0, so the last return statement does not work well. So it needs to be changed into:
return QItemEditorFactory::createEditor(userType, parent);
And it is done!