Connect 4 sliders together



  • Hello guys,

    I've tried to connect 4 sliders (make them all have the same value) together when a checkbox is active, but Qt crashes everytime I move the sliders when the checkbox is active.

    What's the best way for doing that?! what kind of connection should I establish between the sliders to make that possible.

    Thank you for any efforts :-)



  • Could you please share your source code? To set a value of a slider use "setValue()":http://developer.qt.nokia.com/doc/qt-4.8/qabstractslider.html#value-prop



  • Please how leon.anavi said, share source code and tell us where it's crashing, we can't help you without knowing how your stuff works.



  • If you have each slider connected to all sliders and you change one, a signal valueChanged() is emitted to all the sliders which triggers their setValue() slots, which triggers their own valueChanged() signals which changes the value of the slider you personally changed the value of, so another valueChanged() is emitted and so on...

    This creates an infinite loop which quickly exceeds the limit of the function call stack and your programs crashes...

    You need to implement logic to control the whole process and avoid those infinite circular connections.


  • Moderators

    Actually, according to the Qt 4.8 source code for QAbstractSlider::setValue(), it appears that setValue() will not emit valueChanged() if it is being set to its current value. (See lines 5 and 6 below.) So you shouldn't have to worry about that particular infinite loop scenario.

    Snippet:
    @
    void QAbstractSlider::setValue(int value)
    {
    Q_D(QAbstractSlider);
    value = d->bound(value);
    if (d->value == value && d->position == value)
    return;
    d->value = value;
    if (d->position != value) {
    d->position = value;
    if (d->pressed)
    emit sliderMoved((d->position = value));
    }
    #ifndef QT_NO_ACCESSIBILITY
    QAccessible::updateAccessibility(this, 0, QAccessible::ValueChanged);
    #endif
    sliderChange(SliderValueChange);
    emit valueChanged(value);
    }
    @



  • I didn't knew that. Then we really need to see his code in order to figure out what's wrong with it. Running out of call stack was the perfect candidate for his crash. I remember in the book I read on Qt the author explicitly warned to avoid such circular connection loops, but then again, the book is from 6 years ago...



  • This is a general pattern, that is used around Qt. You should also follow it for your own code: don't emit changed messages if you're not actually changed. My property setters basically all follow this pattern:

    @
    void MyClass::setValue(int newValue)
    {
    if (newValue == m_value)
    return;

    m_value = newValue;
    emit valueChanged(m_value);
    

    }
    @

    This way, you can never end up in infinate update loops.



  • Thank you for all your replies :-)

    It worked in the new Qt. I used to use Qt 4.7 and it crashed there in my Office's computer. Now I'm using the new SDK (Qt 4.7.4) it doesn't crash. Apparently they fixed it somehow...!

    It was even more complicated because my slider wasn't only a slider, but also a QDoubleSpinBox connected with the slider to change with it.

    Honestly I had this problem in a previous program on the older Qt, and I'm like "traumatised" from using it again because it drove me crazy then and couldn't solve it. So I decided to post the problem even before writing its code. But now I saw that they fixed it, I apologise to all of you :-)

    Here's the code that worked (in a simple program I made only for that purpose), if anyone is interested :-)

    Widget prototypes:

    @
    #ifndef WIDGET_H
    #define WIDGET_H

    #include <QtGui/QWidget>
    #include <QSlider>
    #include <QCheckBox>
    #include <QGridLayout>
    #include "linkedsliderdoublespinbox.h"

    class Widget : public QWidget
    {
    Q_OBJECT

    public:
    Widget(QWidget *parent = 0);
    ~Widget();
    LinkedSliderDoubleSpinBox *sli1,*sli2,sli3,sli4;
    QCheckBox
    lockCB;
    QGridLayout
    layout;

    public slots:
    void setValue1(double value);
    void setValue2(double value);
    void setValue3(double value);
    void setValue4(double value);

    };

    #endif // WIDGET_H
    @

    Widget codes:

    @

    #include "widget.h"

    Widget::Widget(QWidget *parent)
    : QWidget(parent)
    {
    sli1 = new LinkedSliderDoubleSpinBox(0,10,100,5,this);
    sli2 = new LinkedSliderDoubleSpinBox(0,10,100,5,this);
    sli3 = new LinkedSliderDoubleSpinBox(0,10,100,5,this);
    sli4 = new LinkedSliderDoubleSpinBox(0,10,100,5,this);
    lockCB = new QCheckBox;

    lockCB->setText("&Lock");
    
    layout = new QGridLayout;
    
    layout->addWidget(lockCB);
    layout->addWidget(sli1->getSlider(),1,0,1,1);
    layout->addWidget(sli1->getSpinBox(),1,1,1,1);
    layout->addWidget(sli2->getSlider(),2,0,1,1);
    layout->addWidget(sli2->getSpinBox(),2,1,1,1);
    layout->addWidget(sli3->getSlider(),3,0,1,1);
    layout->addWidget(sli3->getSpinBox(),3,1,1,1);
    layout->addWidget(sli4->getSlider(),4,0,1,1);
    layout->addWidget(sli4->getSpinBox(),4,1,1,1);
    
    
    this->setLayout(layout);
    
    connect(this->sli1,SIGNAL(valueChanged(double)),this,SLOT(setValue1(double)));
    connect(this->sli2,SIGNAL(valueChanged(double)),this,SLOT(setValue2(double)));
    connect(this->sli3,SIGNAL(valueChanged(double)),this,SLOT(setValue3(double)));
    connect(this->sli4,SIGNAL(valueChanged(double)),this,SLOT(setValue4(double)));
    

    }

    Widget::~Widget()
    {
    delete sli1;
    delete sli2;
    delete sli3;
    delete sli4;
    delete lockCB;

    delete layout;
    

    }
    void Widget::setValue1(double value)
    {
    if(lockCB->isChecked())
    {
    sli2->setValue(value);
    sli3->setValue(value);
    sli4->setValue(value);
    }
    }
    void Widget::setValue2(double value)
    {
    if(lockCB->isChecked())
    {
    sli1->setValue(value);
    sli3->setValue(value);
    sli4->setValue(value);
    }
    }
    void Widget::setValue3(double value)
    {
    if(lockCB->isChecked())
    {
    sli1->setValue(value);
    sli2->setValue(value);
    sli4->setValue(value);
    }
    }
    void Widget::setValue4(double value)
    {
    if(lockCB->isChecked())
    {
    sli1->setValue(value);
    sli2->setValue(value);
    sli3->setValue(value);
    }
    }
    @



  • Correct me if I am wrong, but since you are passing a parent to all the slider constructors you don't really need to delete the sliders in the destructor, since the parent widget is responsible for collecting all the children.

    You only need to explicitly delete dynamically allocated objects which don't have a parent or have a default 0 (NULL) parent.

    Also you can create different UI elements without a parent, and as soon as you add them to a layout, the layout becomes their parent, responsible for collecting them. The layout itself becomes parented to the widget when using setLayout(). So you have a full tree of ownership for all elements and all memory management is done for you by QObject. This is part of the beauty of Qt, it makes dynamic memory management in C++ much easier as long as you remember to build consistent data structure hierarchy.

    In your case you have some excess code you don't really need.

    Also, my suspicion over the call stack exceed appears to be a correct one after all, if switching to a more recent version fixed it alone. I guess the check Andre mentioned was absent in your old version.



  • Thank you for this information ddriver! I never knew that QObject handles the destruction of objects like that!!! I've always worried about how to destroy objects hierarchically so that no conflicts would appear. I think it's worth it to check memory stuff with Valgrind.

    Thanks again for the info!



  • This is what the documentation has to say about QObject:

    bq. QObjects organize themselves in object trees. When you create a QObject with another object as parent, the object will automatically add itself to the parent's children() list. The parent takes ownership of the object; i.e., it will automatically delete its children in its destructor. You can look for an object by name and optionally type using findChild() or findChildren()

    A very quick test, MyClass inherits QObject and has only a QString name and the destructor only prints out "Deleting " and the instance name.

    @ myClass * rootParent = new myClass("ROOT"); // root parent
    new myClass("Child 1", rootParent); // new without returning local pointer
    new myClass("Child 2", rootParent);
    myClass * child = new myClass("Child 3", rootParent); // here we need a pointer use as parent
    new myClass("GrandChild 1", child);
    new myClass("GrandChild 2", child);

    rootParent->dumpObjectTree();
    
    delete rootParent;@
    

    And the console output to display the tree structure (dumpObjectTree() only works in DEBUG mode) and indicate all children were collected:

    @myClass::
    myClass::
    myClass::
    myClass::
    myClass::
    myClass::

    Deleting "ROOT"
    Deleting "Child 1"
    Deleting "Child 2"
    Deleting "Child 3"
    Deleting "GrandChild 1"
    Deleting "GrandChild 2"@

    Naturally it will work for all GUI elements since they are all QObjects in order to get signals and slots functionality.



  • Thanks for the reply.

    I have a better idea! how about using them as variables rather than poiners? wouldn't that be better?
    I've always seen objects taken as pointers and I don't understand why people do this. So I follow without really understanding what the deal really is...!



  • You mean using them as local stack objects instead of dynamically allocating the resources? That wouldn't be a good idea, since stack space is limited and when it runs out your program crashes, plus the lifetime of those objects is limited to the local scope. If you create GUI elements in the constructor without pointers, basically do static allocation inside the constructor, all the objects will be deleted once the constructor is finished and you won't get anything.

    Plus the very mechanism of connecting signals and slots uses pointers, so does QObject parenthood, so if you want to parent or connect local objects you have to use the & operator.

    You could make a single QObject or derived object local and attach children to it, in this case it will act as a c++11 std::unique_ptr and will delete all children as soon as it goes out of scope.

    Sure, there is a performance and memory usage overhead when dynamically allocating memory, but as far as I know this is currently the best practice. There were some old examples in Qt that used stack allocation (which is blazingly fast since the compiler does 99.99% of the work during compile time) but they were all labeled as suboptimal by the community. Stack allocated objects are short-lived and in many cases this is simply not desirable.

    The memory footprint overhead of dynamic memory allocation comes from the fact you request memory from the system, and the system will almost always use more memory than you need. You get wasted memory for each and every usage of "new", one way to get over this is instead of creating multiple dynamically allocated objects individually, you create a struct that includes them all, so all the elements are allocated with a single "new" and the only wasted memory space is the padding inserted by the compiler. However this adds some inconvenience since you can no longer access objects directly, you have to do it through the struct, and I rarely see people merging all their dynamically allocated elements in a struct, people just use "new" for each and every object and live with the penalties, both performance and memory footprint wise.

    In general, unless you are passing integer values or other small objects, it is a good idea to use pointers, a pointer is the size of an int, and passing by a pointer is very cheap, if you pass the actual structure instead of a pointer, a full copy will be made. This is not the case with many of the Qt classes which use shallow copy instead. Also passing by pointer allows to directly manipulate an object, same goes for passing by reference, but if you pass the actual object, a copy will be created, passed into the function call, and then that copy will be assigned to the original object, which is a huge overhead for big data structures.

    Many people fear pointers, especially if they come from managed languages, where everything is passed by reference and memory management is done automatically, but it is not really that bad, you can easily implement deterministic "garbage collection" that beats anything managed languages can offer in terms of performance and efficiency.



  • Thanks for your reply.

    I don't know what you mean by saying that the stack memory is limited. Please elaborate on that!

    The option of "losing" stack variables out of scope is the same with pointers, but the difference is that you either lose the whole variable if it's allocated on stack, or you just lose the pointer if the pointer is allocated on the stack (where the data is allocated on the heap) (which would basically happen if we would allocate pointers in constructors like you said, and which we never!!! and it's even worse with pointers if you don't call delete to remove them from the heap, since you lose your handle to the object). The comparison you're doing in this is not fair; because if you allocate a stack variable in the class body, you won't lose it until the class is destroyed; which is totally convenient!!!! and which is even more convenient than having the pointer deleted automatically by QObject. So what's wrong in allocating stack variables in the class's body rather than pointers? it's faster, safer, and easier to deal with... right?

    And by the way, I always pass information to functions by reference. I never pass copies unless the objects I'm passing are really simple like doubles and integers.

    Conceptually, I don't have a problem with pointers, but why should I use them (in my current example, or in my widgets at least) if they're slower and more vulnerable to errors? I could use normal variables and keep using their references with the & operator, and they're still safe and won't cause a segmentation fault if they're allocated in the class (or struct) body.



  • Well, I recently tried allocating a 2D array of 1920x1080 ints on the stack, which is regular HD resolution and not that large - about 8 megabytes, and my compiler wasn't able to do that, my application crashed, because it caused what is known as a stack overflow. Stack space is limited, although the limitation varies from toolchain to toolchain it is there, you cannot afford to allocate a lot of memory on the stack, simple as that... Allocating a 2D array of 1920x1080 ints is only possible using dynamic memory allocation.

    There is a big difference between losing the identifier or the address of a variable and not having it at all. With dynamic memory allocation, you may lose the pointer, but with static memory allocation once an object falls out of scope it gets DELETED, it no longer exists in memory. When allocated on the heap the object will remain until either you delete it or the program execution halts, at which point the OS memory manager will deallocate it.

    Yes, if the object is allocated within the class body it will live as long as the class does, but as I already said, there are stack limitations, and the body of a class essentially uses stack allocation, and all the limitations apply, which is not that convenient :) If the application fails allocating lots of memory in the main function, it will also fail allocating that memory within the body of a class, the only way to overcome this is dynamic memory allocation.

    The main advantage of pointers is you can change where they point at, unless they are const pointers. You cannot do that with references, a reference points always and only to a single object. With pointers you can use pointer arithmetic and move the pointer to manipulate collections for example.



  • Well, the thing with the stack over-flow I never knew, and it answers my question. Thanks for the info. Gonna try it myself :-)

    Thanks. Bye!



  • You are welcome, I hope I didn't misinform you by accident, I myself am fairly new to programming.


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.