PDA

View Full Version : Creating Custom Ellipse Button



VireX
27th April 2007, 06:29
I tried this code then did QEllipseButton b; b.setText("woot"); b.show(); in a simple window of Qt, and it will not show the area. I tried r3, and r1, update,setting a background... but it wont make me a button with a different size.

Like I would like to make an egg shaped button that works just like a pushbutton, but has a nice background image on it.



class QEllipseButton : public QPushButton {
public:
QEllipseButton(QWidget * parent = 0): QPushButton(parent){
}
protected:
void paintEvent ( QPaintEvent * event ){
QRegion r1(QRect(200, 50, 500, 400), // r1: elliptic region
QRegion::Ellipse);
QRegion r2(QRect(200, 70, 90, 30)); // r2: rectangular region
QRegion r3 = r1.intersected(r2); // r3: intersection

QPainter painter(this);
painter.setClipping(true);
painter.setClipRegion(r1);
//QBrush brush(QColor(255,0,0,255));
//painter.setBackground(brush);
//update();
}
};


thx for help.

marcel
27th April 2007, 07:02
Why don't you try QWidget::setMask( const QRegion& ).
Using a painter to achieve this will probably not work, because the background of the widget is not (yet) transparent. You could first fill the widget rect with a transparent pixmap but it is better and easier to use QWidget::setMask.

Regards

marcel
27th April 2007, 07:09
Oh, and even if you subclass QPushButton, overriding paintEvent will not keep the push button behavior. If you like, you could call QPushButton::paintEvent in the paint event of your class( at the beginning ). But this could mess up things even more...

In my opinnion, the best solution is to subclass QAbstractButton and custom draw your button in all the states ( pressed, normal, hovered, disabled, etc...). This allows more freedom in defining the look of your widget.

Regards

VireX
27th April 2007, 08:04
Could you show any code examples of this?

marcel
27th April 2007, 08:17
Unfortunately I do not have any examples, nor the time to create one :). But it should be pretty straightforwards.

You define an enum in your QAbstractButton subclass.


typedef enum States
{
eStateNormal,
eStatePressed,
eStateHovered,
eStateDisabled
} States;

Then , add a State member in your class.
The state switching depends on the user actions.
Initially you start with eStateNormal.
In mousePressEvent, if the button is Qt::LeftButton, the state is eStatePressed.
In mouseMoveEvent, if no btns are pressed, the state is eStateHovered.
In mouseReleaseEvent, if the previous state was eStatePressed, then switch to eStateNormal.

After this, everything happens in paintEvent:


void MyBtn::paintEvent( QPaintEvent* )
{
switch( mButtonState )
{
case eStateNormal:
//Paint in this state
break;
case eStatePressed:
//Paint in this state
break;
case eStateHovered:
//Paint in this state
break;
case eStateDisabled:
//Paint in this state
break;
}
}


Actually, you should make pixmaps for all the states ( either external or you build them here ). It will make drawing faster.

Regards

VireX
29th April 2007, 08:09
Yeah I don't think this is working:


class QEllipseButton : public QAbstractButton {
public:
typedef enum States
{
eStateNormal,
eStatePressed,
eStateHovered,
eStateDisabled
} States;
States m_ButtonState;
QEllipseButton(QWidget * parent = 0): QAbstractButton(parent){
m_ButtonState = eStateNormal;
}
protected:
void mousePressEvent(QMouseEvent *event){
if (event->button() == Qt::LeftButton) {
m_ButtonState = eStatePressed;
}
}
void mouseMoveEvent(QMouseEvent *event){
if(event->buttons() & Qt::LeftButton) {
m_ButtonState = eStateHovered;
}
}
void mouseReleaseEvent(QMouseEvent *event){
if(event->buttons() && m_ButtonState == eStatePressed){
m_ButtonState = eStateNormal;
}
}
void paintEvent ( QPaintEvent * event ){
QRegion r1(QRect(200, 50, 500, 400), // r1: elliptic region
QRegion::Ellipse);
QRegion r2(QRect(200, 70, 90, 30)); // r2: rectangular region
QRegion r3 = r1.intersected(r2); // r3: intersection

QPainter painter(this);
QPixmap qpx;
switch( m_ButtonState ){
case eStateNormal:
qpx.load(QString("JoinNormal.jpg"));
break;
case eStatePressed:
qpx.load(QString("JoinPressed.jpg"));
break;
case eStateHovered:
qpx.load(QString("JoinHover.jpg"));
break;
case eStateDisabled:
qpx.load(QString("JoinNormal.jpg"));
break;
}
QRectF target(10.0, 20.0, 80.0, 60.0);
QRectF source(0.0, 0.0, 70.0, 40.0);
painter.drawPixmap(target, qpx, source);
painter.setClipping(true);
painter.setClipRegion(r1);
//QBrush brush(QColor(255,0,0,255));
//painter.setBackground(brush);
//update();
}
};



