5

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();
}
Aleph0
  • 5,816
  • 4
  • 29
  • 80
  • But `QStyledItemDelegate` is not derived from `QWidget`? How can it have a layout? Should I make it a QWidget? – Aleph0 Mar 30 '17 at 09:23
  • Many thanks anyway. I'm really lost with this stuff. :-) – Aleph0 Mar 30 '17 at 09:26
  • 1
    I propose you to use `QTextDocument::drawContents` to draw formatted content. Sample code may be found there: https://stackoverflow.com/questions/16444558/how-to-force-qabstractitemview-recalculate-items-sizehints – Dmitry Sazonov Mar 31 '17 at 14:21
  • This would not suffice, as I'm planing to enhance this widget. E.g. using icons or other tools to enhance readability. – Aleph0 Apr 03 '17 at 06:07
  • 1
    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: ``. – Dmitry Sazonov Apr 03 '17 at 06:45
  • Thanks for the hint. I will consider you suggestion as a potential solution to my problem. :-) – Aleph0 Apr 03 '17 at 06:46

1 Answers1

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.

Community
  • 1
  • 1
Joseph Ireland
  • 2,465
  • 13
  • 21
  • Awesome. Works as you promised. :-) Maybe you can correct the following typos. `widgetTarget` should be `mFrame` and `mFrame.resize(...)` should be rather `mFrame->resize(...)`. – Aleph0 Mar 30 '17 at 12:27