PDA

View Full Version : make a widget paint itself even thought it is not shown



momesana
27th April 2008, 20:04
Resize and paintevents are queued but not executed when a widget is hidden. They are executed just before the widget is shown. The problem that arises from this is that QPixmap::grabWidget() will return a pixmap that corresponds to how the widget looked before it was hidden and provides some extorted images if the widget has not yet been shown. I am just curious, if it is possible to force a widget to paint itself even though it is not yet shown so grabWidget will return the expected results.

Thanx in advance

p.s. I've tried QWidget::render() and repaint() with the same results.

wysota
27th April 2008, 21:05
What does "widget looked before it was hidden" means? You mean you change the widget's contents when it's hidden and they are not reflected when you call QPixmap::grabWidget() afterwards? This is hardly possible... Are you sure your code is correct? grabWidget() and QWidget::render() effectively make the widget execute its paintEvent() regardless of its current state.

momesana
28th April 2008, 00:34
What does "widget looked before it was hidden" means?
You mean you change the widget's contents when it's hidden and they are not reflected when you call QPixmap::grabWidget() afterwards?

Yes. Exactly. I resize the widget while the widget is still hidden (it is not the active widget on a QStackedLayout) and then grab the widget. the returned pixmap reflects the size it had before I changed the index.



This is hardly possible... Are you sure your code is correct? grabWidget() and QWidget::render() effectively make the widget execute its paintEvent() regardless of its current state.
A trolltech employee on the #qt irc channel on freenode checked it and told me this widget must be shown before grabWidget can take the widget because it will only receive the resize and other events when it is shown, so I assume the code is correct.



#include <QtGui>

class Transition : public QWidget
{
Q_OBJECT
public:
Transition(QWidget* parent = 0) : QWidget(parent){};
virtual void setDuration(int duration) { m_duration = duration; }
virtual int duration() const { return m_duration; }
virtual void setWidget(QWidget* widget) { m_widget = widget; }
virtual QWidget* widget() const { return m_widget; }
virtual void startTransition(QWidget* before, QWidget* after) = 0;

private:
QWidget* m_widget;
int m_duration;
};

class TestTransition : public Transition
{
Q_OBJECT
public:
TestTransition(QWidget* parent = 0) : Transition(parent) {
m_timeLine = new QTimeLine(333, this);
connect(m_timeLine, SIGNAL(valueChanged(qreal)),
this, SLOT(onValueChanged(qreal)));
connect(m_timeLine, SIGNAL(finished()),
this, SLOT(onFinished()));
hide();
}

void setDuration(int duration) {
Transition::setDuration(duration);
m_timeLine->setDuration(this->duration());
}
void startTransition(QWidget* before, QWidget* after) {
resize(widget()->size());
// Grab Images
m_beforeImage = QPixmap::grabWidget(before).toImage();
QPixmap mp = QPixmap::grabWidget(after);

// Set foreground brush
m_fgBrush = QBrush(mp);
m_afterImage = mp.toImage();

m_finalImage = m_beforeImage;
int side = qMax(width(), height());
m_radius = (rect().center() + QPoint(side/2, side/2)).manhattanLength();
m_timeLine->start();
show();

}
void paintEvent(QPaintEvent* event) {
QPainter p(this);
p.drawImage(0, 0, m_finalImage);
}

private:
QTimeLine* m_timeLine;
QImage m_beforeImage;
QImage m_afterImage;
QImage m_finalImage;
QBrush m_fgBrush;
int m_radius;

private slots:
void onValueChanged(qreal value) {
QPainter p(&m_finalImage);
p.setRenderHints(QPainter::Antialiasing);
p.setPen(Qt::gray);
p.setBrush(m_fgBrush);
const int r = (int)(m_radius * value);
p.drawEllipse(rect().center(), r, r);
update();
}

void onFinished() {
hide();
}
};

