PDA

View Full Version : Subclassing a QWidget



aguleo
18th February 2013, 11:13
Hi
I'm trying to understand how to subclass a QWidget.
I want to create a widget with 2 buttons allong with some drawing elements.
How does it work in terms of layout for the subclassed widget.
I ask because aparently the first layout that i use becomes the main layout!
I attach small small example: 8728

alrawab
18th February 2013, 11:35
#ifndef DESENHO_H
#define DESENHO_H

#include <QWidget>
#include <QVBoxLayout>
#include <QSpacerItem>
#include <QPushButton>


class Widget : public QWidget
{
Q_OBJECT

public:
Widget(QWidget *parent = 0);
~Widget();
void draw(QPainter *painter);

protected:
void paintEvent(QPaintEvent *event);

private:
QPushButton *btn1, *btn2;
QSpacerItem *spacer;
QVBoxLayout *verticalLayout;
QHBoxLayout *horizontalLayout;
};

#endif



#include <QtGui>

#include "widget.h"

Widget::Widget(QWidget *parent)
: QWidget(parent)
{
btn1 = new QPushButton(this);
btn2 = new QPushButton(this);
spacer = new QSpacerItem(0,100);
verticalLayout = new QVBoxLayout(this);
horizontalLayout=new QHBoxLayout;
verticalLayout->addStretch(1);
// horizontalLayout->addItem(spacer);
horizontalLayout->addWidget(btn1);
horizontalLayout->addWidget(btn2);
verticalLayout->addLayout(horizontalLayout);
this->setLayout(verticalLayout);
//verticalLayout->addItem(spacer);
//verticalLayout->addWidget(btn1);
//verticalLayout->addWidget(btn2);
}

Widget::~Widget()
{

}

void Widget::paintEvent(QPaintEvent * /* event */)
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing, true);


draw(&painter);
}

void Widget::draw(QPainter *painter)
{


}

Santosh Reddy
18th February 2013, 11:36
You need to re-implement paintEvent of "some drawing elements" and let the "main layout" take care the laying out pushbuttons and "drawing elements".

Zlatomir
18th February 2013, 11:43
You set create a "main" layout and within that you can add another layout if you want to, tell us more about what are you trying to achieve and also take a look at documentation (http://doc.qt.digia.com/4.7-snapshot/layout.html)

Also, you can use forward declaration in headers for all classes that have only pointers or references or are passed as parameter to member function (this makes the compilation faster for bigger projects):


#ifndef DESENHO_H
#define DESENHO_H

#include <QWidget> //you need this header included because you derive from QWidget
/* you don't need these headers included in your header
-- include those in the .cpp file
#include <QVBoxLayout>
#include <QSpacerItem>
#include <QPushButton>
*/
//here you only use forward declaration:
class QVBoxLayout;
class QPushButton;
//and so on
class Widget : public QWidget
{
Q_OBJECT

public:
Widget(QWidget *parent = 0);
~Widget();
void draw(QPainter *painter);

protected:
void paintEvent(QPaintEvent *event);

private:
QPushButton *btn1, *btn2;
QSpacerItem *spacer;
QVBoxLayout *verticalLayout;
};

#endif

aguleo
18th February 2013, 15:02
There was something missing above also.
I was passing this into horizontalLayout and getting a "QLayout: Attempting to add QLayout "" to Widget "", which already has a layout
".

Here is the new version with the contributions above.
widget.h


#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>

class QVBoxLayout;
class QHBoxLayout;
class QPushButton;

class Widget : public QWidget
{
Q_OBJECT

public:
Widget(QWidget *parent = 0);
~Widget();
void draw(QPainter *painter);

protected:
void paintEvent(QPaintEvent *event);

private:
QPushButton *btn1, *btn2;
QVBoxLayout *verticalLayout;
QHBoxLayout *horizontalLayout;

};

#endif



widget.cpp


#include <QtGui>

#include "widget.h"

Widget::Widget(QWidget *parent)
: QWidget(parent)
{
btn1 = new QPushButton(this);
btn2 = new QPushButton(this);
verticalLayout = new QVBoxLayout(this);
horizontalLayout = new QHBoxLayout();
verticalLayout->addStretch(1);
horizontalLayout->addWidget(btn1);
horizontalLayout->addWidget(btn2);
verticalLayout->addLayout(horizontalLayout);
this->setLayout(verticalLayout);
}

Widget::~Widget()
{

}

void Widget::paintEvent(QPaintEvent * /* event */)
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing, true);
//...

