PDA

View Full Version : How to create a custom Button?



Mister_Crac
19th October 2006, 17:14
Hello,

I am trying to create a custom Button of my own in Qt4. What I want it to do is to display an open switch and a closed switch respectively, depending on whether it has been pressed by the user. Like a QCheckBox, but with different graphics. Two possible solutions come to mind:

1.) Create a subclass of QAbstractButton.

I have tried to do this and have achieved some success, however I still have problems. For instance, I can display my own Button within another QWidget, but I cannot add it to any kind of layout (QGridLayout and the like). It is not visible then. I thought this could be a problem with the class I've written not being a subclass of QWidget but of QAbstractButton. But since QAbstractButton is a subclass of QWidget itself, I don't know what I am doing wrong here.
Here is the source code of my own Button class:

File: MyButton.h


#ifndef _MYBUTTON
#define _MYBUTTON
#include <QAbstractButton>

class MyButton : public QAbstractButton
{
Q_OBJECT

public:
MyButton(QWidget *parent = 0);
void paintEvent(QPaintEvent*);

signals:
void valueChanged(int newValue);
};

#endif


File: MyButton.cpp


#include "MyButton.h"
#include <QtGui>

MyButton::MyButton(QWidget *parent)
: QAbstractButton(parent)
{
setCheckable(true);
setChecked(false);
}

void MyButton::paintEvent(QPaintEvent*)
{
QPainter painter(this);
QPen myPen;
myPen.setWidth(2);

if(isChecked())
{
myPen.setColor(Qt::black);
painter.setPen(myPen);
painter.drawLine(20,29,50,20);
int a = 1;
emit valueChanged(a);
}
else
{
myPen.setColor(Qt::darkGray);
painter.setPen(myPen);
painter.drawLine(20,29,30,0);
int b = 0;
emit valueChanged(b);
}
painter.drawLine(0,30,20,30);
painter.drawLine(50,30,70,30);
painter.drawLine(50,30,50,20);
}


2.) Another possibility would be to use the QCheckBox class and just change its graphical appearance by using QIcons. I tried that just a few minutes ago but somehow I can't seem to get it to display another pixmap. Here is a small example that does compile and run but obviously is not enough to change the QCheckBox's graphical appearance:



#include <QtGui>

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

QCheckBox *button = new QCheckBox;
button->setIcon(QIcon("CustomIcon.png"));
button->show();

return app.exec();
}


Any help on this matter is highly welcome. :-)

jrideout
19th October 2006, 20:05
Combine 1 & 2, just subclass QCheckBox

wysota
19th October 2006, 20:15
How about subclassing QPushButton and providing your own paintEvent? You have the checkbox functionality already there.

jpn
19th October 2006, 20:22
You would have to provide a proper implementation for QWidget::sizeHint to get it "working" in a layout.. QAbstractButton doesn't provide any implementation for sizeHint() and the default implementation in QWidget returns an invalid size.

wysota
19th October 2006, 22:10
I suggested subclassing a push button, not the abstract one. Of course subclassing the check box is as good as subclassing the push button. My previous post was a direct response to the original question, not your response, jpn :)

Mister_Crac
20th October 2006, 10:12
You would have to provide a proper implementation for QWidget::sizeHint to get it "working" in a layout.. QAbstractButton doesn't provide any implementation for sizeHint() and the default implementation in QWidget returns an invalid size.

Yay, it works - for the layout. Thank you.
However there is still one problem left: When I move the mouse over my Button Widget (without clicking), it emits the signal that it should only emit when the mouse button is pressed. So I'm doing something wrong with Signals & Slots here? I will post my source code once I've packed it into a single file so you can try out for yourself and see what it does.
cul8r

jpn
20th October 2006, 10:54
I suggested subclassing a push button, not the abstract one. Of course subclassing the check box is as good as subclassing the push button. My previous post was a direct response to the original question, not your response, jpn :)
Yup, got it.. Just wanted to explain why his approach didn't work like he expected. :)


When I move the mouse over my Button Widget (without clicking), it emits the signal that it should only emit when the mouse button is pressed. So I'm doing something wrong with Signals & Slots here?
Well, have you overridden QWidget::enterEvent()? Do you emit any signals from there?

Mister_Crac
20th October 2006, 11:45
Well, have you overridden QWidget::enterEvent()?


No, I haven't. Do I need to do this?


Do you emit any signals from there?

The only place so far where I use emit is in paintEvent():


if(isChecked())
{
myPen.setColor(Qt::black);
painter.setPen(myPen);
painter.drawLine(20,29,50,20);
int a = 1;
emit valueChanged(a);
}
else
{
myPen.setColor(Qt::darkGray);
painter.setPen(myPen);
painter.drawLine(20,29,30,0);
int b = 0;
emit valueChanged(b);
}


I just realised that it makes no sense to put all of my code into one single file, I'm getting moc Linker errors like crazy ("undefinde reference to vtable").
If you need more of my source code posted, I will do so if you ask.

jpn
20th October 2006, 11:57
You should only inform about the state/value change when the change actually occurs. There's no sense in emitting the changed value during each and every paint event. The button gets repainted every once in a while when needed (like if it's geometry is changed by the layout, or after it has been temporarily overlapped by some other window and so on).