class QEllipseButton : public QAbstractButton {
public:
typedef enum States
{
eStateNormal,
eStatePressed,
eStateHovered,
eStateDisabled
} States;
States m_ButtonState;
QEllipseButton(QWidget * parent = 0): QAbstractButton(parent){
m_ButtonState = eStateNormal;
}
protected:
void mousePressEvent(QMouseEvent *event){
if (event->button() == Qt::LeftButton) {
m_ButtonState = eStatePressed;
}
}
void mouseMoveEvent(QMouseEvent *event){
if(event->buttons() & Qt::LeftButton) {
m_ButtonState = eStateHovered;
}
}
void mouseReleaseEvent(QMouseEvent *event){
if(event->buttons() && m_ButtonState == eStatePressed){
m_ButtonState = eStateNormal;
}
}
void paintEvent ( QPaintEvent * event ){
QRegion r1(QRect(200, 50, 500, 400), // r1: elliptic region
QRegion::Ellipse);
QRegion r2(QRect(200, 70, 90, 30)); // r2: rectangular region
QRegion r3 = r1.intersected(r2); // r3: intersection

QPainter painter(this);
QPixmap qpx;
switch( m_ButtonState ){
case eStateNormal:
qpx.load(QString("JoinNormal.jpg"));
break;
case eStatePressed:
qpx.load(QString("JoinPressed.jpg"));
break;
case eStateHovered:
qpx.load(QString("JoinHover.jpg"));
break;
case eStateDisabled:
qpx.load(QString("JoinNormal.jpg"));
break;
}
QRectF target(10.0, 20.0, 80.0, 60.0);
QRectF source(0.0, 0.0, 70.0, 40.0);
painter.drawPixmap(target, qpx, source);
painter.setClipping(true);
painter.setClipRegion(r1);
//QBrush brush(QColor(255,0,0,255));
//painter.setBackground(brush);
//update();
}
};


I posted twice, so you can copy one, since someone not so smart decided to use number lines with Geshi knowing that no one can COPY the code because of damn number lines.

caduel
29th April 2007, 10:21
You have to trigger repainting after updating your state (call update()).
Just setting your internal state variable will not cause your button to repaint itself in this state.

HTH
Christoph

marcel
29th April 2007, 10:26
:) Yes.
I forgot that, but you must call an update() to schedule a paintEvent each time you switch the states.

Other than this, the code looks good.

Regards

VireX
30th April 2007, 07:50
No the code still does not work:

#include <QtGui>
#include "flowlayout.h"
class QEllipseButton : public QAbstractButton {
public:
typedef enum States
{
eStateNormal,
eStatePressed,
eStateHovered,
eStateDisabled
} States;
States m_ButtonState;
QEllipseButton(QWidget * parent = 0): QAbstractButton(parent){
m_ButtonState = eStateNormal;
}
protected:
void mousePressEvent(QMouseEvent *event){
if (event->button() == Qt::LeftButton) {
m_ButtonState = eStatePressed;
update();
}
}
void mouseMoveEvent(QMouseEvent *event){
if(event->buttons() & Qt::LeftButton) {
m_ButtonState = eStateHovered;
update();
}
}
void mouseReleaseEvent(QMouseEvent *event){
if(event->buttons() && m_ButtonState == eStatePressed){
m_ButtonState = eStateNormal;
update();
}
}
void paintEvent ( QPaintEvent * event ){
QRegion r1(QRect(200, 50, 500, 400), // r1: elliptic region
QRegion::Ellipse);
QRegion r2(QRect(200, 70, 90, 30)); // r2: rectangular region
QRegion r3 = r1.intersected(r2); // r3: intersection

QPainter painter(this);
QPixmap qpx;
switch( m_ButtonState ){
case eStateNormal:
qpx.load(QString("Normal.jpg"));
break;
case eStatePressed:
qpx.load(QString("Pressed.jpg"));
break;
case eStateHovered:
qpx.load(QString("Hover.jpg"));
break;
case eStateDisabled:
qpx.load(QString("Normal.jpg"));
break;
}
QRectF target(100.0, 100.0, 100.0, 100.0);
QRectF source(100.0, 100.0, 100.0, 100.0);
painter.drawPixmap(target, qpx, source);
painter.setClipping(true);
painter.setClipRegion(r1);
//QBrush brush(QColor(255,0,0,255));
//painter.setBackground(brush);
//update();
}
};

