PDA

View Full Version : QScrollArea vs. QAbstractScrollArea



zaphod.b
6th July 2009, 13:22
Hi all,

I'm trying to write a thin Qt wrapper which allows to scroll a canvas rendered by a 3rd-party class Foo.

This seems to work fine:


class MyWrapper : public QScrollArea
//class MyWrapper : public QAbstractScrollArea
{
Q_OBJECT

private:
Foo _foo;
QLabel _canvas;

public:
MyWrapper()
{
setWidget(&_canvas); //QScrollArea
// setViewport(&_canvas); //QAbstractScrollArea
_canvas.setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
_foo.setHook(hook, this);
}

//3rd-party class Foo signals "render finished" through this callback
friend void hook(void *data, bool canceled);

public slots:
void showRendered()
{
QImage img(reinterpret_cast<const uchar *>(_foo.buffer()),
_pdfView->bufferWidth(),
_pdfView->bufferHeight(),
QImage::Format_ARGB32);
_canvas.setPixmap(QPixmap::fromImage(img));
_canvas.adjustSize();
}

protected:
void paintEvent(QPaintEvent *event)
{
_foo.update();
}

void resizeEvent(QResizeEvent *event)
{
_foo.resize(viewport()->width(), viewport()->height());
}
};

void hook(void *data, bool canceled)
{
MyWrapper*wrapper = (PdfWidget *)data;
//Foo::update() spawns a sparate thread
QMetaObject::invokeMethod(wrapper, "showRendered", Qt::AutoConnection);
}


As Foo provides everything needed to move/zoom/resize/pan the canvas, QAbstractScrollArea seems more appropriate to inherit from.
However, if I apply the changes indicated in the above code snippet, nothing is painted at all in the viewport.

What am I missing? Any help appreciated.

wysota
6th July 2009, 13:30
If you derive from the abstract class, you have to do the painting yourself in the paintEvent.

zaphod.b
6th July 2009, 13:40
Could you please elaborate on this?
I thought setting the pixmap in showRendered() would do the painting, asynchronously though. Do I need to literally use a QPainter, and if so, why?

wysota
6th July 2009, 13:46
There is no asynchronous painting on widgets in Qt. Everything has to be done in the paintEvent. Once a paint event is scheduled for a widget, Qt clears its contents and so you see nothing. You should store a pixmap with what Foo draws for you and render the appropriate part of it in the paintEvent() to the viewport.

zaphod.b
6th July 2009, 13:55
Thanks for your clear advice, I'll go for it.


Once a paint event is scheduled for a widget, Qt clears its contents and so you see nothing.
Didn't know the clearing, thx for pointing me there.

If there's a problem with the asynchronous call, why does the code [seem to] work when subclassing QScrollArea?

wysota
6th July 2009, 14:47
Because you are using setPixmap() which sets a pixmap on the label and schedules a paint event for the label. So in this case everything is correct, there is nothing "asynchronous" related to painting on widgets there. But you shouldn't do the thing you do by overriding paintEvent(). If at all, you should override resizeEvent() and call update() on _foo there.

zaphod.b
6th July 2009, 17:34
Thx again wysota, I very much appreciate your time and patience! However, I'm still not clear here.

The only difference between MyWrapper inheriting from QScrollArea or QAbstractScrollArea - apart from the base class of course - is the way _canvas is made known to it. So it seems feasible that "ownership" means something different depending on whether I call QScrollArea::setWidget(&_canvas) or QAbstractScrollArea::setViewport(&_canvas). But I can't figure out in which way different.

I understand that if I use a QPainter to paint a widget, I can only do so from within paintEvent(), as the QPainter class reference warns. What I don't understand is why I need to use a QPainter and cannot call _canvas.setPixmap(..) all the same when MyWrapper is a QAbstractScrollArea. This seems to not even depend on asynchronicity.

wysota
6th July 2009, 18:16
The only difference between MyWrapper inheriting from QScrollArea or QAbstractScrollArea - apart from the base class of course - is the way _canvas is made known to it.
The important part is that with QScrollArea _canvas is a full blown regular widget that acts on its own whereas when deriving from the abstract scroll area you as the author are responsible for drawing everything on the viewport.