draw(&painter);
}

void Widget::draw(QPainter *painter)
{


}



Added after 11 minutes:

By the way, showld i be using this line?

#include <QtGui>

Zlatomir
18th February 2013, 17:47
Those two lines are basically doing the same thing:


verticalLayout = new QVBoxLayout(this);

//this second line isn't necessary the layout is already set (you passed this pointer to the QVBoxLayout's constructor)
//this->setLayout(verticalLayout);


As for: #include <QtGui>, i recommend that you include as few headers as possible (and even use the forward declaration wherever possible) so this is not a good solution and on top of slow compilation some people complained that code completion in Creator is slow when including all headers for a module.

Also your issue is only with the layout system or as Santosh Reddy guessed you have issues with drawing (paintEvent) too?

aguleo
18th February 2013, 21:24
To be honest ...

You need to re-implement paintEvent of "some drawing elements" and let the "main layout" take care the laying out pushbuttons and "drawing elements".
... i'm not shore what he was talkin about!
Drawing is ok ... the problem arrised when i added extra widgets. Then i had problems placing them and the layout question emerged.

Santosh Reddy
18th February 2013, 21:52
I want to create a widget with 2 buttons allong with some drawing elements.
Show where and how are you drawing "some drawing elements"

aguleo
18th February 2013, 22:08
Something like this ... a blue box arround the widget.

#include <QtGui>

#include "widget.h"

Widget::Widget(QWidget *parent)
: QWidget(parent)
{
btn1 = new QPushButton(this);
btn2 = new QPushButton(this);
verticalLayout = new QVBoxLayout(this);
horizontalLayout = new QHBoxLayout();
verticalLayout->addStretch(1);
horizontalLayout->addWidget(btn1);
horizontalLayout->addWidget(btn2);
verticalLayout->addLayout(horizontalLayout);
this->setFixedSize(200, 100);
this->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
}

Widget::~Widget()
{

}

void Widget::paintEvent(QPaintEvent * /* event */)
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing, true);
painter.setPen(Qt::blue);
draw(&painter);
}

void Widget::draw(QPainter *painter)
{
QPointF points[4] = {
QPointF(0.0, 0.0),
QPointF(200.0, 0.0),
QPointF(200.0, 100.0),
QPointF(0.0, 100.0)
};
painter->drawConvexPolygon(points, 4);

}

Santosh Reddy
18th February 2013, 22:40
Don't implement the paint for the widget for which the layout managers are set. You need to paint the widget with no layout on it.


#include <QWidget>
#include <QPainter>
#include <QPaintEvent>
#include <QPushButton>
#include <QGridLayout>
#include <QApplication>

class DrawingWidget : public QWidget
{
public:
DrawingWidget(QWidget *parent = 0) : QWidget(parent) {}

protected:
void paintEvent(QPaintEvent *event) // <<<<<<<<<<<<< Do drawing here on this widget (which does not has layout)
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing, true);
painter.setPen(Qt::blue);

QSize size = event->rect().size(); // <<<<<<<<<< always do painting in side the event->rect()

QPointF points[4] = {
QPointF(0.0, 0.0),
QPointF(size.width(), 0.0),
QPointF(size.width(), size.height()),
QPointF(0.0, size.height())
};
painter.drawConvexPolygon(points, 4);
}
};

class Widget : public QWidget
{
Q_OBJECT

public:
Widget(QWidget *parent = 0)
: QWidget(parent)
, layout(new QGridLayout(this)) // This will be your main layout, you could change this to a combination on VBox/Hbox, I used grid for its flexibility to act as both
{
layout->addWidget(new DrawingWidget, 0, 0, 1, 2); // <<<<<<<<<<<<< Drawing Widget
layout->addWidget(new QPushButton("< Prev"), 1, 0, 1, 1);
layout->addWidget(new QPushButton("Next >"), 1, 1, 1, 1);
}

private:
QGridLayout *layout;
};

