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? -
Hi,
Why not just connect each slider
valueChanged
signal to the othersetValue
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.
-
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