Monday 11 February 2013

Customize widget: Creating new signal and slots


Customize widget

Qt provide list of widgets that we can use with signal and slot as we used in last blog. We used QWidget as a container in our last example. We can make combination of Qt widgets, that can be used as a single object or widget having its own signal and slots . It is possible by inheriting QWidget class and adding components to it. Here is a complete example with explanation:
First we have to specify what it will do?
We are going to make a widget which is a dialog box that can take user's input to add or remove RGB point in a color transfer function of a volume visualization object. It require following inputs:

1.       Point where user want to add/remove RGB values
2.       Value of R
3.       Value of G
4.       Value of B
5.       User want to add
6.       User want to remove    
7.       User don't want any action

Now we have to use appropriate Qt widgets to get these inputs. So here is the list:
1.       Point where user want to add/remove RGB values: spinbox that is QSpinBox.
2.       Value of R: Since we want that user can specify a smallest value 0.1 so we need a spinbox that take double value, so QDoubleSpinBox.
3.       Value of G: QDoubleSpinBox
4.       Value of B: QDoubleSpinBox
5.       User want to add: an Add button so QPushButton
6.       User want to remove: A Remove button so again QPushButton
7.       User don't want any action: a cancel button, QPushButton
These input should be passed by widget to the main application where these parameters will be used. So we have to make a signal which will carry all the input information and pass it to slot of main application that will be connected to it.
We have decided the way of input and output now how widget should appear?
First we have to decide what  other things required to make widget's presentation more informative to user and second draw a rough layout where we want to place these all widgets.
We need labels for each spinbox to show which spinbox can be use to input point/r/g/b. Also some layout objects to position these all widgets as so that our widget appear as:

Now we have to define class of our widget. Make a header file with name ColorTFWidget.h
as follow:
#ifndef _ColorTFWidget_H_
#define _ColorTFWidget_H_

#include <QWidget>

class QPushButton;
class QSpinBox;
class QDoubleSpinBox;

class ColorTFWidget : public QWidget
{
     Q_OBJECT
public:
     ColorTFWidget(QWidget * parent = 0);
     ~ColorTFWidget();

private:
     QSpinBox * point ;
     QDoubleSpinBox * r ;   
     QDoubleSpinBox * g ;
     QDoubleSpinBox * b;
    
     QPushButton * Add;
     QPushButton * Remove;
     QPushButton * Cancel;

signals:
     void SignalColorTF(int action, int point, double r, double g, double b );

private slots:
     //internal slots
          void OnAdd();
          void OnRemove();
    
};

#endif _ColorTFWidget_H_
As a general rule starting with header gaurds we are includeing QWidget since our class is inheriting it. For other classes that belong to widgets we are using forwared declaration.
If you having question that why we are doing forward declaration? here is a nice explanation.
We are inherting QWidget with public scope. Since we are going to make our own signal and slots it is mandatory to add Q_OBJECT macro in the class defination.
Q_OBJECT macro is responsible to involve meta object compiler(moc).  Here moc will generate neccessary code to setup signal slot mechanism.  You can find more info about moc here.
Constructor and destructor will be defined in cpp file.
We are creating data members only those widgets that are input or output or objects those will be accessed by any method of class.
 Other objects like layouts and labels will not be changed once they created and not used by any method so they will be created and added to widget in consturctor.
Signal SignalColorTF will provide connectivty to our widget object to share its data with other widget. This signal have five parameters which we want to pass. action will be used as a flag whcih carry info about either user want to remove point or add.
Widget is having two internal slots there use will be clear in cpp file by there defination.


cpp file of class is as follows:
//include class declaration header
#include "ColorTFWid.h"

//include all headers of widgets used in class else those added in headers
#include <QPushButton>
#include <QSpinBox>
#include <QDoubleSpinBox>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QLabel>

