QSlider鼠标直接跳跃

49

我希望在用户点击qslider的某个位置时,不是步进式移动,而是直接跳到该位置。如何实现?


2
请查看Micka的答案,而不是接受的答案,以获取简单的解决方案。 - Tim MB
2
请注意,滑块的默认行为因平台而异。例如,在MacOS上,滑块会直接跳转到鼠标单击的位置。 - normanius
12个回答

36

在使用 @spyke、@Massimo Callegari 和 @Ben 的所有版本中遇到问题(滑块位置不正确),我在 QSlider 源代码中找到了一些 Qt 样式功能:QStyle::SH_Slider_AbsoluteSetButtons

你需要创建一个新的 QStyle,这可能非常麻烦,或者你可以像用户 jpn 在http://www.qtcentre.org/threads/9208-QSlider-step-customize?p=49035#post49035中所示那样使用ProxyStyle

我已经添加了另一个构造函数并修复了一个拼写错误,但使用了原始源代码的其余部分。

#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”属性,你可以按照自己的方式返回;)

可能需要在滑块删除时销毁这些代理样式,尚未测试。


2
是的,这是正确的答案。 - ens
2
也来表达对这个答案的赞赏。在 macOS 中,QSliders 已经以这种方式运作,因此我认为必须有比子类化 QSlider 更优雅的解决方案来在 Windows 中获得相同的行为。使用 QStyle / QProxyStyle 解决这个问题是有道理的。 - Bri Bri
2
我的编译器不允许我使用QProxyStyle构造函数,因为有些构造函数被隐藏了,所以我需要显式地定义它,就像这样:MyStyle(QStyle* style = nullptr) : QProxyStyle(style){} - Tim MB
4
抱歉,样式表无法与自定义样式一起使用。 - mentalmushroom
1
@mentalmushroom 这个有变化吗?我在6.2版本中成功地使用了代理样式和样式表(两者都应用于应用程序)。 - SleepProgger
显示剩余3条评论

25

好的,我怀疑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);
  }
};

1
对于水平滑块,其值将无法正确计算。我建议使用以下代码:setValue(minimum() + (maximum() - minimum()) * (static_cast<float>(event->x()) / static_cast<float>(width()))); - Alexander Sorokin
谢谢你提供的代码,但在我的情况下,我需要添加“+1”才能使其正常工作:“setValue(minimum() + ((maximum()-minimum()+1) * (height()-event->y())) / height() );” - Alex
检查它的外观是否也被颠倒,如果是的话,从maximum()中减去计算出的值。 - Abderrahmene Rayene Mihoub

20

我也需要这个功能,尝试了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”传递到基类中删除即可。 - Roman Saveljev
这应该是正确的答案,不需要修改即可运行。 - Andrew

15

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

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

3
这是正确的方式!它有效、简单且不受操作系统限制。 - zephyr

15

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个字符的编辑,因此添加了另一个注释。 - Chance
要获取句柄(sr)的宽度,请查看此页面 https://forum.qt.io/topic/36346/solved-qslider-handle-size-after-stylesheet/9 - reddy

6
以下代码实际上是一种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;
        }
    }
    // ...
}

4

基于周围注释的最终实现:

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 的解决方案更短,并且共享内置功能。 - automorphic

3
这种对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

1

我一直在尝试并在网上搜索这个问题,并期望Qt能够以更智能的方式解决,但不幸的是没有什么大的帮助(也许是我没有正确搜索)

好吧,我已经在Qt Creator中完成了此操作:

  1. 在头文件中添加一个事件过滤器(以QObject和QEvent为参数)(返回bool类型)
  2. 在构造函数中进行初始化。例如,如果您的滑块是HSlider,则使用以下代码:ui->HSlider->installEventFilter(this);
  3. 在定义部分:

    a. 检查对象是否为您的滑块类型,例如:ui->HSlider == Object

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

    c. 如果以上所有条件都通过,则表示您已在滑块上获得了鼠标事件,可以在定义部分执行以下操作:ui->HSlider->setValue(Qcursor::pos().x() - firstval); return QMainWindow::eventFilter(object, event);

注意:firstVal:可以通过打印光标位置来获取,0 = 滑块的初始位置(使用QCursor::pos().x()帮助)。

希望这有所帮助。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接