Question

I have a custom QDialog with a set of custom sliders (i.e., QWidgets each consisting of a slider and an associated label) on it. In essence, this dialog is used to change the coordinates of an object in a 3D scene by adjusting the sliders for each dimension.

Currently, the QDialog itself stores a pointer to the scene object it modifies. Thus, the slot that takes care of object movement when a slider emits valueChanged is also part of the QDialog class. Since it has no way of knowing which slider was moved, the movement function (rather inefficiently) just loops through all of the sliders on the dialog, gathers their values, and assigns a new configuration to the 3D object.

Ideally, only the dimension that changed would have to be re-assigned when a slider is moved. So I tried using QSignalMapper to identify each slider with a numerical index. This would require the ability to send a valueChanged signal with two parameters: one identifying the sender slider, and one giving the new value itself. Unfortunately, as I learned here, QSignalMapper can't do this.

Another way to get the functionality I want would perhaps be to use the sender() method. But, according to the documentation, that's bad practice--it violates the principle of modularity.

I can think of a couple of other solutions: allow the custom slider class to store its parent dialog (seems bad in the same say that sender() is bad), or maybe even store the movable object itself as a static member of the custom slider class instead of (non-statically/as it is now) in the overall dialog.

Which of these approaches, if any, would be the best way to go here? What alternatives should I consider?

Was it helpful?

Solution

Possible solution is connect QSlider signal sliderReleased(), emitted when the user releases the slider with the mouse, with QSignalMapper map() and store sliders id with pointer on some list. When value has hanged, QDialog could emit another signal with information of slider id and new value.

QSignalMapper *mapper = new QSignalMapper(this);

connect(slider_0, SIGNAL(sliderReleased()), mapper, SLOT(map()));
mapper->setMapping(slider_0, 0);
tab_s[0] = slider_0;
connect(slider_1, SIGNAL(sliderReleased()), mapper, SLOT(map()));
mapper->setMapping(slider_1, 1);
tab_s[1] = slider_1;
connect(slider_2, SIGNAL(sliderReleased()), mapper, SLOT(map()));
mapper->setMapping(slider_2, 2);
tab_s[2] = slider_2;


connect(mapper, SIGNAL(mapped(int)),
        this, SLOT(checkSlider(int)));

and in some slot:

void SomeDialog::checkSlider(int id)
{
    emit valueChanged(id, tab_s[id]->value());
}

OTHER TIPS

The ideal solution would be to subclass QSlider and re-emit the valueChanged() signal with added parameters (x/y/z axis). Let's say MySlider which constructs with given axis index (0/1/2):

class MySlider : public QSlider
{
    Q_OBJECT
public:
    MySlider(int axis, QWidget *parent);

signals:
    void valueChanged(int axis, int value);

private slots:
    void reemitValueChanged(int value);

private:
    int m_axis;
};

MySlider::MySlider(int axis, QWidget *parent)
    : QSlider(parent)
{
    m_axis = axis;
    connect(this, SIGNAL(valueChanged(int)),
            this, SLOT(reemitValueChanged(int)));
}

void MySlider::reemitValueChanged(int value)
{
    emit valueChanged(m_axis, value);
}

MySlider intercepts valueChanged(int) signal from QSlider, and emits its own signal valueChanged(int,int) with the axis index (0/1/2). You can use MySlider in the application now:

for (int i=0; i<3; i++) {
    sliders[i] = new MySlider(i, this);
    connect(sliders[i], SIGNAL(valueChanged(int,int)),
            this, SIGNAL(updateScene(int,int)));
}

Of course, you'll have to arrange these sliders in a layout or something. Here's the source of this approach.

I think using the sender() method is fine. I'd write something like this:

enum Axes
{
    axisX,
    axisY,
    axisZ
}

class MyDialog : public QDialog
{
   ...
signals:
   void valueChanged(int value, int type);
}

MyDialog::MyDialog(QWidget *parent) : 
    QDialog(parent)
{    
    ...
    connect(xSlider, SIGNAL(valueChanged(int)), 
            this, SLOT(onSliderChange(int));
    connect(ySlider, SIGNAL(valueChanged(int)), 
            this, SLOT(onSliderChange(int));
    connect(zSlider, SIGNAL(valueChanged(int)), 
            this, SLOT(onSliderChange(int));
}

void MyDialog::onSliderChange(int value)
{
    int axis = -1;
    QSlider *slider = dynamic_cast<QSlider*>(sender());
    if (slider == xSlider)
    {
        axis = axisX;
    }
    else if (slider == ySlider)
    {
        axis = axisY;
    }
    else if (slider == zSlider)
    {
        axis = axisZ;
    }
    else
    {
        qWarning() << "Wrong sender";
    }

    if (axis != -1)
    {
        emit valueChanged(value, axis);
    }
}
//signalMapper.h
//--------------

#ifndef SIGNALMAPPER_H
#define SIGNALMAPPER_H

#include <QWidget>
#include <QGridLayout>
#include <QSlider>
#include <QLabel>

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = 0);
    ~Widget();

private:
    QGridLayout*    m_pGridLayoutMain;
    QLabel*         m_pLabel;

private slots:
    void setLabelText(QWidget *pWidget);
};

#endif // SIGNALMAPPER_H


//signalMapper.cpp
//----------------

#include "signalMapper.h"
#include <QSignalMapper>
#include <QString>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
{
    setMinimumSize(400, 200);
    QSignalMapper* pSignalMapper = new QSignalMapper(this);

    m_pGridLayoutMain = new QGridLayout(this);
    m_pGridLayoutMain->setContentsMargins(10, 10, 10, 10);
    m_pGridLayoutMain->setSpacing(10);

    m_pLabel = new QLabel(this);
    m_pLabel->setMinimumSize(150, 20);
    m_pLabel->setAlignment(Qt::AlignCenter);
    m_pLabel->setFrameStyle(QFrame::Box | QFrame::Sunken);
    m_pGridLayoutMain->addWidget(m_pLabel, 0, 0);

    for(int i=1; i < 10; i++)
    {
        QSlider* pSlider = new QSlider(this);

        QString strObjName = "Slider " + QString().setNum(i);
        pSlider->setObjectName(strObjName);
        pSlider->setMinimum(0);
        pSlider->setMaximum(100);
        pSlider->setSingleStep(1);
        pSlider->setOrientation(Qt::Horizontal);
        pSlider->setValue(35);

        connect(pSlider, SIGNAL(valueChanged(int)), pSignalMapper, SLOT(map()));
        pSignalMapper->setMapping(pSlider, pSlider);
        m_pGridLayoutMain->addWidget(pSlider, i, 0);
    }

    connect(pSignalMapper, SIGNAL(mapped(QWidget*)), this, SLOT(setLabelText(QWidget*)));
}

Widget::~Widget()
{

}

void Widget::setLabelText(QWidget *pWidget)
{
    QSlider* pSlider = dynamic_cast<QSlider*>(pWidget);

    if(pSlider)
    {
        qDebug("Success");
        m_pLabel->setText(pSlider->objectName()+" value changed to "+QString().setNum(pSlider->value()));
    }
    else
    {
        qDebug("Failure");
    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top