PDA

View Full Version : Custom Widget - First Steps



sekatsim
19th June 2008, 01:11
Been trying to create a custom tab widget that will auto-fill itself with a user-definable number of Faders.. each fader being a custom widget composed of a few buttons and a slider. I'm not really sure how to get started at defining a custom widget though. I've read the docs concerning the "vcr" example, but I can't find the original sources for the example. They say:



Vcr::Vcr( QWidget *parent, const char *name )
: QWidget( parent, name )
{
QHBoxLayout *layout = new QHBoxLayout( this );
layout->setMargin( 0 );

QPushButton *rewind = new QPushButton( QPixmap( rewind_xpm ), 0, this, "vcr_rewind" );
layout->addWidget( rewind );


We create a QHBoxLayout in which we'll place the buttons. We've only shown the rewind button in the code above since all the others are identical except for the names of the buttons, pixmaps and signals. For each of the buttons we require we call the QPushButton constructor passing it the appropriate embedded pixmap. We then add it to the layout. Finally we connect the button's clicked() signal to the appropriate signal. Since the clicked() signals aren't specific to our widget we want to emit signals that reflect the widget's use. The rewind(), play(), etc. signals are meaningful in the context of our widget so we propagate each button's clicked() signal to the appropriate widget-specific signal.

I dont understand, though, which part of that code composes the "signal" aspect.. as I see no "emit" or "connect" or anything of the sort.
Could someone perhaps give me an example of a qpushbutton ("Foo"), that will cause the parent container to give a "button1-presseddown" signal when it is pressed? I dont need custom pixmaps or anything, I just need a way to get three or four widgets into one container, and to be able to receive signals from the container instead of the individual buttons.

Thank you

JimDaniel
19th June 2008, 03:50
This code isn't quite as elaborate as it seems, it should give you all the information you need about how to pass around signals within an object. I added the handleClick() method to the class just so you can see how the parent signal buttonClicked() connects to a slot, normally handleClick() would live in some other object.

Feel free to ask if you have any questions. You ought to be able to put this code into a qt app and step it through the debugger so you can see what is happening.

The QSignalMapper is not necessary for your purposes I don't think, but its a common problem that comes up when you're writing code like this, with multiple buttons in a parent container all sending signals, so I gave you an example of that too.



#include <QtGui/QApplication>
#include "ParentSignals.h"

int main(int argc, char *argv[])
{
QApplication a(argc, argv);
ParentSignals parent_signals;
parent_signals.show();
return a.exec();
}

#ifndef PARENTSIGNALS_H
#define PARENTSIGNALS_H

#include <QtGui>

class ParentSignals : public QWidget
{
Q_OBJECT

public:
ParentSignals(QWidget * parent = 0);
virtual ~ParentSignals();

signals:
void buttonClicked(int button);

public slots:
void handleClick(int button);

private:
QPushButton * button_one;
QPushButton * button_two;
QPushButton * button_three;
QPushButton * button_four;
QPushButton * button_five;

QSignalMapper * button_mapper;
};

#endif // PARENTSIGNALS_H

#include "ParentSignals.h"

ParentSignals::ParentSignals(QWidget * parent) : QWidget(parent)
{
button_mapper = new QSignalMapper();

button_one = new QPushButton("One");
connect(button_one, SIGNAL(clicked()), button_mapper, SLOT(map()));
button_mapper->setMapping(button_one, 1);

button_two = new QPushButton("Two");
connect(button_two, SIGNAL(clicked()), button_mapper, SLOT(map()));
button_mapper->setMapping(button_two, 2);

button_three = new QPushButton("Three");
connect(button_three, SIGNAL(clicked()), button_mapper, SLOT(map()));
button_mapper->setMapping(button_three, 3);

button_four = new QPushButton("Four");
connect(button_four, SIGNAL(clicked()), button_mapper, SLOT(map()));
button_mapper->setMapping(button_four, 4);

button_five = new QPushButton("Five");
connect(button_five, SIGNAL(clicked()), button_mapper, SLOT(map()));
button_mapper->setMapping(button_five, 5);

connect(button_mapper, SIGNAL(mapped(int)), this, SIGNAL(buttonClicked(int)));

connect(this, SIGNAL(buttonClicked(int)), this, SLOT(handleClick(int)));

QHBoxLayout * layout = new QHBoxLayout();
layout->addWidget(button_one);
layout->addWidget(button_two);
layout->addWidget(button_three);
layout->addWidget(button_four);
layout->addWidget(button_five);

this->setLayout(layout);
}

ParentSignals::~ParentSignals()
{

}

void ParentSignals::handleClick(int button)
{
//do something cool here
}

sekatsim
20th June 2008, 02:24
Thank you so much, first of all. This was just what I needed. I do have one question though:

I'm trying to add a QSlider, and set its min and max values. Based off the documentation, I took


new QSlider ( int minValue, int maxValue, int pageStep, int value, Orientation orientation, QWidget * parent, const char * name = 0 )

and made it into


new QSlider ( 0, 100, 10, 0, Qt::Vertical, QWidget * parent, const char * name = 0 )

But I cant figure out what to pass for "QWidget * parent, const char * name = 0". Everything I try returns errors.

Any suggestions?

JimDaniel
20th June 2008, 05:42
Looking at Qt Assistant for 4.4.0, I don't see a QSlider constructor that takes those parameters, so I'm not sure what version you're using.

But assuming the parameter list is correct, your problem is that when you pass in arguments, you can't declare a QWidget like that, its asking for a specific instance of a QWidget - if this QSlider lives in your custom widget, then you would pass in "this."

As for the const char * name - followed by = 0 in the parameter list makes it a default parameter, which means you don't have to pass in anything - if you don't pass in anything 0, or nothing, will be passed in - in this case. But again, you can't declare anything as the actual argument, a const char * is looking for something like "My Cool Slider."

So in case you're confused, if this QSlider lives in your custom widget, you could do something like this, and it should work:



new QSlider(0, 100, 10, 0, Qt::Vertical, this, "My Cool Slider");


But take another look at the documentation for QSlider, your profile says you are using Qt 4.

sekatsim
20th June 2008, 22:49
Seems like I was using the wrong version of the documentation, sorry about that. Got it working well now, I have one "submaster" widget with my three buttons, a slider, and a spinbox. I connected the slots between the slider and spinbox so that they display eachother's values.

What I now need to do is create a tabwidget that can be filled automatically with these Submaster Widgets.. 18 to a tab. I'd like it so that when the user clicks "Button 1" in "Widget 2", the tab widget will emit something like "Widget2,Button1 pressed".

Would the best way to do this be to make a QSignalMapper for each button (so all of the "Button1"s would go to one mapper), or should I have one QSignalMapper for each SubmasterWidget, which then goes into Another QSignalMapper for the TabWidget-- or is this even possible?

Also: Is there anyway to pass a value through a signal mapper? I have a signal being emitted when my slider-value is changed, but I'm not quite sure how to process it.

Thank you for your help!

JimDaniel
21st June 2008, 17:29
This should get you started:



#include <QtGui/QApplication>
#include "TabWidget.h"

int main(int argc, char *argv[])
{
QApplication a(argc, argv);
TabWidget w;
w.show();
return a.exec();
}

#include <QList>
#include <QWidget>

class SubMaster;

class TabWidget : public QWidget
{
Q_OBJECT

public:
TabWidget(QWidget * parent = 0);
virtual ~TabWidget();

signals:
void buttonOneClicked(int submaster);
void buttonTwoClicked(int submaster);
void buttonThreeClicked(int submaster);

void sliderValueChanged(int submaster, int value);
void spinboxValueChanged(int submaster, int value);

public slots:
void handleSliderValueChange(int submaster);
void handleSpinboxValueChange(int submaster);

private:
QList<SubMaster *> m_submasters;
};

#endif // TABWIDGET_H

#include <QVBoxLayout>
#include <QSignalMapper>

#include "TabWidget.h"
#include "ParentSignals.h"

TabWidget::TabWidget(QWidget *parent) : QWidget(parent)
{
QVBoxLayout * layout = new QVBoxLayout();

QSignalMapper * buttonOneMapper = new QSignalMapper(this);
QSignalMapper * buttonTwoMapper = new QSignalMapper(this);
QSignalMapper * buttonThreeMapper = new QSignalMapper(this);
QSignalMapper * sliderMapper = new QSignalMapper(this);
QSignalMapper * spinboxMapper = new QSignalMapper(this);

for(int i = 0; i < 18; ++i)
{
SubMaster * sub_master = new SubMaster();

connect(sub_master, SIGNAL(buttonOneClicked()), buttonOneMapper, SLOT(map()));
buttonOneMapper->setMapping(sub_master, i);
connect(buttonOneMapper, SIGNAL(mapped(int)), this, SIGNAL(buttonOneClicked(int)));

connect(sub_master, SIGNAL(buttonTwoClicked()), buttonTwoMapper, SLOT(map()));
buttonTwoMapper->setMapping(sub_master, i);
connect(buttonTwoMapper, SIGNAL(mapped(int)), this, SIGNAL(buttonTwoClicked(int)));

connect(sub_master, SIGNAL(buttonThreeClicked()), buttonThreeMapper, SLOT(map()));
buttonThreeMapper->setMapping(sub_master, i);
connect(buttonThreeMapper, SIGNAL(mapped(int)), this, SIGNAL(buttonThreeClicked(int)));

connect(sub_master, SIGNAL(sliderValueChanged()), sliderMapper, SLOT(map()));
sliderMapper->setMapping(sub_master, i);
connect(sliderMapper, SIGNAL(mapped(int)), this, SLOT(handleSliderValueChange(int)));

connect(sub_master, SIGNAL(spinboxValueChanged()), spinboxMapper, SLOT(map()));
spinboxMapper->setMapping(sub_master, i);
connect(spinboxMapper, SIGNAL(mapped(int)), this, SLOT(handleSpinboxValueChange(int)));

layout->addWidget(sub_master);

m_submasters.push_back(sub_master);
}

this->setLayout(layout);
}

TabWidget::~TabWidget()
{

}

void TabWidget::handleSliderValueChange(int submaster)
{
int slider_value = m_submasters.at(submaster)->currentSliderValue();
emit sliderValueChanged(submaster, slider_value);
}

void TabWidget::handleSpinboxValueChange(int submaster)
{
int spinbox_value = m_submasters.at(submaster)->currentSpinBoxValue();
emit spinboxValueChanged(submaster, spinbox_value);
}


#ifndef SUBMASTER_H
#define SUBMASTER_H

#include <QtGui>
#include <QtCore>

class SubMaster : public QWidget
{
Q_OBJECT

public:
SubMaster(QWidget * parent = 0);
virtual ~SubMaster();

int currentSliderValue() { return slider->value(); }
int currentSpinBoxValue() { return spin_box->value(); }

signals:
void buttonOneClicked();
void buttonTwoClicked();
void buttonThreeClicked();

void sliderValueChanged();
void spinboxValueChanged();

private:
QPushButton * button_one;
QPushButton * button_two;
QPushButton * button_three;

QSlider * slider;
QSpinBox * spin_box;
};

#endif // SUBMASTER_H

#include "SubMaster.h"

SubMaster::SubMaster(QWidget * parent) : QWidget(parent)
{
button_one = new QPushButton("One");
connect(button_one, SIGNAL(clicked()), this, SIGNAL(buttonOneClicked()));

button_two = new QPushButton("Two");
connect(button_two, SIGNAL(clicked()), this, SIGNAL(buttonTwoClicked()));

button_three = new QPushButton("Three");
connect(button_three, SIGNAL(clicked()), this, SIGNAL(buttonThreeClicked()));

slider = new QSlider();
connect(slider, SIGNAL(valueChanged(int)), this, SIGNAL(sliderValueChanged()));

spin_box = new QSpinBox();
connect(spin_box, SIGNAL(valueChanged(int)), this, SIGNAL(spinboxValueChanged()));

connect(slider, SIGNAL(valueChanged(int)), spin_box, SLOT(setValue(int)));
connect(spin_box, SIGNAL(valueChanged(int)), slider, SLOT(setValue(int)));

QHBoxLayout * layout = new QHBoxLayout();
layout->addWidget(button_one);
layout->addWidget(button_two);
layout->addWidget(button_three);
layout->addWidget(slider);
layout->addWidget(spin_box);

this->setLayout(layout);
}

SubMaster::~SubMaster()
{
}

sekatsim
22nd June 2008, 14:52
JimDaniel,

Thank you again for all the help. I'm attaching a screenshot, so you can see that I'm not just copying your work.

Everything has gone well so far, I've gotten the custom widget to run withing my existing program, and I've been able to make it look the same as the older individual widgets did. The problem I'm having now is regarding signals and slots.

I have the master layout for the custom widget in TabWidget.h, as per your suggestions. However, in my "MainWindowImpl.cpp" file, where the rest of my slots are


connect(TabWidget,SIGNAL(buttonOneClicked(int submaster,int)),this,SLOT(bumpUp1(int submaster,int)));

Returns an:

error: expected primary-expression before ‘,’ token

I've tried a bunch of different things-- I tried making an instance of the class, which didnt work, and I tried connecting directly to the individual submasters themselves, which didnt work.

For example:


TabWidget * tab_widget = new TabWidget();
connect(tab_widget,SIGNAL(buttonOneClicked(int submaster,int)),this,SLOT(bumpUp1(int submaster,int)));

Builds, but on clicking the button, returns

Object::connect: No such signal TabWidget::buttonOneClicked(int submaster,int)
Object::connect: (receiver name: 'MainWindow')

It occurred to me that all other widgets are referenced by name, and not by class.. is that the problem that I'm having? How would I go about attaching a name to this custom widget? Or is that not the problem at all?

Thank you again for your assistance

JimDaniel
22nd June 2008, 15:21
Cool, app looks slick. How are you styling the GUI if you don't mind me asking?

Your problem in this case is just that you can't have a descriptor for parameters in the connect:



connect(tab_widget, SIGNAL(buttonOneClicked(int, int)), this, SLOT(bumpUp1(int, int)));

sekatsim
26th June 2008, 17:19
Great, got it working beautifully now. I just styled it in QDesigner, and set every object to Inherit, aside from a few tweaks. I usually try to conform to UI guidelines, but since it's designed to be run in a dark room, a bright style wouldnt have really worked for me.

Thanks for all of your help!