QSliders linked together

  • Hey fellow Qt devs

    The goal I wish to archive is 2 QSliders whose with values changes simultaneously if I added Qt::ControlModifier to mouse interaction with either of them. So far I got this:

    StepSlider::StepSlider(QWidget *parent)
    : QSlider(parent)
        QObject::connect(this, &QSlider::valueChanged, this, [this](int value)
            const int step = 5;
            int offset = value % step;
            if( offset != 0)
                this->setValue(value - offset);
    void StepSlider::setMirroredSlider(QSlider *slider)
        mirroredSlider = slider;
    void StepSlider::mousePressEvent(QMouseEvent *event)
        qDebug() << objectName() << " press pos: " << event->pos().x() << " ," << event->pos().y();
        if (event->modifiers() == Qt::ControlModifier)
            QMouseEvent *e = new QMouseEvent(QEvent::MouseButtonRelease, QPointF(), Qt::MiddleButton, 0, 0);
            *e = *event;
            qApp->postEvent(mirroredSlider, e);
    void StepSlider::wheelEvent(QWheelEvent *event)
        if (event->modifiers() == Qt::ControlModifier)
            QWheelEvent *e = new QWheelEvent(QPointF(), 0, 0, 0);
            *e = *event;
            qApp->postEvent(mirroredSlider, e);
    void StepSlider::mouseMoveEvent(QMouseEvent *event)
        //ten przypadek działa jedynie wtedy kiedy wartości slidera są identyczne
        //oznacza to, że mousePos().x() musi mu się zgadzać, inaczej coś się pieprzy
        //rozwiązanie - obliczyć pos dla 2 slidera tak, jakby wskaźnik myszy był nad nim
        //co może się przydać
        //int QStyle::sliderPositionFromValue(min, max, val, space, upsideDown);
        //QRect QStyle::subControlRect(QStyle::ComplexControl control, const QStyleOptionComplex *option, QStyle::SubControl subControl, const QWidget *widget = nullptr) const
    //    QStyleOptionSlider opt;
    //    initStyleOption(&opt);
    //    opt.subControls = QStyle::SC_SliderGroove | QStyle::SC_SliderHandle;
    //    QRect handleRect =style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderHandle, this);
        int posFromValue = QStyle::sliderPositionFromValue(mirroredSlider->minimum(), mirroredSlider->maximum(), mirroredSlider->value(), mirroredSlider->width());
        qDebug() << mirroredSlider->objectName() << " value:" << posFromValue;
        if (event->modifiers() == Qt::ControlModifier)
            QMouseEvent *e = new QMouseEvent(event->type(), QPointF(posFromValue + 1, event->y()), event->button(), event->buttons(), Qt::NoModifier);
            //QMouseEvent *e = new QMouseEvent(QEvent::MouseButtonRelease, QPointF(), Qt::MiddleButton, 0, 0);
            //*e = *event;
            qApp->postEvent(mirroredSlider, e);

    I got the effect by posing appropriate event to other QSlider, just without keyboard modifier. Wheel and click (and hold) a mouse button works excellent, but there is an issue with mouse move - it works only when handles of sliders have the same position (case *e = *event, QMouseEvent created with default values, currently commented out). I'm certain that is because there is check in QSlider implementation like if (mouseClikPos() != handlePos) doNothing; , so my solution was to change mouseEvent.pos().x() to the position of the handle of 2nd slider. I calculated it using QSliderPositionFromValue() as you see, then I checked with qDebugs if the result is plausible - it is, though it seems it counts left edge of the handle.
    But this does not work :/
    Looks like operation *e = *event copied other vital data, but I have no idea what I could be missing. Could you help mates?

  • Lifetime Qt Champion


    Why not just connect each slider valueChanged signal to the other setValue slot ?

  • Hey,

    Because each slider can have different value, and what I want to simultaneously increase/decrease it by common step (5). Ex:
    Slider 1 - 20 , max 100
    Slider 2 - 50, max 100
    I started increasing slider 1 using mouse wheel + ctrl modifier, after 1st spin:
    Slider 1 - 25
    Slider 2 - 55
    and so on till slider 2 reaches max, since then only slider 1 keeps increasing.

    I don't insist with mouseevent-based solution, made it only because I though it'll be quick and easy.

  • Lifetime Qt Champion

    Then QSlider::actionTriggered might be what you are looking for.

  • I got to thinking about this last night and it is as SGaist initially recommended, but with a custom slot for each slider. Also, depending upon the complexity of the slot, you may or may not also need a guard sentinel in each slot to keep from recursively calling the slots.

    This is a working python example:

    //your code h# -*- coding: utf-8 -*-
    # Form implementation generated from reading ui file 'Sliders.ui'
    # Created by: PyQt5 UI code generator 5.10.1
    # WARNING! All changes made in this file will be lost!
    from PyQt5 import QtCore, QtGui, QtWidgets
    class Ui_Sliders(object):
        def setupUi(self, Sliders):
            Sliders.resize(259, 203)
            self.verticalLayout = QtWidgets.QVBoxLayout(Sliders)
            self.Slider1 = QtWidgets.QSlider(Sliders)
            self.Slider1.setMinimumSize(QtCore.QSize(241, 16))
            self.Slider2 = QtWidgets.QSlider(Sliders)
            self.Slider2.setMinimumSize(QtCore.QSize(241, 16))
            spacerItem = QtWidgets.QSpacerItem(20, 108, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
            self.buttonBox = QtWidgets.QDialogButtonBox(Sliders)
        def retranslateUi(self, Sliders):
            _translate = QtCore.QCoreApplication.translate
            Sliders.setWindowTitle(_translate("Sliders", "Dialog"))
        def Slider1SetValue(self, _v):
            self.Slider1.setValue(99 - _v)
        def Slider2SetValue(self, _v):
            self.Slider2.setValue(99 - _v)
    # ==============================================================================
    if __name__ == "__main__":
        import sys
        app = QtWidgets.QApplication(sys.argv)
        Sliders = QtWidgets.QDialog()
        ui = Ui_Sliders()

  • Thanks for trying @Kent-Dorfman , but I don't know Python so the example is useless for me.

    ActionTriggered seems to work better:

    void StepSlider::mouseMoveEvent(QMouseEvent *event)
        int valueBefore = value();
        int valueDelta = value() - valueBefore;
        if (event->modifiers() == Qt::ControlModifier && valueDelta != 0)
            mirroredSlider->triggerAction(valueDelta > 0 ? QAbstractSlider::SliderSingleStepAdd : QAbstractSlider::SliderSingleStepSub);
        qDebug() << objectName() << " value delta:" << valueDelta;

    though in some situations mirroredSlider is not incremented/decremented properly - but that may be related to my sliders in the testbed project doesn't have properly set stuff to allow change values only by stepSize = 5

Log in to reply

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