int main(int argc, char *argv[])
{
QApplication app(argc, argv);
Widget w;

w.show();
return app.exec();
}

#include "main.moc"

aguleo
19th February 2013, 10:08
You separated drawing form the rest of the widgets and the way thay are set ia a layout. Correct?

But why do it like that?
Apart from a good coding example, setting hierarchical relations between classes, i can not clearly see the advantages.
Please point them to help me understand the experts :)

Santosh Reddy
19th February 2013, 10:13
Please refer to my earlier statement

Don't implement the paint for the widget for which the layout managers are set. You need to paint the widget with no layout on it.
If you implement the painting for a widget and also use layout managers on it, then layout managers will over write the painting you do:eek:

anda_skoa
19th February 2013, 17:52
If you want to paint "below" the widgets, then call the super class' paintEvent after your call to draw().

Cheers,
_

aguleo
19th February 2013, 19:15
New version:

keep the layouts;
don't separate drawing from other widgets;
painting "below" the widgets


widget.h


#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>

class QVBoxLayout;
class QHBoxLayout;
class QPushButton;
class QSize;

class Widget : public QWidget
{
Q_OBJECT

public:
Widget(QWidget *parent = 0);
~Widget();
void draw(QPainter *painter, QPaintEvent *event);

protected:
void paintEvent(QPaintEvent *event);

private:
QPushButton *btn1, *btn2;
QVBoxLayout *verticalLayout;
QHBoxLayout *horizontalLayout;

};

#endif



widget.cpp


#include <QtGui>

#include "widget.h"

Widget::Widget(QWidget *parent)
: QWidget(parent)
{
btn1 = new QPushButton(this);
btn2 = new QPushButton(this);
verticalLayout = new QVBoxLayout(this);
horizontalLayout = new QHBoxLayout();
verticalLayout->addStretch(1);
horizontalLayout->addWidget(btn1);
horizontalLayout->addWidget(btn2);
verticalLayout->addLayout(horizontalLayout);
}

Widget::~Widget()
{

}

void Widget::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing, true);
painter.setPen(Qt::blue);
draw(&painter, event);
}

void Widget::draw(QPainter *painter, QPaintEvent *event)
{

QSize size = event->rect().size();
QPointF points[4] = {
QPointF(0.0, 0.0),
QPointF(size.width(), 0.0),
QPointF(size.width(), size.height()),
QPointF(0.0, size.height())
};
painter->drawConvexPolygon(points, 4);
painter->drawLine(0.0, 0.0, size.width(), size.height());
painter->drawLine(size.width(), 0.0, 0.0, size.height());
}



Hope i get an ok now :confused:

Santosh Reddy
19th February 2013, 20:47
Hope i get an ok now
This might seem ok for you, for now, but I am sure this not the final thing you wanted. You will for sure later add more drawing in the paint function, then how would you avoid drawing on the QPushButtons. There are ways to avoid it, but the question is do you what to do so?

anda_skoa
20th February 2013, 18:52
New version:

keep the layouts;
don't separate drawing from other widgets;
painting "below" the widgets



Are you sure about the last point? I don't see a call to QWidget::paintEvent() inside Widget::paintEvent()

Cheers,
_

aguleo
20th February 2013, 21:23
I think this is working like planed.
Perhaps we are having a communication problem with the word "below" :)
Please take a look at the attachment: 8744

Santosh Reddy
21st February 2013, 17:15
So it means that you wanted the QPushButtons to be over the custom painting (done in the parent widget's paint event).

So all set, you have what you wanted :)

aguleo
23rd February 2013, 16:19
I was exploring Santosh Reddy's example and a question came to my mind!
How will the Widget and the DrawingWidget communicate?

Imagine that buttonx controls the position of an object in the DrawingWidget. Showld i go for the setter/getter approach or start emitting custom signals?