[QStyledItemDelegate - qss] render custom widget and its stylesheet
-
Dear all,
this is my firts post on this forum, even if I'm following this for 2 years.
I've decided writing this post because I'm not able to find any solution right now.This is the problem:
I need to show some values using a listview and I'd like to use QStyledItemDelegate for data viewing.
I create AlertItemDelegate class that inheritance QStyledItemDelegate and I've rewritten paint method as follow:// header class AlertItemDelegate : public QStyledItemDelegate { Q_OBJECT public: using Ptr = AlertItemDelegate*; using ConstPtr = const AlertItemDelegate*; public: explicit AlertItemDelegate(QObject* parent = Q_NULLPTR); ~AlertItemDelegate(); public: void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const; QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const; }; // implementation void AlertItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { QVariant data = index.data(); if (data.canConvert<Error>()) { QString test = this->getTest(); auto&& styleOption = QStyleOptionViewItem(option); ErrorItemWidget widget; widget.resize(option.rect.width(), option.rect.height()); widget.setError(qvariant_cast<Error>(data)); painter->save(); painter->translate(option.rect.topLeft()); widget.render(painter); painter->restore(); } else { QStyledItemDelegate::paint(painter, option, index); } }
As you can see in code, during paintEvent I create a custom widget (ErrorItemWidget), set widget values and finally I render it.
This is paintEvent method, called when I render the widget;void ErrorItemWidget::paintEvent(QPaintEvent* event) { QWidget::paintEvent(event); QStyleOption opt; opt.initFrom(this); QStylePainter p(this); int testWidth = size().width(); int testheight = size().height(); p.save(); p.drawPrimitive(QStyle::PE_Widget, opt); /* *** PROBLEM !!! MOUSE HOVER DOES NOT WORK IN DELEGATE * QStyleOption opt seems not correctly initialized int value = opt.state; if (opt.state & QStyle::State_MouseOver) this->setStyleSheet("color: red"); else this->setStyleSheet("color: white"); QString test = this->styleSheet(); */ QRect r = opt.rect; p.setRenderHint(QPainter::Antialiasing); QPixmap icon; switch (this->errorType) { case ErrorType::INFO : icon.load(infoIcon); break; case ErrorType::WARNING : icon.load(warningIcon); break; case ErrorType::ERROR : icon.load(errorIcon); break; case ErrorType::FATAL : icon.load(fatalIcon); break; } int iconTopPosition = /*contenctRect.y() +*/ (r.height() - iconHeight)/2; icon = icon.scaled(iconWidth, iconHeight, Qt::AspectRatioMode::KeepAspectRatio, Qt::SmoothTransformation); p.drawPixmap(iconLeft, iconTopPosition, iconWidth, iconHeight, icon); QFont font = this->font(); p.setFont(font); QFontMetrics fMetrics(font); int capHeight = fMetrics.capHeight(); int yText = /*contenctRect.y()*/ + (r.height() - capHeight)/2 + capHeight; QString deviceKeyStr = Utils::getStringFromDeviceKey(static_cast<DeviceKey>(deviceKey)); p.drawText(deviceKeyLeft, yText, deviceKeyStr); p.drawText(errorIdLeft, yText, QString::number(errorId)); p.drawText(errorDescriptionLeft, yText, errorDescription); p.restore(); }
This works well, however there is something that I don't like.
- using render tecnique, during widget paintEvent seems that QStyleOption has not the same values of QStyleOption of delegate; how can I pass it from delegate to widget? Is it possible? This should be useful for rendering in case of mouseHover case.
- to draw widget correctly, I have to set sizeHint of the widget; in this case, I set minHeight = maxHeight using qss as follow:
.ErrorItemWidget { min-width: 30px; min-height: 46px; max-height: 46px; padding: 0px 16px; border-bottom-width: 2px; border-style: solid; font-size: 16px; qproperty-iconWidth: 24; qproperty-iconHeight: 24; qproperty-iconLeft: 16; qproperty-deviceKeyLeft: 70; qproperty-errorIdLeft: 160; qproperty-errorDescriptionLeft: 216; }
Is possible to get min-height or max-height values from qss? Or do I need to hardly write the same values onto code?
Thanks all in advance!
Nicola -
Hi and welcome to devnet,
Do you mean parse the style sheet somewhere to get the value ?
-
Hi, yes I mean something similar or, if possible, use qt parsing stylesheet system to take some qss values.
What I'd like to do is completely separate styles attribute from C++ code: set size, color, border, etc. in qss stylesheets and using C++ code for logic only.Is this possible using delegate?
My ideal process would be the following:
- inherit delegate class
- delegate::size-hint: create local widget and call its sizeHint method
- widget::size-hint: get min-height/max-height and others values from qss stylesheet
- delegate::paint: create local widget and render it (this will call widget paintEvent method)
- widget::paintEvent: get QStyleOption from delegate and render widget correctly (for hover cases for example)
Is this feasible?
Thanks,
Nicola -
From the beginning, I use this widget in another context so I'd like (if possible) use the same code for both purpose.
Anyway, does it exist a way to load stylesheet file (.qss) inside AlertItemDelegate::paint method?If so, I can paint everything in paint delegate method without creating temporary widget object.
-
This is what I've done right now.
Using the same code for painting and widget, I'm able to use a custom widget as usual; moreover, for listview I'm able to render the same widget correctly (because calling render method of widget, the widget is able to read .qss stylesheet)Here is the example:
this is a single widget
and this is the rendered widget inside the listview
in listview, as you can see, I'm able to render widget correctly and, in case of focus, I can change focus style using a custom property
this is the code:
void AlertItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { QVariant data = index.data(); if (data.canConvert<Error>()) { auto&& styleOption = QStyleOptionViewItem(option); ErrorItemWidget widget; if (styleOption.state & QStyle::State_HasFocus) widget.setProperty("Test", true); // <-- custom focus property else widget.setProperty("Test", QVariant::Invalid); // <-- remove property for normal state widget.resize(option.rect.width(), option.rect.height()); widget.setError(qvariant_cast<Error>(data)); painter->save(); painter->translate(option.rect.topLeft()); widget.render(painter); painter->restore(); } else { QStyledItemDelegate::paint(painter, option, index); } }
The drawback of this solution is that I need to create a widget for each model item (however, I've render 2000 elements and I don't see any performance hit)
The other drawback is that the height of each item is hardly code inside size-hint method, that is:QSize AlertItemDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const { int width = option.rect.width(); int height = 48; // <--- if I can get this from stylesheet, I'll be happy :) return QSize(width, height); }
The good thing is that I can change widget stylesheet using .qss file; inside C++ code there isn't any style detail.
Any suggestion to get height value from stylesheet?
Thanks all!
Nicola