[Qt] 实现Range Slider

因为工作的原因,我们需要在qt中有一种比较友好的方式去让用户为某一个参数选择一个范围。

举个例子,比如像我们想在淘宝上去买一个键盘,我们输入了“键盘”并搜索,搜索出来了各式各样的键盘以后,我们想只看100 - 200元的键盘,我们就可以设置一个价格区间为100 - 200元。

那对于一个应用来说,怎么去让用户设置这个“区间”呢?我们开始用的一种比较简单的办法,就是给用户两个输入框,一个输入框代表最小值,一个输入框代表最大值,两个输入框都输入数据后,就以这个最小值和最大值来作为一个“区间”。但是这个方法有以下缺点:

    1.每次都需要在两个框进行输入,略显繁琐;

    2.对于程序来说,还需要进行输入的有效性判断:是否超过某个范围,是否有除数字以外的非法字符等。

处于上述的考虑,我们放弃了给用户输入框让用户输入的想法,转而考虑使用一个拖动条来给用户进行一个范围的设置。因为对于一个区间来说,其必定由两部构成:最小值和最大值,所以qt自带的slider无法满足我们的需求,因为其只有一个拖动条,只能选定一个值,于是我们只有自己制作一个range slider来使用。

先上结果,制作完成的代码及demo在这里:RangeSlider

最终效果如下图所示:

(红色文字部分不属于RangeSlider,只是添加在图片上用于说明的文字)

如上图所示,我们称左右两个可以拖动的方块为left handle和right handle。

RangeSlider没有继承于Qt原生的slider(原生slider没有提供足够的自由度去实现RangeSlider),而是继承于最基本的widget,上图中的所有内容均在RangeSlider中的paint函数中绘制:

void RangeSlider::paintEvent(QPaintEvent* aEvent)
{
    Q_UNUSED(aEvent);
    QPainter painter(this);

    // Background
    QRectF backgroundRect = QRectF(scLeftRightMargin, (height() - scSliderBarHeight) / 2, width() - scLeftRightMargin * 2, scSliderBarHeight);
    QPen pen(Qt::gray, 0.8);
    painter.setPen(pen);
    painter.setRenderHint(QPainter::Qt4CompatiblePainting);
    QBrush backgroundBrush(QColor(0xD0, 0xD0, 0xD0));
    painter.setBrush(backgroundBrush);
    painter.drawRoundedRect(backgroundRect, 1, 1);

    // First value handle rect
    pen.setColor(Qt::darkGray);
    pen.setWidth(0.5);
    painter.setPen(pen);
    painter.setRenderHint(QPainter::Antialiasing);
    QBrush handleBrush(QColor(0xFA, 0xFA, 0xFA));
    painter.setBrush(handleBrush);
    QRectF leftHandleRect = firstHandleRect();
    painter.drawRoundedRect(leftHandleRect, 2, 2);

    // Second value handle rect
    QRectF rightHandleRect = secondHandleRect();
    painter.drawRoundedRect(rightHandleRect, 2, 2);

    // Handles
    painter.setRenderHint(QPainter::Antialiasing, false);
    QRectF selectedRect(backgroundRect);
    selectedRect.setLeft(leftHandleRect.right() + 0.5);
    selectedRect.setRight(rightHandleRect.left() - 0.5);
    QBrush selectedBrush(mBackgroudColor);
    painter.setBrush(selectedBrush);
    painter.drawRect(selectedRect);
}

整个功能最核心的部分就是设置mouse相关的event,主要是两部分事件,一是鼠标在拖动条的空白处点击时,left handle或right handle需要往鼠标处跳动一截距离,这里目前设置的跳动的距离为1/10最大长度:

二是鼠标按住left handle和right handle拖动时,需要在拖动条上有相应变化:

