QStyledItemDelegate drawing custom widget failed
Asked Answered
D

1

6

In one of my projects I'm using a QTableWidget in order to display some complex computational results. In order to increase the readability of the table I'm in need to display two aligned values inside of a single table cell.

Later on I want to customize the widget even more by using colors or arrows etc..

For this I derived from QStyledItemDelegate and I called table ->setItemDelegate(new TwoNumbersDelegate) on my QTableWidget instance.

For some reasons the QFrame is never display. I really tried everything. Strangely, a call to drawLine gives some result, but only in the left cell on the top.

My idea is, that calling mFrame->render(...) is not the correct way to do it, but what is the correct way?

My include file is:

#pragma once

#include <QStyledItemDelegate>
class QLabel;

    class TwoNumbersDelegate : public QStyledItemDelegate {
    public:
        TwoNumbersDelegate(QObject* parent = nullptr);

        virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;

        virtual QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override;

    private:
        QLabel* mLeft;
        QLabel* mRight;
        QFrame* mFrame;
    };

My cpp-File is:

#include "TwoNumbersDelegate.h"
#include <QLabel>
#include <QPainter>
#include <QHBoxLayout>

TwoNumbersDelegate::TwoNumbersDelegate(QObject* parent /*= nullptr*/) : QStyledItemDelegate(parent)
{
    mLeft = new QLabel("%1");
    mRight = new QLabel("%2");
    mFrame = new QFrame;
    mFrame->setLayout(new QHBoxLayout);
    mFrame->layout()->addWidget(mLeft);
    mFrame->layout()->addWidget(mRight);
}

void TwoNumbersDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    auto data=index.data(Qt::EditRole);
    auto list=data.toList();
    if (list.size() != 2) {
        QStyledItemDelegate::paint(painter, option, index);
    }
    auto leftValue=list.at(0).toDouble();
    auto rightValue=list.at(1).toDouble();
    mLeft->setText(QString("%1").arg(leftValue));
    mRight->setText(QString("%2").arg(rightValue));
    mLeft->render(painter, QPoint(), option.rect);
    painter->drawLine(4, 4, 7, 7); // Draws Line, but not in every cell of my table?
}

QSize TwoNumbersDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    return mFrame->minimumSizeHint();
}
Delete answered 30/3, 2017 at 8:40 Comment(6)
But QStyledItemDelegate is not derived from QWidget? How can it have a layout? Should I make it a QWidget?Delete
Many thanks anyway. I'm really lost with this stuff. :-)Delete
I propose you to use QTextDocument::drawContents to draw formatted content. Sample code may be found there: #16445058Quincyquindecagon
This would not suffice, as I'm planing to enhance this widget. E.g. using icons or other tools to enhance readability.Delete
There are no problems to use icons in QTextDocument. It supports rich text formatting and Qt-html subset. So everything that you can output to QTextEdit are available for fast rendering. So you may try something like this: <img src=":/myres/someicon.png" />.Quincyquindecagon
Thanks for the hint. I will consider you suggestion as a potential solution to my problem. :-)Delete
S
7

A few potential issues here:

Layout

Since the widget is invisible, the layout isn't being calculated, so things might be drawn out of place, calls to resize ignored, etc.

To ensure the layout is updated, in the constructor, add

mFrame->setAttribute(Qt::WA_DontShowOnScreen, true);
mFrame->show();

This makes the widget behave as if it is visible (which we want), but doesnt draw anything to the screen directly.

Paint location

Drawing with a delegate is clipped to the cell, so if you draw in the wrong place, you won't see anything at all. The bounds of the cell are given by options.rect, and these coordinates are in terms of the qtableview. So your drawline command only draws in the top-left cell.

This clipping also means you don't need to worry about what region of the widget to render, just translate the painter to use the cell's coordinates, and paint the whole widget.

painter->save();
painter->translate(option.rect.topLeft());
mFrame->render(painter, QPoint(), QRegion(), QWidget::DrawChildren );
painter->restore();

All together, here is an updated .cpp file:

TwoNumbersDelegate::TwoNumbersDelegate(QObject* parent /*= nullptr*/) : QStyledItemDelegate(parent)
{
    mLeft = new QLabel("%1");
    mRight = new QLabel("%2");
    mFrame = new QFrame;
    mFrame->setLayout(new QHBoxLayout);
    mFrame->layout()->addWidget(mLeft);
// you could add a spacer here maybe
    mFrame->layout()->addWidget(mRight);

    mFrame->setAttribute(Qt::WA_DontShowOnScreen, true);
    mFrame->show();
}

void TwoNumbersDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    auto data=index.data(Qt::EditRole);
    auto list=data.toList();
    if (list.size() != 2) {
        QStyledItemDelegate::paint(painter, option, index);
    }

    mLeft->setText(list.at(0).toString());
    mRight->setText(list.at(1).toString());
    mFrame->resize(opt.rect.size());

    // if there are still layout problems maybe try adding:
    // mFrame->layout()->invalidate();
    // mFrame->layout()->activate();

    painter->save();
    painter->translate(option.rect.topLeft());
    mFrame->render(painter, QPoint(), QRegion(), QWidget::DrawChildren );
    // painter->drawLine(4, 4, 7, 7); // Draws Line in every cell now
    painter->restore();
}

QSize TwoNumbersDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    return mFrame->minimumSizeHint();
}

Let me know if you need any more help. Also, a warning: I haven't so much as run this through a compiler, so let me know if you still need help, or if there are any little errors to correct.

Superintend answered 30/3, 2017 at 12:21 Comment(1)
Awesome. Works as you promised. :-) Maybe you can correct the following typos. widgetTarget should be mFrame and mFrame.resize(...) should be rather mFrame->resize(...).Delete

© 2022 - 2024 — McMap. All rights reserved.