I just realised that it makes no sense to put all of my code into one single file, I'm getting moc Linker errors like crazy ("undefinde reference to vtable").
If you need more of my source code posted, I will do so if you ask.
Add line:

#include "main.moc"

at the end of your main.cpp.

You will have to include the moc file by hand in case you don't have separate class header files. This is for example when embedding everything as an example to a single main.cpp.

Mister_Crac
20th October 2006, 12:06
You should only inform about the state/value change when the change actually occurs.

That's what I thought I was doing with the


if(isChecked())


statement. But obviously that is wrong.
Now, if I put // comment in front of my second "emit" statement, it works alright 50% of the time. ;)
So what am I supposed to do exactly, I need to react to a certain event (the user clicks the left mouse button while the mouse pointer is over the MyButton switch).
Do I need a MouseEvent() and a PaintEvent(), and the PaintEvent() only gets called from the MouseEvent() when the state of the Button changes?
Where do I put the emit() then?

Mister_Crac
20th October 2006, 12:18
It seems I found a solution. I added another if statement. Like this:



if(isChecked())
{
myPen.setColor(Qt::black);
painter.setPen(myPen);
painter.drawLine(20,29,50,20);

if(isDown())
emit valueChanged(1);
}
else
{
myPen.setColor(Qt::darkGray);
painter.setPen(myPen);
painter.drawLine(20,29,30,0);

if(isDown())
emit valueChanged(0);
}

jpn
20th October 2006, 12:25
The value does not change in the paint event. The value changes when user interacts with the button, eg. when receiving certain mouse and key events or when the value is changed from code. A paint event occurs whenever appropriate, but the value has not necessarily changed still if a paint event is received. QAbstractButton already provides you a bunch of signals of button state changes:

void QAbstractButton::clicked ( bool checked = false )
void QAbstractButton::pressed ()
void QAbstractButton::released ()
void QAbstractButton::toggled ( bool checked )

wouldn't these suffice for you?

wysota
20th October 2006, 17:31
Why don't you just subclass one of existing implementations of QAbstractButton like QCheckBox, QToolButton or QPushButton? You'd only need to reimplement the paint event... I guess even something like this would be enough:


class SwitchButton : public QCheckBox {
public:
SwitchButton(QWidget *p=0) : QCheckBox(p){}
QSize sizeHint() const{
QString c = checkState()==Qt::Checked ? ":/mypixmap_checked.png" : ":/mypixmap_unchecked.png";
return QPixmap(c).size();
}
protected:
void paintEvent(QPaintEvent *){
QPainter p(this);
QString c = checkState()==Qt::Checked ? ":/mypixmap_checked.png" : ":/mypixmap_unchecked.png";
p.drawPixmap( QPixmap(c));
}
}

jacek
20th October 2006, 17:59
QString c = checkState()==Qt::Checked ? ":/mypixmap_checked.png" : ":/mypixmap_unchecked.png";
return QPixmap(c).size();

IMO it would be better to use QPixmapCache instead of loading those pixmaps every time they are needed.

wysota
20th October 2006, 22:42
IMO it would be better to use QPixmapCache instead of loading those pixmaps every time they are needed.
That's only an example, I wanted to make it as simple as can be. It would be enough to have two static QPixmap members in the class that would hold those pixmaps, I don't see a benefit from the cache here.

jacek
20th October 2006, 23:53
That's only an example, I wanted to make it as simple as can be.
But still, it's an improper example. You've just made it too simple.


It would be enough to have two static QPixmap members in the class that would hold those pixmaps, I don't see a benefit from the cache here.
How do you want to initialize static QPixmaps? QApplication instance must be created first.

wysota
21st October 2006, 08:03
But still, it's an improper example. You've just made it too simple.


How do you want to initialize static QPixmaps? QApplication instance must be created first.
Somehow I knew you would ask that question :)


class x{
static QPimxap *p1;
static QPixmap *p2;
public x(){
if(!p1){
p1 = new QPixmap(...);
p2 = new QPixmap(...);
}
}
};

QPixmap *x::p1 = 0;
QPixmap *x::p2 = 0;
Of course this causes a slight memory leak if you don't count instances of your class. An alternative is to have those pixmaps directly in the class as regular members, only that it will have a bigger memory footprint.

BTW. It's not an improper example - you simply don't like it.

jacek
21st October 2006, 19:53
It's not an improper example - you simply don't like it.
It's just unnecessarily inefficient.

jrideout
22nd October 2006, 23:59
I've been playing around with the new stylesheet (http://doc.trolltech.com/4.2/stylesheet.html) thing and it makes everything much simpler. (The below is a stylesheet, not c++)


QCheckBox::indicator:unchecked {
image: url(:/images/checkbox_unchecked.png);
}

QCheckBox::indicator:checked {
image: url(:/images/checkbox_checked.png);
}


This is implemented with qApp->setStyleSheet() globally or with myCheckbox->setStyleSheet() for a particular checkbox. Also, check out the Style Sheet Example (http://doc.trolltech.com/4.2/widgets-stylesheet.html)