Qt实现双控制柄的Slider

目标

  1. 实现带有左右两个控制柄的滑动条;
  2. 控件可设定最小值和最大值;
  3. 控件可设定控制柄的最小距离;

效果演示

思路

  1. 标准的Slider控件只有一个Handle,所以想要通过改造QSlider来实现两个Handle是非常困难的,"自绘"是一个更好的实现方案;

  2. 新建DoubleHandleSlider子类继承于QSlider,重写paintEvent函数,在该函数里面使用QPainter绘制出希望的控件样式;

  1. 根据需求,Slider的滑槽有两种样式,分别是①和②,可以先绘制一层①,然后再绘制一层②则可以实现;
  1. handle和tips的实现方法有两种,一种是通过在painterEvent中绘制,另一种是创建四个QLabel分别用作left handle,right handle,left tips 和 right tips,然后实时更新它们的位置;我选择使用QLabel的方式,因为这样可以更好的使用鼠标事件。

  2. handle的移动方式会有两种,一是点击滑槽,二是拖拽handle;

a.第一种通过捕获滑槽的mouse pressed事件得到mouse的x值作为handle的x坐标值,刷新显示;

b. 通过捕获mouse moving事件实时得到mouse的x值作为handle的x值,实时刷新显示;

  1. 因为有两个handle,所以需要设定了以下规则:

a. 在left handle 左侧点击滑槽,设置left handle;

b. 在right handle 右侧点击滑槽,设置right handle;

c. 在left handle 和 right handle 中间点击滑槽,点击的位置更靠近哪个handle则设置哪个handle;

  1. handle值的设置需要满足以下规则:

a. left handle 大于等于最小值;

b. right handle 小于等于最大值;

c. left handle 和right handle的距离值大于等于设定的距离值;

主要细节

1.滑槽的绘制

cpp 复制代码
void DoubleHandleSlider::paintEvent(QPaintEvent *event)
{
    Q_UNUSED(event);
    m_sliderWidth = width()- m_rightHandle->width();

    //创建painter;
    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing, true);

    //创建pen,pen的作用时描边,因为这里的样式是没有边的,所以需要把pen的类型设为NoPen;
    QPen pen;
    pen.setStyle(Qt::PenStyle::NoPen);
    painter.setPen(pen);

    //创建画刷,滑槽是通过画刷绘制的,所以这里要设定好滑槽的颜色;
    QBrush brush(QColor(0x7D, 0x7D, 0x7D));
    painter.setBrush(brush);

    //创建painter 想要绘制的路径,之所以要用这个方法,是因为我们需要画出两端是圆弧形的滑槽;最后两个参数控制了两端圆弧的实现;
    QPainterPath painterPath;
    painterPath.addRoundedRect(0, 28, m_sliderWidth, 3, 3, 3);
    //绘制手柄的两边灰色滑动条;
    painter.drawPath(painterPath);

    //画刷更换颜色,继续画另一个滑槽;
    brush.setColor(QColor(0xFF, 0x55, 0x57));
    painter.setBrush(brush);

    //计算left handle和right handle的位置,同时也是另一个滑槽的两端位置;
    float leftX = m_sliderWidth* (float)(m_leftVal-m_minVal)/m_duration;
    float rightX = m_sliderWidth* (float)(m_rightVal-m_minVal)/m_duration;

    QPainterPath painterPath2;
    painterPath2.addRoundedRect(leftX, 28, rightX- leftX, 3, 3, 3);
    //绘制两手柄中间亮色滑动条;
    painter.drawPath(painterPath2);

    //刷新handle和tips的位置;
    refreshPosition(leftX, rightX);
}

2.handle的移动

cpp 复制代码
bool DoubleHandleSlider::eventFilter(QObject *watched, QEvent *event)
{
    if(watched == m_leftHadle)
    {
        if(event->type() == QEvent::Enter)
        {
            setCursor(QCursor(Qt::OpenHandCursor));
            m_mouseEnterType = InLeftHandle;
        }
        else if(event->type() == QEvent::Leave)
        {
            setCursor(QCursor(Qt::OpenHandCursor));
            m_mouseEnterType = NotInHandle;
        }
    }
    else if(watched == m_rightHandle)
    {
        if(event->type() == QEvent::Enter)
        {
            setCursor(QCursor(Qt::OpenHandCursor));
            m_mouseEnterType = InRightHandle;
        }
        else if(event->type() == QEvent::Leave)
        {
            setCursor(QCursor(Qt::OpenHandCursor));
            m_mouseEnterType = NotInHandle;
        }
    }
    else
    {
        return QWidget::eventFilter(watched, event);
    }

    return false;
}

