QSlider mouse direct jump

后端 未结 12 1731
爱一瞬间的悲伤
爱一瞬间的悲伤 2020-12-23 16:27

Instead of stepping when the user clicks somewhere on the qslider I want to make the slider jump to that position. How can this be implemented ?

相关标签:
12条回答
  • 2020-12-23 17:16

    The following code is actually a hack, but it works fine without sub-classing QSlider. The only thing you need to do is to connect QSlider valueChanged signal to your container.

    Note1: You must set a pageStep > 0 in your slider

    Note2: It works only for an horizontal, left-to-right slider (you should change the calculation of "sliderPosUnderMouse" to work with vertical orientation or inverted appearance)

    MainWindow::MainWindow(QWidget *parent) :
        QMainWindow(parent),
        ui(new Ui::MainWindow)
    {
    
        // ...
        connect(ui->mySlider, SIGNAL(valueChanged(int)),
                this, SLOT(mySliderValueChanged(int)));
       // ...
    }
    
    
    void MainWindow::mySliderValueChanged(int newPos)
    {
        // Make slider to follow the mouse directly and not by pageStep steps
        Qt::MouseButtons btns = QApplication::mouseButtons();
        QPoint localMousePos = ui->mySlider->mapFromGlobal(QCursor::pos());
        bool clickOnSlider = (btns & Qt::LeftButton) &&
                             (localMousePos.x() >= 0 && localMousePos.y() >= 0 &&
                              localMousePos.x() < ui->mySlider->size().width() &&
                              localMousePos.y() < ui->mySlider->size().height());
        if (clickOnSlider)
        {
            // Attention! The following works only for Horizontal, Left-to-right sliders
            float posRatio = localMousePos.x() / (float )ui->mySlider->size().width();
            int sliderRange = ui->mySlider->maximum() - ui->mySlider->minimum();
            int sliderPosUnderMouse = ui->mySlider->minimum() + sliderRange * posRatio;
            if (sliderPosUnderMouse != newPos)
            {
                ui->mySlider->setValue(sliderPosUnderMouse);
                return;
            }
        }
        // ...
    }
    
    0 讨论(0)
  • 2020-12-23 17:16

    This modification to the JumpSlider above works in PyQt5:

    class JumpSlider(QSlider):
        def _FixPositionToInterval(self,ev):
            """ Function to force the slider position to be on tick locations """
    
            # Get the value from the slider
            Value=QStyle.sliderValueFromPosition(self.minimum(), self.maximum(), ev.x(), self.width())
    
            # Get the desired tick interval from the slider
            TickInterval=self.tickInterval()
    
            # Convert the value to be only at the tick interval locations
            Value=round(Value/TickInterval)*TickInterval
    
            # Set the position of the slider based on the interval position
            self.setValue(Value)
    
        def mousePressEvent(self, ev):
            self._FixPositionToInterval(ev)
    
        def mouseMoveEvent(self, ev):
            self._FixPositionToInterval(ev)
    
    0 讨论(0)
  • 2020-12-23 17:17

    I also met this problem. My solution is shown below.

    slider->installEventFilter(this);
    ---    
    bool MyDialog::eventFilter(QObject *object, QEvent *event)
        {
            if (object == slider && slider->isEnabled())
            {
                if (event->type() == QEvent::MouseButtonPress)
                {
                    auto mevent = static_cast<QMouseEvent *>(event);
                    qreal value = slider->minimum() + (slider->maximum() - slider->minimum()) * mevent->localPos().x() / slider->width();
                    if (mevent->button() == Qt::LeftButton)
                    {
                        slider->setValue(qRound(value));
                    }
                    event->accept();
                    return true;
                }
                if (event->type() == QEvent::MouseMove)
                {
                    auto mevent = static_cast<QMouseEvent *>(event);
                    qreal value = slider->minimum() + (slider->maximum() - slider->minimum()) * mevent->localPos().x() / slider->width();
                    if (mevent->buttons() & Qt::LeftButton)
                    {
                        slider->setValue(qRound(value));
                    }
                    event->accept();
                    return true;
                }
                if (event->type() == QEvent::MouseButtonDblClick)
                {
                    event->accept();
                    return true;
                }
            }
            return QDialog::eventFilter(object, event);
        }
    

    You can also override these event handlers of QSlider.

    • QSlider::mousePressedEvent
    • QSlider::mouseMoveEvent
    • QSlider::mouseDoubleClickEvent
    0 讨论(0)
  • 2020-12-23 17:21

    My final implementation based on surrounding comments:

    class ClickableSlider : public QSlider {
    public:
        ClickableSlider(QWidget *parent = 0) : QSlider(parent) {}
    
    protected:
        void ClickableSlider::mousePressEvent(QMouseEvent *event) {
          QStyleOptionSlider opt;
          initStyleOption(&opt);
          QRect sr = style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderHandle, this);
    
          if (event->button() == Qt::LeftButton &&
              !sr.contains(event->pos())) {
            int newVal;
            if (orientation() == Qt::Vertical) {
               double halfHandleHeight = (0.5 * sr.height()) + 0.5;
               int adaptedPosY = height() - event->y();
               if ( adaptedPosY < halfHandleHeight )
                     adaptedPosY = halfHandleHeight;
               if ( adaptedPosY > height() - halfHandleHeight )
                     adaptedPosY = height() - halfHandleHeight;
               double newHeight = (height() - halfHandleHeight) - halfHandleHeight;
               double normalizedPosition = (adaptedPosY - halfHandleHeight)  / newHeight ;
    
               newVal = minimum() + (maximum()-minimum()) * normalizedPosition;
            } else {
                double halfHandleWidth = (0.5 * sr.width()) + 0.5;
                int adaptedPosX = event->x();
                if ( adaptedPosX < halfHandleWidth )
                      adaptedPosX = halfHandleWidth;
                if ( adaptedPosX > width() - halfHandleWidth )
                      adaptedPosX = width() - halfHandleWidth;
                double newWidth = (width() - halfHandleWidth) - halfHandleWidth;
                double normalizedPosition = (adaptedPosX - halfHandleWidth)  / newWidth ;
    
                newVal = minimum() + ((maximum()-minimum()) * normalizedPosition);
            }
    
            if (invertedAppearance())
                setValue( maximum() - newVal );
            else
                setValue(newVal);
    
            event->accept();
          } else {
            QSlider::mousePressEvent(event);
          }
        }
    };
    
    0 讨论(0)
  • 2020-12-23 17:23

    after having problems with all versions of @spyke @Massimo Callegari and @Ben (slider position wasnt correct for the whole area) I found some Qt Style functionality within QSlider sourcecode: QStyle::SH_Slider_AbsoluteSetButtons.

    You have to create a new QStyle which can be a very annoying, or you use ProxyStyle as shown by user jpn in http://www.qtcentre.org/threads/9208-QSlider-step-customize?p=49035#post49035

    I've added another constructor and fixed a typo, but used the rest of the original source code.

    #include <QProxyStyle>
    
    class MyStyle : public QProxyStyle
    {
    public:
        using QProxyStyle::QProxyStyle;
    
        int styleHint(QStyle::StyleHint hint, const QStyleOption* option = 0, const QWidget* widget = 0, QStyleHintReturn* returnData = 0) const
        {
            if (hint == QStyle::SH_Slider_AbsoluteSetButtons)
                return (Qt::LeftButton | Qt::MidButton | Qt::RightButton);
            return QProxyStyle::styleHint(hint, option, widget, returnData);
        }
    };
    

    now you can set the style of your slider in the sliders constructor (if your slider is derived from QSlider):

    setStyle(new MyStyle(this->style()));
    

    or it should work this way if it is a standard QSlider:

    standardSlider.setStyle(new MyStyle(standardSlider->style()));
    

    so you use the original style of that element, but if the QStyle::SH_Slider_AbsoluteSetButtons "property" is asked you return as you want ;)

    maybe you'll have to destroy these proxystyles on slider deletion, not tested yet.

    0 讨论(0)
  • 2020-12-23 17:23

    The answer of Massimo Callegari is almost right, but the calculation of newVal ignores the slider handle width. This problem comes up when you try to click near the end of the slider.

    The following code fixes this for horizontal sliders

    double halfHandleWidth = (0.5 * sr.width()) + 0.5; // Correct rounding
    int adaptedPosX = event->x();
    if ( adaptedPosX < halfHandleWidth )
            adaptedPosX = halfHandleWidth;
    if ( adaptedPosX > width() - halfHandleWidth )
            adaptedPosX = width() - halfHandleWidth;
    // get new dimensions accounting for slider handle width
    double newWidth = (width() - halfHandleWidth) - halfHandleWidth;
    double normalizedPosition = (adaptedPosX - halfHandleWidth)  / newWidth ;
    
    newVal = minimum() + ((maximum()-minimum()) * normalizedPosition);
    
    0 讨论(0)
提交回复
热议问题