class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget* parent = 0) : QMainWindow(parent) {
QCalendarWidget* w = new QCalendarWidget;
QTextEdit* t = new QTextEdit;

// Layout
m_layout = new QStackedLayout;
m_layout->addWidget(w);
m_layout->addWidget(t);

// Central Widget
QWidget* cw = new QWidget;
cw->setLayout(m_layout);
setCentralWidget(cw);

// Transition
m_transition = new TestTransition(this);
m_transition->setWidget(cw);
m_transition->setDuration(400);

QAction* act = new QAction(this);
act->setShortcut(tr("Ctrl+A"));
connect(act, SIGNAL(triggered()), this, SLOT(changePage()));
addAction(act);
}
private:
QStackedLayout* m_layout;
TestTransition* m_transition;

private slots:
void changePage() {
int index = m_layout->currentIndex();
index == 1 ? --index : ++index;

// Transition
m_transition->startTransition(m_layout->widget(m_layout->currentIndex()),
m_layout->widget(index));
m_layout->setCurrentIndex(index);
}
};

int main(int argc, char** argv)
{
QApplication app(argc, argv);
MainWindow mw;
mw.show();
return app.exec();
}
#include "main.moc"

executing the animation by pressing ctrl + A, resizing the widget and starting the animation again will show the problem. It is solved if setCurrentIndex() is called before the animation starts. I don't have access to the fixed version right now (I'll post it tomorrow) but that doesn't change the problem that QPixmap::grabWidget() seems to return a pixmap that reflects the state of the widget before it was hidden and doesn't seem to repaint itself in reponse to render() or grabWidget().

Any Ideas :( ?

Thanx in advance

wysota
28th April 2008, 07:55
Your problem is that widget is only resized for the first time when it is first shown. So it's not a matter of painting a hidden widget, but painting a widget that has not been visible before. There are ways to fake visibility of a widget - you need to set some flags for it, grab its contents and clear the flags again. Also calling ensurePolished() and family could be advisable.

Aren't you trying to do something like this?
http://doc.trolltech.com/qq/qq16-fader.html
http://labs.trolltech.com/blogs/2007/08/21/fade-effects-a-blast-from-the-past/

momesana
28th April 2008, 10:47
Your problem is that widget is only resized for the first time when it is first shown.

Well the problem shows up everytime the widget is resized while hidden.



There are ways to fake visibility of a widget - you need to set some flags for it, grab its contents and clear the flags again. Also calling ensurePolished() and family could be advisable.

Sounds promising. I'll try that.



Aren't you trying to do something like this?
http://doc.trolltech.com/qq/qq16-fader.html
http://labs.trolltech.com/blogs/2007/08/21/fade-effects-a-blast-from-the-past/
[/code]
Exactly :). Thanks for the interesting links.

Thanks a lot

wysota
28th April 2008, 11:08
Well the problem shows up everytime the widget is resized while hidden.
The question is why would you want to resize a widget when it's hidden... it'd be a pure waste of cpu cycles.

momesana
28th April 2008, 11:31
The question is why would you want to resize a widget when it's hidden... it'd be a pure waste of cpu cycles.
Well, I don't. All I want is to have grabWidget() take this into consideration before taking the screenshot so it doesn't return outdated pixmaps or pixmaps whose size does not correspond to the actual current size of the widget :-). So I don't question Qt's policy of ignoring (rather postponing ...) such events when the widget is hidden, but the way grabWidget works. It should do whatever is necessary to return a pixmap that resembles the way the widget would look like if it was shown.

wysota
28th April 2008, 12:57
grabWidget() grabs the widget at its current state. Calling ensurePolished() might help, although it's a long shot. In general I'd try avoiding grabbing an invisible widget, if I were you. Remember that a widget only has a size when it is visible. In all other situations its size should be treated as undetermined. If you want a dirty hack, use this:

widget->show();
qApp->processEvents();
QPixmap px = QPixmap::grabWidget(widget);
widget->hide();
qApp->processEvents();