void RangeSlider::mousePressEvent(QMouseEvent* aEvent)
{
    if(aEvent->buttons() & Qt::LeftButton)
    {
        mSecondHandlePressed = secondHandleRect().contains(aEvent->pos());
        mFirstHandlePressed = !mSecondHandlePressed && firstHandleRect().contains(aEvent->pos());
        if(mFirstHandlePressed)
        {
            mDelta = aEvent->pos().x() - (firstHandleRect().x() + scHandleSideLength / 2);
        }
        else if(mSecondHandlePressed)
        {
            mDelta = aEvent->pos().x() - (secondHandleRect().x() + scHandleSideLength / 2);
        }
        if(aEvent->pos().y() >= 2
           && aEvent->pos().y() <= height()- 2)
        {
            int step = mInterval / 10 < 1 ? 1 : mInterval / 10;
            if(aEvent->pos().x() < firstHandleRect().x())
            {
                setLowerValue(mLowerValue - step);
            }
            else if(aEvent->pos().x() > firstHandleRect().x() + scHandleSideLength
                    && aEvent->pos().x() < secondHandleRect().x())
            {
                if(aEvent->pos().x() - (firstHandleRect().x() + scHandleSideLength) <
                   (secondHandleRect().x() - (firstHandleRect().x() + scHandleSideLength)) / 2)
                {
                    if(mLowerValue + step < mUpperValue)
                    {
                        setLowerValue(mLowerValue + step);
                    }
                    else
                    {
                        setLowerValue(mUpperValue);
                    }
                }
                else
                {
                    if(mUpperValue - step > mLowerValue)
                    {
                        setUpperValue(mUpperValue - step);
                    }
                    else
                    {
                        setUpperValue(mLowerValue);
                    }
                }
            }
            else if(aEvent->pos().x() > secondHandleRect().x() + scHandleSideLength)
            {
                setUpperValue(mUpperValue + step);
            }
        }
    }
}

void RangeSlider::mouseMoveEvent(QMouseEvent* aEvent)
{
    if(aEvent->buttons() & Qt::LeftButton)
    {
        if(mFirstHandlePressed)
        {
            if(aEvent->pos().x() - mDelta + scHandleSideLength / 2 <= secondHandleRect().x())
            {
                setLowerValue((aEvent->pos().x() - mDelta - scLeftRightMargin - scHandleSideLength / 2) * 1.0 / validWidth() * mInterval + mMinimum);
            }
            else
            {
                setLowerValue(mUpperValue);
            }
        }
        else if(mSecondHandlePressed)
        {
            if(firstHandleRect().x() + scHandleSideLength * 1.5 <= aEvent->pos().x() - mDelta)
            {
                setUpperValue((aEvent->pos().x() - mDelta - scLeftRightMargin - scHandleSideLength / 2 - scHandleSideLength) * 1.0 / validWidth() * mInterval + mMinimum);
            }
            else
            {
                setUpperValue(mLowerValue);
            }
        }
    }
}

void RangeSlider::mouseReleaseEvent(QMouseEvent* aEvent)
{
    Q_UNUSED(aEvent);

    mFirstHandlePressed = false;
    mSecondHandlePressed = false;
}

目前想到的最主要的就是这些,后续有什么想到的我会再补充。

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
好的,我来解答你的问题。 首先,你可以使用 PyQt5 中的 QTabWidget 类来创建一个带有选项卡的窗口。然后,你可以将每个选项卡的内容区域设置为一个可滚动的窗口部件,这样就可以实现内容区域可滚动的效果。 接下来,你可以使用 QGridLayout 类创建一个网格布局,并将其添加到每个选项卡的滚动窗口中。然后,你可以使用 QSlider 类创建滑块,将它们添加到网格布局中。 最后,为了实现每页添加 5 个滑块的效果,你可以使用循环来动态地添加滑块到网格布局中。 以下是一个示例代码: ```python from PyQt5.QtWidgets import QApplication, QWidget, QTabWidget, QScrollArea, QGridLayout, QSlider class MainWindow(QWidget): def __init__(self): super().__init__() # 创建一个选项卡窗口 self.tab_widget = QTabWidget(self) self.tab_widget.setGeometry(50, 50, 800, 600) # 创建 3 个选项卡 for i in range(3): # 创建一个滚动窗口部件 scroll_area = QScrollArea() scroll_area.setWidgetResizable(True) # 创建一个网格布局 grid_layout = QGridLayout() grid_layout.setHorizontalSpacing(20) grid_layout.setVerticalSpacing(20) # 往网格布局中添加 5 个滑块 for j in range(5): slider = QSlider() grid_layout.addWidget(slider, j, 0) # 将网格布局添加到滚动窗口中 widget = QWidget() widget.setLayout(grid_layout) scroll_area.setWidget(widget) # 将滚动窗口添加到选项卡中 self.tab_widget.addTab(scroll_area, f"Tab {i+1}") if __name__ == "__main__": app = QApplication([]) window = MainWindow() window.show() app.exec_() ``` 在这个示例中,我们创建了一个名为 MainWindow 的窗口,并在其中使用 QTabWidget 创建了一个带有 3 个选项卡的窗口。然后,我们往每个选项卡的滚动窗口中添加了一个 QGridLayout,并在其中动态添加了 5 个 QSlider。 注意,我们在示例代码中将滑块添加到了第一列,如果你需要添加更多列,只需要在循环中增加列数即可。另外,如果你需要调整滑块之间的间距,可以使用 setHorizontalSpacing 和 setVerticalSpacing 方法来设置水平和垂直间距。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值