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

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

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ThisIsClark

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值