[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.

    1. 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.
    2. 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


  • Lifetime Qt Champion

    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:

    1. inherit delegate class
    2. delegate::size-hint: create local widget and call its sizeHint method
    3. widget::size-hint: get min-height/max-height and others values from qss stylesheet
    4. delegate::paint: create local widget and render it (this will call widget paintEvent method)
    5. widget::paintEvent: get QStyleOption from delegate and render widget correctly (for hover cases for example)

    Is this feasible?

    Thanks,
    Nicola



  • Sorry if it sounds dumb but why are you creating a whole widget if you then implement the painting manually. Can't you just do in AlertItemDelegate::paint everything you do inside ErrorItemWidget::paintEvent and solve all your problems?



  • 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
    0_1562332451778_widget.png

    and this is the rendered widget inside the listview
    0_1562332527764_listview-render.png

    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