【问题标题】:QSlider mouse direct jumpQSlider鼠标直接跳转
【发布时间】:2012-06-23 08:37:33
【问题描述】:

当用户单击 qslider 上的某个位置时,我不想踩踏,而是想让滑块跳到那个位置。如何实现?

【问题讨论】:

  • 查看下面 Micka 的答案,而不是简单解决方案的公认答案。
  • 请注意,滑块的默认行为是特定于平台的。例如在 MacOS 上,滑块会直接跳到鼠标点击的位置。

标签: qt qslider


【解决方案1】:

对上述 JumpSlider 的修改在 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)

【讨论】:

    【解决方案2】:

    嗯,我怀疑 Qt 是否有用于此目的的直接功能。

    尝试使用自定义小部件。这应该可行!

    试试下面的逻辑

    class MySlider : public QSlider
    {
    
    protected:
      void mousePressEvent ( QMouseEvent * event )
      {
        if (event->button() == Qt::LeftButton)
        {
            if (orientation() == Qt::Vertical)
                setValue(minimum() + ((maximum()-minimum()) * (height()-event->y())) / height() ) ;
            else
                setValue(minimum() + ((maximum()-minimum()) * event->x()) / width() ) ;
    
            event->accept();
        }
        QSlider::mousePressEvent(event);
      }
    };
    

    【讨论】:

    • 无法正确计算水平滑块值。我建议这样的变体:setValue(minimum() + (maximum() - minimum()) * (static_cast<float>(event->x()) / static_cast<float>(width())));
    • 感谢代码,但在我的情况下,我需要添加一个“+1”才能使其工作:“setValue(minimum() + ((maximum()-minimum()+1) * (height()-event->y())) / height() );"
    【解决方案3】:

    我一直在网上尝试和搜索,并期待 Qt 能以更智能的方式做到这一点,不幸的是没有太大帮助(可能是我没有正确搜索)

    我已经在 Qt creator 中做到了:

    1. 在header中添加一个eventFilter(以QObject和QEvent为参数)(bool返回类型)
    2. 在构造函数中初始化..例如..如果你的滑块是 HSlider 然后 ui->HSlider->installEventFilter(this);
    3. 在定义中:

      一个。检查对象是否是您的滑块类型,例如:ui->HSlider == Object

      b.检查鼠标单击事件,例如:QEvent::MouseButtonPress == event->type

      c。如果以上所有通过意味着您在滑块上有鼠标事件,请执行以下操作:在定义中:ui->HSlider->setValue( Qcursor::pos().x() - firstval ); return QMainWindow::eventFilter(object, event);

    注意:fistVal : 可以通过打印光标位置 0 = 滑块的初始位置来取出(在QCursor::pos().x() 的帮助下)

    希望对你有帮助

    【讨论】:

      【解决方案4】:

      我也遇到过这个问题。我的解决方案如下所示。

      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);
          }
      

      您还可以覆盖 QSlider 的这些事件处理程序。

      • QSlider::mousePressedEvent
      • QSlider::mouseMoveEvent
      • QSlider::mouseDoubleClickEvent

      【讨论】:

        【解决方案5】:

        在@spyke @Massimo Callegari 和@Ben 的所有版本出现问题(滑块位置对于整个区域都不正确)后,我在 QSlider 源代码中发现了一些 Qt 样式功能:QStyle::SH_Slider_AbsoluteSetButtons

        您必须创建一个新的 QStyle,这可能很烦人,或者您使用 ProxyStyle,如用户 jpn 在 http://www.qtcentre.org/threads/9208-QSlider-step-customize?p=49035#post49035 中所示

        我添加了另一个构造函数并修正了一个错字,但使用了原始源代码的其余部分。

        #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);
            }
        };
        

        现在您可以在滑块构造函数中设置滑块的样式(如果您的滑块是从 QSlider 派生的):

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

        或者如果它是标准的 QSlider,它应该以这种方式工作:

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

        所以您使用该元素的原始样式,但如果要求 QStyle::SH_Slider_AbsoluteSetButtons“属性”,您可以根据需要返回 ;)

        也许您必须在删除滑块时销毁这些代理样式,尚未测试。

        【讨论】:

        • 是的,这是正确的答案。
        • 也附和欣赏这个答案。 QSlider 已经在 macOS 中以这种方式运行,所以我认为必须有一个比子类化 QSlider 更优雅的解决方案才能在 Windows 中获得相同的行为。这样的解决方案涉及使用 QStyle / QProxyStyle 是有道理的。
        • 我的编译器不允许我使用 QProxyStyle 构造函数,因为有些构造函数是隐藏的,所以我需要像这样显式定义它:MyStyle(QStyle* style = nullptr) : QProxyStyle(style) {}
        • 很遗憾,样式表不适用于自定义样式
        • QWidget::setStyle() 不会转让所有权,所以new MyStyle 会被泄露。
        【解决方案6】:

        我的最终实现基于周围的 cmets:

        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);
              }
            }
        };
        

        【讨论】:

        • 这确实有效,但@Micka 的解决方案更短并且共享内置功能。
        【解决方案7】:

        这是一个在python中使用QStyle.sliderValueFromPosition()的简单实现:

        class JumpSlider(QtGui.QSlider):
        
            def mousePressEvent(self, ev):
                """ Jump to click position """
                self.setValue(QtGui.QStyle.sliderValueFromPosition(self.minimum(), self.maximum(), ev.x(), self.width()))
        
            def mouseMoveEvent(self, ev):
                """ Jump to pointer position while moving """
                self.setValue(QtGui.QStyle.sliderValueFromPosition(self.minimum(), self.maximum(), ev.x(), self.width()))
        

        【讨论】:

        • 这是要走的路!它有效,简单,并且独立于操作系统。
        【解决方案8】:

        Massimo Callegari 的答案几乎是对的,但是 newVal 的计算忽略了滑块手柄宽度。当您尝试单击滑块末端附近时会出现此问题。

        以下代码修复了水平滑块的问题

        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);
        

        【讨论】:

        • 缺少分号,必须编辑 6 个字符,所以添加了另一条评论
        • 要获取手柄的宽度(sr),请查看此页面forum.qt.io/topic/36346/…
        【解决方案9】:

        我认为,

        可以使用 QStyle::sliderValueFromPosition() 函数。

        http://qt-project.org/doc/qt-5/qstyle.html#sliderValueFromPosition

        【讨论】:

          【解决方案10】:

          我也需要这个并尝试了 spyke 解决方案,但它缺少两件事:

          • 倒置的外观
          • 手柄拾取(鼠标在手柄上时,不需要直接跳转)

          所以,这是经过审查的代码:

          void MySlider::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()) == false)
            {
              int newVal;
              if (orientation() == Qt::Vertical)
                 newVal = minimum() + ((maximum()-minimum()) * (height()-event->y())) / height();
              else
                 newVal = minimum() + ((maximum()-minimum()) * event->x()) / width();
          
              if (invertedAppearance() == true)
                  setValue( maximum() - newVal );
              else
                  setValue(newVal);
          
              event->accept();
            }
            QSlider::mousePressEvent(event);
          }
          

          【讨论】:

          • 几乎很棒。如果特殊位置已经完成,我只需要删除将event 传递给基类。
          • 这应该是正确答案,无需修改即可。
          【解决方案11】:

          以下代码实际上是一个 hack,但它可以在没有子类化 QSlider 的情况下正常工作。您唯一需要做的就是将 QSlider valueChanged 信号连接到您的容器。

          注意1:您必须在滑块中设置 pageStep > 0

          注意2:它只适用于水平的,从左到右的滑块(您应该将“sliderPosUnderMouse”的计算更改为垂直方向或倒置外观)

          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;
                  }
              }
              // ...
          }
          

          【讨论】:

            【解决方案12】:

            一种简单的方法是从QSlider 派生并重新实现mousePressEvent(....) 以使用setSliderPosition(int) 设置标记位置。

            【讨论】:

              猜你喜欢
              • 2018-02-28
              • 2019-03-12
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2017-11-13
              相关资源
              最近更新 更多