So it seems feasible that "ownership" means something different depending on whether I call QScrollArea::setWidget(&_canvas) or QAbstractScrollArea::setViewport(&_canvas). But I can't figure out in which way different.
setWidget and setViewport do two completely different things. A viewport is strictly a canvas you can (and should) draw on. Its previous content is irrelevant, the view will take control over it, will erase it or draw on it when it sees fit. setWidget on the other hand inserts an external widget into the scroll area and the view is forbidden from drawing on it. It can only manipulate its geometry if you allow it to. The widget itself is responsible for rendering itself and managing its geometry.


What I don't understand is why I need to use a QPainter and cannot call _canvas.setPixmap(..) all the same when MyWrapper is a QAbstractScrollArea. This seems to not even depend on asynchronicity.

Because the view will draw over whatever the "viewport" renders itself and you will see nothing. At least that's theory. What happens in your particular case might be differnt - a human factor enters the picture here.

One more thing - the widget can be larger than the viewport which is always the size of the abstract scroll area widget (of course without all the decorations, margins and scrollbars).

zaphod.b
7th July 2009, 09:29
I'm getting there at last. Excellent explanation, wysota, thanks a lot!

What irritated me was that QAbstractScrollArea isn't abstract at all, so I wrongly assumed it would be more alike QScrollArea than it actually is. Also, viewport()/setViewport() get/set a QWidget, yet the required properties merely seem to be to provide an area and eventually be painted on. (After all, with QPainter viewport()/setViewport() work on QRect, which seems logical as QPainter already knows about the paint device and nothing else is needed. And the QAbstractScrollArea constructor/destructor documentation hints that the class is considered to be just a viewport with some decoration API.) Is there a deeper reason why the Trolls still went with a QWidget but ignore most of its functionality?

Actually, when subclassing QAbstractScrollArea, I don't need to setVieport() at all and _canvas is dispensible. I wonder in which scenario one would need to setViewport()?

Anyway, I can get on from here and sleep well.;)
Thx again!

wysota
7th July 2009, 10:01
What irritated me was that QAbstractScrollArea isn't abstract at all
It is. It can be perceived as "something you can scroll".


Is there a deeper reason why the Trolls still went with a QWidget but ignore most of its functionality?
I don't understand what you mean.


I wonder in which scenario one would need to setViewport()?
For example if you would like to set a QGLWidget as the viewport, as we often do for QGraphicsView.

zaphod.b
7th July 2009, 11:04
What irritated me was that QAbstractScrollArea isn't abstract at all

It is. It can be perceived as "something you can scroll".

Well, yes - but not abstract as in pure virtual. Please don't ask for logic; it's only how I initially perceived QAbstractScrollArea after switching from QScrollArea.:D





Is there a deeper reason why the Trolls still went with a QWidget but ignore most of its functionality?

I don't understand what you mean.

A QPaintDevice has a width and height too. So if being paintable and having a size is all that's needed for a viewport, my first guess would be to pass a QPaintDevice instead of a full-fledged QWidget. But this may well be a misconception owed to my little knowledge, as your QGLWidget example indicates.

wysota
7th July 2009, 12:19
QPaintDevice is pure virtual :)

zaphod.b
7th July 2009, 13:14
QPaintDevice is pure virtual :)

That's ok. Just declare QAbstractScrollArea::setViewport(QPaintDevice *); and leave it to the caller if he passes a QWidget or another QPaintDevice instance. If there isn't a good reason for a QWidget formal parameter, that is...

wysota
7th July 2009, 14:03
Well... the viewport is a widget. The abstract scroll area is composed of the "main" widget, two scrollbars and a viewport. At some point you need to draw on the viewport and if you were drawing on anything else than a widget (like a printer) it might be hard to copy the result to screen later on.

zaphod.b
7th July 2009, 14:56
if you were drawing on anything else than a widget (like a printer) it might be hard to copy the result to screen later on.

Good point.

I think I'd rather not pester you further on this unless I achieve better permeation of the topic...;)
For I might want to resort to your help again in the future.:D

Thank you!

wysota
7th July 2009, 15:26
Don't worry, there is no risk for you :)