Painting Widget with QPainter



  • Hi guys,

    I'm trying to paint a Widget in my custom Delegate, basically I just want to show a widget inside a cell of a tableView.

    I found multiple way to do that, but it's not working as I want.

    Solution 1:
    Use the .render method of QWidget to render it
    Problem : Nothing is painted (see "screenshot":https://www.dropbox.com/s/qeiisgzw7e5t1m1/solution0_render.png)
    @
    EditTargetPowerWidget *widgetTarget = new EditTargetPowerWidget();
    widgetTarget->targetStart->setText("Test");
    widgetTarget->render(painter, option.rect.center(), option.rect, QWidget::DrawChildren );
    @

    Solution 2:
    Convert the QWidget to a pixmap and then paint the pixmap with the painter
    problem : Color and background all messed up (see "screenshot":https://www.dropbox.com/s/mr92gcibngvir6x/solution2_pixmap.png)

    @
    EditTargetPowerWidget *widgetTarget = new EditTargetPowerWidget();
    QPixmap pix = QPixmap::grabWidget(widgetTarget);
    painter->drawPixmap(option.rect, pix);
    @

    Solution 3:
    Try to replicate the Widget and draw the stuff manually using drawText
    Problem : Ok to display one line of data, but I don't know how to replicate my GridLayout in here. (see "screenshot":https://www.dropbox.com/s/3m8eh5sc174iyxd/solution3_drawText.png)

    @
    std::shared_ptr<Interval> interval(qvariant_cast<std::shared_ptr<Interval>>( index.model()->data(index, Qt::DisplayRole) ));

        int targetStepPower = interval->getPowerStepType();
        qDebug() << "TARGET STEP!" << targetStepPower;
        QString str_Step = Interval::getStepTypeFromInt( targetStepPower );
        double startFTP = interval->getFTP_start() * 100;
        double endFTP = interval->getFTP_end() * 100;
        int range = interval->getFTP_range();
    
        painter->drawText(option.rect, Qt::AlignLeft | Qt::AlignVCenter, str_Step); //Draw all other data also.. with good layout?@
    

    If someone has figured how to use the render() method of QWidget, or fix the QPixmap display problem, I think it would be a better solution than using drawText... thank you!


  • Moderators

    ad solution 1:
    make sure that your painter has not been translated and paints to the correct location on the viewport. "option.rect.center()" doesn't seem correct here...
    Please post the whole painting code of your delegate.

    i wouldn't suggest solution 3... for sake of simplicity.



  • I think the solution 3 could be used with some "tricks", in order to have texts arranged in "subcells" in a "cell" ... what is a "cell"?: it is thought as successive standard(qt) table items in a row

    so take any successive items in a table row, in the amount of your grid layout columns and then controls the drawText as follow:

    1. for drawing text in a certain column(subcell) with index.column()
    2. for drawing text in certain subcell's row with new line character "\n"
      i.e.
      @ painter->drawText(option.rect.adjusted(4,4,0,0),QString("\n")+your text@

    I used this and it worked, and for changing text font or color by QSS I used dummy/hidden widgets styled externally and using their values in delegate's painter



  • Thanks for your help,

    For the render method of QWidget, I don't really understand the arguments that I need to pass. Usually I use painter and option.rect (where to display) in order to paint, so i'm kind of lost with those 4 parameters

    Link to function :
    http://qt-project.org/doc/qt-5.0/qtwidgets/qwidget.html#render-2

    Here is the full paint method of my delegate:
    @//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    void SpinBoxDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const {

    if (option.state & QStyle::State_Selected) {
        painter->setPen(QPen( Qt::red, 5 ));
        painter->drawRect(option.rect);
        return;
    }
    
    painter->setPen(QPen( Qt::white, 1 ));
    
    /// Type
    if (index.column() == 0) {
        int value = index.model()->data(index, Qt::DisplayRole).toInt();
        QString intervalType = Interval::getTypeFromInt(value);
    
        painter->drawText(option.rect, Qt::AlignLeft | Qt::AlignVCenter, intervalType);
    }
    
    /// Duration
    else if (index.column() == 1) {
        QTime time = index.model()->data(index, Qt::DisplayRole).toTime();
        QString toDisplay = Util::showQTimeAsString(time);
        painter->drawText(option.rect, Qt::AlignLeft | Qt::AlignVCenter, toDisplay );
    }
    
    /// Display Message
    else if (index.column() == 2) {
        QString msg = index.model()->data(index, Qt::DisplayRole).toString();
        painter->drawText(option.rect, Qt::AlignLeft | Qt::AlignVCenter, msg);
    }
    
    /// Target Power
    else if (index.column() == 3) {
    
    
    
        //// SOLUTION 1, NOTHING IS DRAW
        EditTargetPowerWidget *widgetTarget = new EditTargetPowerWidget();
        widgetTarget->targetStart->setText("Test");
        widgetTarget->render(painter, QPoint(), option.rect, QWidget::DrawChildren );
    

    // widgetTarget->paintEngine()

        /// SOLUTION 2 - NOT GOOD LOOKING (Color messed up)
        //        EditTargetPowerWidget *widgetTarget = new EditTargetPowerWidget();
        //        QPixmap pix = QPixmap::grabWidget(widgetTarget);
        //        painter->drawPixmap(option.rect, pix);
    
    
        /// SOLUTION 3, DRAW IT BY HAND - WORKS BUT HARD TO REPLICATE WIDGET WITH TEXT
        //        std::shared_ptr<Interval> interval(qvariant_cast<std::shared_ptr<Interval>>( index.model()->data(index, Qt::DisplayRole) ));
    
        //        int targetStepPower = interval->getPowerStepType();
        //        qDebug() << "TARGET STEP!" << targetStepPower;
        //        QString str_Step = Interval::getStepTypeFromInt( targetStepPower );
        //        double startFTP = interval->getFTP_start() * 100;
        //        double endFTP = interval->getFTP_end() * 100;
        //        int range = interval->getFTP_range();
    
        //        painter->drawText(option.rect, Qt::AlignLeft | Qt::AlignVCenter, str_Step);
    
    
    }
    
    
    /// Standard delegate display - Display Message
    else {
        QStyleOptionViewItem viewOption(option);
        viewOption.palette.setColor(QPalette::Text, QColor(Qt::white));
        QStyledItemDelegate::paint(painter, viewOption, index);
    }
    

    }@

    I will try to fix #1 and then use #3 as last resort.
    Thanks!


  • Moderators

    try this:
    @
    painter->save();
    EditTargetPowerWidget *widgetTarget = new EditTargetPowerWidget();
    widgetTarget->resize( option.rect.size() );
    widgetTarget->targetStart->setText("Test");
    painter->translate(option.rect.topLeft());
    widgetTarget->render(painter, QPoint(), QRegion(), QWidget::DrawChildren );
    painter->restore();
    @

    And another suggestion:
    Don't create a new widget on every time your paint() method is called. No need to allocate new memory every time, beside you never delete this widget, thus it leaks.
    Rather hold a single instance in your delegate class and reuse it when needed.



  • Thanks Raven it's working now!

    https://www.dropbox.com/s/no0edjn08ei66ap/workingPaint.png

    One last thing, I'll check more but if you know already, Inside this custom widget, I have QLabels but the main application stylesheet doesn't get applied to those label it seems (they should be white)
    Or i can just use .setStylesheet but I like all my presentation code to be in the same place.

    thanks again!


  • Moderators

    set the stylesheet on the QApplication instance, so even parent-less widgets get the style, or

    set a parent-widget, so that i derives the stylesheet style from a widget which already has got it set in the hierarchy



  • Oh I totally forgot to give the widget a parent, there is the problem.
    Thanks again!



  • The display is working fine now, but my delegate has a memory leak in the paint function now.

    I made a video showing the problem in action :
    https://www.youtube.com/watch?v=b882e4cEREE&feature=youtu.be

    I have noted that it is in the column that I used QWidget.render(). So in the code below, if I comment column 3,4 and 5, I don't have a memory leak.
    Sorry I know I asked a lot of question already, hopefully I can give back to Qt..

    Do you think that the line
    "widgetTarget->render(painter, QPoint(), QRegion(), QWidget::DrawChildren );"
    could trigger an infinite loop? I'm failing to see where all that memory goes.. I should probably learn to use the debugger also -_-

    @//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    void SpinBoxDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const {

    qDebug() << "paintNow...";
    if (option.state & QStyle::State_Selected) {
        painter->setPen(QPen( Qt::red, 5 ));
        painter->drawRect(option.rect);
        return;
    }
    
    painter->setPen(QPen( Qt::white, 1 ));
    
    /// Type
    if (index.column() == 0) {
        int value = index.model()->data(index, Qt::DisplayRole).toInt();
        QString intervalType = Interval::getTypeFromInt(value);
    
        painter->drawText(option.rect, Qt::AlignLeft | Qt::AlignVCenter, intervalType);
    }
    
    /// Duration
    else if (index.column() == 1) {
        QTime time = index.model()->data(index, Qt::DisplayRole).toTime();
        QString toDisplay = Util::showQTimeAsString(time);
        painter->drawText(option.rect, Qt::AlignLeft | Qt::AlignVCenter, toDisplay );
    }
    
    /// Display Message
    else if (index.column() == 2) {
        QString msg = index.model()->data(index, Qt::DisplayRole).toString();
        painter->drawText(option.rect, Qt::AlignLeft | Qt::AlignVCenter, msg);
    }
    
    /// Target Power
    else if (index.column() == 3) {
    
    
        EditTargetPowerWidget *widgetTarget = new EditTargetPowerWidget(ptrParent, "POWER");
        std::shared_ptr<Interval> interval(qvariant_cast<std::shared_ptr<Interval>>( index.model()->data(index, Qt::DisplayRole) ));
    
        int targetStepPower = interval->getPowerStepType();
        double startFTP = interval->getFTP_start() * 100;
        double endFTP = interval->getFTP_end() * 100;
        int range = interval->getFTP_range();
    
        widgetTarget->stepComboBox->setCurrentIndex(targetStepPower);
        widgetTarget->targetStartValue->setValue(startFTP);
        widgetTarget->targetEndValue->setValue(endFTP);
        widgetTarget->targetRangeValue->setValue(range);
    
        painter->save();
        widgetTarget->resize( option.rect.size() );
        painter->translate(option.rect.topLeft());
        widgetTarget->render(painter, QPoint(), QRegion(), QWidget::DrawChildren );
        painter->restore();
    
    }
    
    /// Target Cadence
    else if (index.column() == 4) {
    
    
        EditTargetPowerWidget *widgetTarget = new EditTargetPowerWidget(ptrParent, "CADENCE");
        std::shared_ptr<Interval> interval(qvariant_cast<std::shared_ptr<Interval>>( index.model()->data(index, Qt::DisplayRole) ));
    
        int targetStepCadence = interval->getCadenceStepType();
        int startCadence = interval->getCadence_start();
        int endCadence = interval->getCadence_end();
        int range = interval->getCadence_range();
    
        widgetTarget->stepComboBox->setCurrentIndex(targetStepCadence);
        widgetTarget->targetStartValue->setValue(startCadence);
        widgetTarget->targetEndValue->setValue(endCadence);
        widgetTarget->targetRangeValue->setValue(range);
    
        painter->save();
        widgetTarget->resize( option.rect.size() );
        painter->translate(option.rect.topLeft());
        widgetTarget->render(painter, QPoint(), QRegion(), QWidget::DrawChildren );
        painter->restore();
    
    }
    
    /// Target Cadence
    else if (index.column() == 5) {
    
    
        EditTargetPowerWidget *widgetTarget = new EditTargetPowerWidget(ptrParent, "HR");
        std::shared_ptr<Interval> interval(qvariant_cast<std::shared_ptr<Interval>>( index.model()->data(index, Qt::DisplayRole) ));
    
        int targetStepHR = interval->getHRStepType();
        double startHR = interval->getHR_start() *100;
        double endHR = interval->getHR_end() *100;
        int range = interval->getHR_range();
    
        widgetTarget->stepComboBox->setCurrentIndex(targetStepHR);
        widgetTarget->targetStartValue->setValue(startHR);
        widgetTarget->targetEndValue->setValue(endHR);
        widgetTarget->targetRangeValue->setValue(range);
    
        painter->save();
        widgetTarget->resize( option.rect.size() );
        painter->translate(option.rect.topLeft());
        widgetTarget->render(painter, QPoint(), QRegion(), QWidget::DrawChildren );
        painter->restore();
    
    }
    
    
    /// Standard delegate display - Display Message
    else {
        QStyleOptionViewItem viewOption(option);
        viewOption.palette.setColor(QPalette::Text, QColor(Qt::white));
        QStyledItemDelegate::paint(painter, viewOption, index);
    }
    

    }@



  • something wrong with the line
    @widgetTarget->render(painter, QPoint(), QRegion(), QWidget::DrawChildren );@
    i comment this and don't get memory leak.. i'll post if I find a solution

    [Edit: The function paint of my delegate is being called non-stop when the TableView is active.. still investigating why]



  • Finally I used drawText as it is easier and more versatile, doesn't have the memory problem I had with QWidget.render()
    needs more coding but at least what you code is what you get
    Thanks for your help again! :)

    @ else if (index.column() == 3 && paint) {

        painter->save();
        EditTargetPowerWidget *widgetTarget = new EditTargetPowerWidget(ptrParent, "POWER");
        std::shared_ptr<Interval> interval(qvariant_cast<std::shared_ptr<Interval>>( index.model()->data(index, Qt::DisplayRole) ));
    
        int targetStepPower = interval->getPowerStepType();
        double startFTP = interval->getFTP_start() * 100;
        double endFTP = interval->getFTP_end() * 100;
        int range = interval->getFTP_range();
    
        widgetTarget->stepComboBox->setCurrentIndex(targetStepPower);
        widgetTarget->targetStartValue->setValue(startFTP);
        widgetTarget->targetEndValue->setValue(endFTP);
        widgetTarget->targetRangeValue->setValue(range);
    

    // widgetTarget->resize( option.rect.size() );
    // painter->translate(option.rect.topLeft());
    // widgetTarget->render(painter, QPoint(), QRegion(), QWidget::DrawWindowBackground );

        QString targetStep_str = Interval::getStepTypeFromInt(targetStepPower);
        QString target_str = "["+ QString::number(startFTP)  + " - " + QString::number(endFTP) + "]";
        QString range_str = "±" + QString::number(range);
        QString target1 = tr("Target: ");
        QString range1 = tr("Range: ");
        QString watts = tr(" Watts");
        QString pFtp = tr(" % FTP");
    
    
        painter->drawText(option.rect, Qt::AlignLeft | Qt::AlignVCenter,
                          targetStep_str + "\n" +
                          target1 +  target_str + pFtp + "\n" +
                          range1 + range_str + watts);
    
    
        painter->restore();
    
    
    }@


  • I modified my code to improve memory and find where is the problem. I think it is a memory leak with the createEditor() that doesn't get deleted after each use.

    I would like to know if there is an example of this technique
    "source":http://qt-project.org/doc/qt-5.0/qtwidgets/itemviews-spinboxdelegate.html
    Furthermore it is also possible to reuse (and avoid deleting) the editor widget by reimplementing the destroyEditor() function

    I tried but the createEditor and setEditorData are "const" function and I can't store any pointer there..
    [EDIT: I removed the const in createEditor but now the function is now longer called, I wanted to create all my editor in the Constructor and reuse the same.. this technique doesn't work -_-]

    I changed the way I change my model using a pointer in Qvariant rather than passing the whole object in a Qvariant, it helps but the memory used is still too high,

    @//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    void SpinBoxDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const {

    /// Type
    if (index.column() == 0) {
        IntervalComboBox *comboBox = static_cast<IntervalComboBox*>(editor);
        int value = comboBox->currentIndex();
        model->setData(index, value, Qt::EditRole);
    }
    
    /// Duration
    else if (index.column() == 1) {
        QTimeEdit *timeEdit = static_cast<QTimeEdit*>(editor);
        QTime time1 = timeEdit->time();
        model->setData(index, time1, Qt::EditRole);
    }
    
    /// Display Message
    else if (index.column() == 2) {
        QLineEdit *lineEdit = static_cast<QLineEdit*>(editor);
        QString msg = lineEdit->text();
        model->setData(index, msg, Qt::EditRole);
    }
    
    /// Target Power
    else if (index.column() == 3) {
        
        EditTargetPowerWidget *targetWidget = static_cast<EditTargetPowerWidget*>(editor);
        
        int targetStepPower = targetWidget->stepComboBox->currentIndex();
        Interval::StepType stepType = static_cast<Interval::StepType>( targetStepPower );
        double startFTP = targetWidget->targetStartValue->value()/100;
        double endFTP = targetWidget->targetEndValue->value()/100;
        int range = targetWidget->targetRangeValue->value();
        
        
        Interval *interval = (Interval*) index.model()->data(index, Qt::DisplayRole).value<void *>();
        interval->setPowerData(stepType, startFTP, endFTP, range);
        
        /// MEMORY STILL INCREASE BY 2MB EACH EDIT, pointer *targetWidget not deleted?
        
        /// Break model/view architecture here...
        ///        model->setData(index, variant, Qt::EditRole);
    }
    

    @



  • Found the problem!

    In the paint(), I was using a Widget and not deleting it after using it..

    @ else if (index.column() == 3 && paint) {

        painter->save();
        EditTargetPowerWidget *targetWidget = new EditTargetPowerWidget(ptrParent, "POWER");
    
    
        Interval *interval = (Interval*) index.model()->data(index, Qt::DisplayRole).value<void *>();
    
        int targetStepPower = interval->getPowerStepType();
        double startFTP = interval->getFTP_start() * 100;
        double endFTP = interval->getFTP_end() * 100;
        int range = interval->getFTP_range();
    
        targetWidget->stepComboBox->setCurrentIndex(targetStepPower);
        targetWidget->targetStartValue->setValue(startFTP);
        targetWidget->targetEndValue->setValue(endFTP);
        targetWidget->targetRangeValue->setValue(range);
    
    
        targetWidget->resize( option.rect.size() );
        painter->translate(option.rect.topLeft());
        targetWidget->render(painter, QPoint(), QRegion(), QWidget::DrawChildren );
    
    
        painter->restore();
        delete targetWidget;
    
    
    }@


  • Found the problem!

    I forgot to delete a pointer of my QWidget in the paint() function. this was increasing the memory usage each time.

    @
    else if (index.column() == 3 && paint) {

        painter->save();
        EditTargetPowerWidget *targetWidget = new EditTargetPowerWidget(ptrParent, "POWER");
    
        Interval *interval = (Interval*) index.model()->data(index, Qt::DisplayRole).value<void *>();
    
        int targetStepPower = interval->getPowerStepType();
        double startFTP = interval->getFTP_start() * 100;
        double endFTP = interval->getFTP_end() * 100;
        int range = interval->getFTP_range();
    
        targetWidget->stepComboBox->setCurrentIndex(targetStepPower);
        targetWidget->targetStartValue->setValue(startFTP);
        targetWidget->targetEndValue->setValue(endFTP);
        targetWidget->targetRangeValue->setValue(range);
    
        targetWidget->resize( option.rect.size() );
        painter->translate(option.rect.topLeft());
        targetWidget->render(painter, QPoint(), QRegion(), QWidget::DrawChildren );
    
    
        painter->restore();
        delete targetWidget;
    
    }@

Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.