Solved Style change of own widget in QTableView
-
Hello,
I implemented a descendant of QLineEdit. The descendant has only one difference from QLineEdit - it overrides focusOutEvent which checks if inserted text is valid (I am using 3rd party library for validation which is not suitable to be applied via own QValidator, thus I perform the validation in that method) and if it is not it changes text color to red (setStyleSheet). I am using this widget within QTableView where it is inserted via setItemDelegateForColumn using own derivate of QItemDelegate which returns the widget from createEditor method.Problem is that whenever I add invalid value to a cell with that widget the text color does not change. When I use this widget alone, i.e. outside of QTableView as a single element in some layout, it behaves correctly. Does QTableView suppress own style of inner widgets by default? Or, is there a different problem? Thx for help.
#include <QLineEdit> #include <QFocusEvent> class ExpressionEdit : public QLineEdit { Q_OBJECT public: ExpressionEdit(QWidget* parent = nullptr); ExpressionEdit(const QString& contents, QWidget* parent = nullptr); signals: void validStateChanged(bool isValid); protected: virtual void focusOutEvent(QFocusEvent* e) override; private: bool mValidState; }; ExpressionEdit::ExpressionEdit(QWidget* parent) : QLineEdit(parent) { this->setText("0"); this->mValidState = true; } ExpressionEdit::ExpressionEdit(const QString& contents, QWidget* parent) : QLineEdit(contents, parent) { this->mValidState = true; } void ExpressionEdit::focusOutEvent(QFocusEvent* e) { // 3rd party lib for validation bool valid = Expression::isValid(this->text().toStdString(), Common::fnVariables()); if (valid) { this->setStyleSheet("color: black;"); } else { this->setStyleSheet("color: red; font-weight: bold;"); } if ((mValidState == true && valid == false) || (mValidState == false && valid == true)) { emit validStateChanged(valid); this->mValidState = valid; } QLineEdit::focusOutEvent(e); }
#include <QItemDelegate> #include "expressionedit.h" class ExpressionEditDelegate: public QItemDelegate { Q_OBJECT public: ExpressionEditDelegate(QObject* parent = nullptr); signals: void validStateChanged(bool valid); private slots: void changeValidState(bool valid); protected: QWidget* createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override; void setEditorData(QWidget * editor, const QModelIndex & index) const override; void setModelData(QWidget * editor, QAbstractItemModel * model, const QModelIndex & index) const override; void updateEditorGeometry(QWidget * editor, const QStyleOptionViewItem & option, const QModelIndex & index) const override; }; ExpressionEditDelegate::ExpressionEditDelegate(QObject *parent) : QItemDelegate(parent) { } QWidget* ExpressionEditDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const { ExpressionEdit *editor = new ExpressionEdit(parent); connect(editor, &ExpressionEdit::validStateChanged, this, &ExpressionEditDelegate::changeValidState); return editor; } void ExpressionEditDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const { QString value = index.model()->data(index, Qt::EditRole).toString(); ExpressionEdit *line = static_cast<ExpressionEdit*>(editor); line->setText(value); } void ExpressionEditDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const { ExpressionEdit *line = static_cast<ExpressionEdit*>(editor); QString value = line->text(); model->setData(index, value); } void ExpressionEditDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const { editor->setGeometry(option.rect); } void ExpressionEditDelegate::changeValidState(bool valid) { emit validStateChanged(valid); }
// Delegate assigned to QTableView ui->table->setItemDelegateForColumn(2, new ExpressionEditDelegate(this));
-
@thompsonx
what is connected to or what happens on the delegate's validStateChanged signal?
I guess you are just reopening a new editor? Means a new editor widget instance is created and the set stylesheet is lost.The short way would be to do the validation also in the delegates createEditor() method (or even in the custom line edit's focusIn event) or also reimplement the delegates's editorEvent() method and emit
QAbstractItemDelegate::closeEditor(editor, QAbstractItemDelegate::EndEditHint)
accordingly to the received events. Here theQAbstractItemDelegate::EndEditHint
is important. -
validStateChanged is used to inform connected elements whether an ExpressionEdit's content is valid or not. I have one dialog window where multiple elements are including single ExpressionEdits, tables with ExpressionEdits and other elements used for edit forms. This signal is used to propagate the information about valid state to parent elements. The signal stops at the element controlling dialog window and this dialog cannot be confirmed until all elements are valid.
Nevertheless, the validation works correctly. I was asking about the style change inside ExpressionEdit::focusOutEvent(). It does not change when an ExpressionEdit is inside the delegate and table. When it is located in a layout or frame it works completely fine. I guess that QTableView blocks own style of inner elements. How should I change the code to make the style change visible within the table?
-
@thompsonx
try a more specific stylesheet:this->setStyleSheet("ExpressionEdit { color: red; font-weight: bold; }");
Nevertheless the default behavior for editor widgets is that they are closed on focus out. Thats why i assumed a new editor instance is reopened/created immediately which of course leads to loosing the old stylesheet set.
-
Your assumption was correct. After focutOutEvent the ExpressionEdit is deleted and its content is copied to QTableView's model, therefore I could not see the style change since the table creates ExpressionEdit only for editting. For displaying the cell's content it uses delegate's method paint.
So, I solved it by overriding paint method in my delegate. I moved from ExpressionEdit to QLineEdit because it is not necessary. I perform the validation during painting. Problem solved. Now, I just need to figure out how to inform parents about (in)valid state but that is out of this topic (probably, I will add some method which should be called before dialog confirmation).
@raven-worx Thank you. If there is a better way to do the cell coloring let me know, otherwise this topic is solved.
There is how I changed my delegate:
ExpressionEditDelegate::ExpressionEditDelegate(QObject *parent) : QItemDelegate(parent) { } QWidget* ExpressionEditDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const { QLineEdit *editor = new QLineEdit(parent); return editor; } void ExpressionEditDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const { QString value = index.model()->data(index, Qt::EditRole).toString(); QLineEdit *line = static_cast<QLineEdit*>(editor); line->setText(value); } void ExpressionEditDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const { QLineEdit *line = static_cast<QLineEdit*>(editor); QString value = line->text(); model->setData(index, value); } void ExpressionEditDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const { editor->setGeometry(option.rect); } void ExpressionEditDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { QVariant data = index.data(); QString text = data.toString(); QStyleOptionViewItem newOption(option); if (!ExpressionEdit::isValid(text)) { newOption.font.setBold(true); newOption.palette.setColor(QPalette::Text, Qt::red); } QItemDelegate::paint(painter, newOption, index); }