a. 在eventFilter判断鼠标所在位置的类型,分别有NotInHandle,InLefthandle和InRightHandle三种类型;

b. 这里需要注意的是,在创建handle时,需要调用installEventFilter()才能捕获相应控件的事件;

cpp 复制代码
void DoubleHandleSlider::mousePressEvent(QMouseEvent *event)
{
    //判断鼠标是否在滑动条范围内;
    if(event->pos().y()< m_leftHadle->y() || event->pos().y()> m_leftHadle->y()+ m_leftHadle->height())
        return;

    if(m_mouseEnterType == NotInHandle)
    {
        int handleDuration = m_rightHandle->x()- m_leftHadle->x();

        //鼠标在左手柄的左边;
        if(event->pos().x()< m_leftHadle->x())
            refreshLeftVal(event->pos().x());
        //鼠标在右手柄的右边;
        else if(event->pos().x()> m_rightHandle->x())
            refreshRightVal(event->pos().x());
        //鼠标在两个手柄的中间,但靠近左手柄;
        else if(event->pos().x()< m_leftHadle->pos().x()+ handleDuration/2)
            refreshLeftVal(event->pos().x());
        //鼠标在两个手柄的中间,但更靠近有手柄;
        else if(event->pos().x()>= m_leftHadle->pos().x()+ handleDuration/2)
            refreshRightVal(event->pos().x());

        update();
    }
}

c.再在mousePressedEvent中根据类型处理,当类型是NotInHandle时,根据前面思路5.a和6的规则设置handle;

cpp 复制代码
void DoubleHandleSlider::mouseMoveEvent(QMouseEvent *event)
{
    if(m_mouseEnterType == InLeftHandle)
    {
        refreshLeftVal(event->pos().x());
        update();
    }
    else if(m_mouseEnterType == InRightHandle)
    {
        refreshRightVal(event->pos().x());
        update();
    }
}

void DoubleHandleSlider::refreshLeftVal(float x)
{
    //重新计算左值;
    m_leftVal = m_duration* x/m_sliderWidth+ m_minVal;
//    qDebug()<< m_leftVal;
    if(m_leftVal< m_minVal)
        m_leftVal = m_minVal;
    else if(m_leftVal>= m_rightVal- m_handleIntervalVal)
        m_leftVal = m_rightVal- m_handleIntervalVal;

    emit valueChanged(m_leftVal, m_rightVal);
}

void DoubleHandleSlider::refreshRightVal(float x)
{
    //重新计算右值;
    m_rightVal = m_duration* x/m_sliderWidth+ m_minVal;
//    qDebug()<< m_rightVal;
    if(m_rightVal> m_maxVal)
        m_rightVal = m_maxVal;
    else if(m_rightVal<= m_leftVal+ m_handleIntervalVal)
        m_rightVal = m_leftVal+ m_handleIntervalVal;

    emit valueChanged(m_leftVal, m_rightVal);
}

d. 根据在eventFilter中得到的鼠标所在位置的类型,在mouseMovingEvent中用mouse的x值按思路5.b和6的规则更新handle值;

其它

  1. tips 只需要跟随handle位置变化而变化即可;

  2. 在handle的移动实现中,只是更新handle的值,并没有直接更新handle的位置;handle的位置更新是在paintEvent()调用的,是根据handle的值换算出来位置值,这样就跟滑槽的绘制保持了同步性;

---------------------------【Demo完整代码】-----------------------------------

相关推荐
不午睡的探索者1 小时前
音视频开发入门:FFmpeg vs GStreamer
c++·github·音视频开发
楼田莉子1 小时前
C++算法学习专题:前缀和
c++·学习·算法·leetcode·蓝桥杯
Jooolin1 小时前
【C++】C++11出来之后,到目前为止官方都做了些什么更新?
数据结构·c++·ai编程
长沙红胖子Qt1 小时前
VTK开发笔记(三):熟悉VTK开发流程,编写球体,多半透明球体Demo
c++·qt
Jooolin2 小时前
【C++】C++11都有什么新特性?
c++·ai编程·编程语言
Mr_Xuhhh2 小时前
Qt中UDP回显服务器和客户端
服务器·qt·udp
蓝风破云2 小时前
模拟实现STL中的list容器
c语言·数据结构·c++·链表·迭代器·list·iterator
Quz3 小时前
QML Chart组件之图例
qt
☆璇3 小时前
【C++】C++的IO流
开发语言·c++
HalvmånEver3 小时前
盛最多水的容器:双指针法的巧妙运用(leetcode 11)
c++·学习·leetcode