----------------- hOW i run it: -------
QEllipseButton b;
b.setText("hoot");
b.show();

marcel
30th April 2007, 08:11
QRectF target(100.0, 100.0, 100.0, 100.0);
QRectF source(100.0, 100.0, 100.0, 100.0);

Is the problem, by any chance, that you don't see any pixmap?
Try drawing everything relative to (0,0), not (100, 100).
Also, overwrite QAbstractButton::sizeHint() and return your size hint( which probably is the size of one of the pixmaps ).

Regards

VireX
30th April 2007, 17:15
I used 0, and I finally saw the picture. However, the clip stuff never works. Also putting everything inside a QHBoxLayout and settingLayout with a widget, makes it dissappear.

Also Hover absolutely does not work.

marcel
1st May 2007, 17:21
Regarding hover:
Hover means you just move your mouse over the widget. You switch to hover if the left button is pressed, in mouseMoveEvent, which is not correct.
To detect hover, there are less processor stressing methods, like QWidget::enterEvent and QWidget::leaveEvent.

Regarding adding your custom btn to layouts:
When I said drawing everything relative to (0,0) I meant to the origin of what rect() returns: rect().topLeft().
Adding to layouts did not worked because everything was drawn in the topleft corner of the layout :).

Hope it works now.

Regards

VireX
1st May 2007, 23:29
Well how do I add automaticlaly to wherever it needs to draw it...? Is there a way?
Any code examples?

spud
3rd May 2007, 12:56
Wouldn't it be easier to do this with style sheets?
i.e.


int main(int argc, char **argv)
{
QApplication app(argc, argv);
QPushButton btn("quit");
btn.setStyleSheet
(
"QPushButton {"
" border-image: url(normal.png) 17% 17% 17% 17% stretch stretch; "
" border-width: 8px;"
"}"
"QPushButton:hover {"
" border-image: url(hover.png) 17% 17% 17% 17% stretch stretch; "
" border-width: 8px;"
"}"
"QPushButton:pressed {"
" border-image: url(pressed.png) 17% 17% 17% 17% stretch stretch; "
" border-width: 8px;"
"}"
);
QObject::connect(&btn, SIGNAL(clicked()), &app, SLOT(quit()));
btn.show();
app.exec();
}

You obviously have to adjust the percentage and border values to fit your bitmap and you won't be able to get a true ellipse since some part of the image must be stretched or repeated depending on the text length, but it seems a lot easier.

VireX
3rd May 2007, 18:47
I realize that, I won't be able to get it perfectly. I won't be able to cut the square black background into a transparent one or turn the Square feature of pushbutton into an ELLIPSE feature.

the Stretch stuff by the way didn't change anything except when the image size is different than the button size.

marcel
3rd May 2007, 18:56
Hey man, it works just fine if you do it as I've shown you.

It will work anyway if you use "ready-made" png images for the states. I mean you draw an ellipse in a png and leave everything else transparent. Then just fill the widget with these pixmaps.

If you use transp pixmaps the you don't need setting any clipping masks.

Regards

high_flyer
4th May 2007, 13:23
If you use transp pixmaps the you don't need setting any clipping masks.
Are you sure about that?
Is this from experience?
Some time back I implemented a styled button, which can take any shape, and as I remember, I had to use masks in addition to the png (which had transparent areas).

marcel
4th May 2007, 13:38
Yes, I have already tried it some time ago.
Also, I might have filled the widget with a transparent pixmap first, then applied the actual png... Can't remember exactly, but it can be done.

Regards

huyhoangfool
30th May 2012, 11:23
ui->pushButton->setFixedSize(150,100);

ui->pushButton->setStyleSheet("QPushButton {"
"background-color: red;"
"border-style: solid;"
"border-width: 3px;"
"border-radius: 75px 50px;"
"border-color: blue;"
"font: bold 14px;"
"padding: 6px;}"
"QPushButton:pressed {"
"background-color: rgb(224, 0, 0);}");

That's what i have implemented ... (For someone looking for a way to go)

Please note : setFixedSize(150,100); and "border-radius: 75px 50px;"

Regards.