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)
    {
        setSingleStep(5);
        setPageStep(5);
    
        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;
            e->setModifiers(Qt::NoModifier);
            qApp->postEvent(mirroredSlider, e);
        }
        QSlider::mousePressEvent(event);
    }
    
    void StepSlider::wheelEvent(QWheelEvent *event)
    {
        if (event->modifiers() == Qt::ControlModifier)
        {
            QWheelEvent *e = new QWheelEvent(QPointF(), 0, 0, 0);
            *e = *event;
            e->setModifiers(Qt::NoModifier);
            qApp->postEvent(mirroredSlider, e);
        }
        QSlider::wheelEvent(event);
    }
    
    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;
            e->setModifiers(Qt::NoModifier);
            qApp->postEvent(mirroredSlider, e);
        }
        QSlider::mouseMoveEvent(event);
    }
    

    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

    Hi,

    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.setObjectName("Sliders")
            Sliders.resize(259, 203)
            self.verticalLayout = QtWidgets.QVBoxLayout(Sliders)
            self.verticalLayout.setObjectName("verticalLayout")
            self.Slider1 = QtWidgets.QSlider(Sliders)
            self.Slider1.setMinimumSize(QtCore.QSize(241, 16))
            self.Slider1.setOrientation(QtCore.Qt.Horizontal)
            self.Slider1.setObjectName("Slider1")
            self.verticalLayout.addWidget(self.Slider1)
            self.Slider2 = QtWidgets.QSlider(Sliders)
            self.Slider2.setMinimumSize(QtCore.QSize(241, 16))
            self.Slider2.setOrientation(QtCore.Qt.Horizontal)
            self.Slider2.setObjectName("Slider2")
            self.verticalLayout.addWidget(self.Slider2)
            spacerItem = QtWidgets.QSpacerItem(20, 108, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
            self.verticalLayout.addItem(spacerItem)
            self.buttonBox = QtWidgets.QDialogButtonBox(Sliders)
            self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
            self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok)
            self.buttonBox.setObjectName("buttonBox")
            self.verticalLayout.addWidget(self.buttonBox)
    
            self.retranslateUi(Sliders)
            self.buttonBox.accepted.connect(Sliders.accept)
            self.buttonBox.rejected.connect(Sliders.reject)
            QtCore.QMetaObject.connectSlotsByName(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()
        ui.setupUi(Sliders)
        ui.Slider1.setValue(99)
        ui.Slider2.setValue(0)
        ui.Slider1.valueChanged.connect(ui.Slider2SetValue)
        ui.Slider2.valueChanged.connect(ui.Slider1SetValue)
        Sliders.show()
        sys.exit(app.exec_())
    
    


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

    @SGaist
    ActionTriggered seems to work better:

    void StepSlider::mouseMoveEvent(QMouseEvent *event)
    {
        int valueBefore = value();
        QSlider::mouseMoveEvent(event);
        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.