ColorTFWidget::ColorTFWidget(QWidget * parent ):QWidget(parent)
//parent is used to make this widget a child of some other widget
{

     //creating label
     // ampersion symbol (&) before any word will make it as a Alt+ shortcut key
     //to select its buddy widget
     QLabel * pointlabel = new QLabel("At &Point: ");

     //instantiating spin box
     point = new QSpinBox;
    
     //setting range of spin box
     point->setRange(0,1024);

     //a buddy is widget that will be selected when Alt + shortcut key pressed
     pointlabel->setBuddy(point);

     QLabel * rlabel = new QLabel("&R: ");
     r = new QDoubleSpinBox;
     r->setSingleStep(0.1);
     r->setRange(0,255);
     rlabel->setBuddy(r);

     QLabel * glabel = new QLabel("&G: ");
     g = new QDoubleSpinBox;
     g->setSingleStep(0.1);
     r->setRange(0,255);
     glabel->setBuddy(g);
    
     QLabel * blabel = new QLabel("&B: ");
     b = new QDoubleSpinBox;
     b->setSingleStep(0.1);
     b->setRange(0,255);
     blabel->setBuddy(b);

     //creating layout to arrange labels in a horizontal line
     QHBoxLayout * labelLayout = new QHBoxLayout;

     //adding labels to layout according to order they shoud appear
     labelLayout->addWidget (pointlabel);
     labelLayout->addWidget (rlabel);
     labelLayout->addWidget (glabel);
     labelLayout->addWidget (blabel);
    
     //creating layout for spin boxes
     QHBoxLayout * spinboxLayout = new QHBoxLayout;

     //adding spin box to layout according to order they should appear
     spinboxLayout->addWidget (point);
     spinboxLayout->addWidget (r);
     spinboxLayout->addWidget (g);
     spinboxLayout->addWidget (b);

     //adding layouts to a vertical layout
     QVBoxLayout * upperLayout = new QVBoxLayout;
     upperLayout->addLayout(labelLayout);
     upperLayout->addLayout(spinboxLayout);

     //creating pushbuttons
     Add = new QPushButton("Add");
     Remove = new QPushButton("Remove");
     Cancel = new QPushButton("Cancel");

     //creating button layout and adding them
     QHBoxLayout * buttonLayout = new QHBoxLayout;
     buttonLayout->addWidget(Add);
     buttonLayout->addWidget(Remove);
     buttonLayout->addWidget(Cancel);

     //arranging all layouts in a single layout
     QVBoxLayout * mainLayout = new QVBoxLayout(this);
     mainLayout->addLayout(upperLayout);
     mainLayout->addLayout(buttonLayout);

     //we can set single layout to a QWidget
     this->setLayout(mainLayout);
     //adding title of window
     this->setWindowTitle("Edit Color TF");

     //connecting buttons signal to internal slots
     connect(Add,SIGNAL(clicked()),this,SLOT(OnAdd()));
     connect(Remove,SIGNAL(clicked()),this,SLOT(OnRemove()));
     connect(Cancel,SIGNAL(clicked()),this,SLOT(close()));
}

ColorTFWidget::~ColorTFWidget()
{
     //delete all widgets
     delete point ;
     delete r;
     delete g;
     delete b;
     delete Add;
     delete Remove;
     delete Cancel;
}

void ColorTFWidget::OnAdd()
{
     // when Add is clicked this slot will emit signal with following information
     emit SignalColorTF( 1, point->value(), r->value(),g->value(),b->value());
     this->close();
}

void ColorTFWidget::OnRemove()
{
     // when Remove is clicked this slot will emit signal with following information
     emit SignalColorTF( 0, point->value(), r->value(),g->value(),b->value());
     this->close();
}
Now write a main.cpp to use this widget:
#include <QApplication>
#include "ColorTFWid.h"

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

     ColorTFWidget w;
    w.show();
   
    return a.exec();
}

To do:
You can make another widget in main and can connect ColorTFWidget's signal to slot of another widget to display those values.

Error log:
1.If you make a mistake in connection like this:
 connect(Add,SIGNAL(clicked()),this,SLOT(OnAdd(int)));
It will not cause any compiler error but your signal will not be connected to slot. A warning will be print on command line.
It happens usually when you edit slots or when slot have parameters.
In slots that have parameters for example OnABCD(int a) if you write parameter name in connect
 connect(Add,SIGNAL(clicked()),this,SLOT(OnABCD(int a)));
same problem will appear.


2. Some linker errors like following will appear if you forget to add header and cpp properly to .pro.  Don't forget to qmake after any change in .pro file.

main.obj:-1: error: LNK2019: unresolved external symbol "public: virtual __cdecl ColorTFWidget::~ColorTFWidget(void)" (??1ColorTFWidget@@UEAA@XZ) referenced in function main

Sometimes you need to clean build and then qmake to generate new moc rules.




3 comments:

  1. hi can you tell me how to generate a signal on mouse move event

    ReplyDelete
    Replies
    1. override mouseMove virtual function (you will have to subclass)

      Delete
  2. You have to enable mouse tracking along with suggestion of osirisgothra

    ReplyDelete