Qt 双滑块QSlider的实现

在开发项目时,有需求要做双滑块的滑动条,本着能不造轮子就不造轮子的原则,去网上搜了一番,果然有Qt开源的拓展库,Qt Extension Library,有一个控件是QxtSpanSlider,实现了简单的双滑块,然而自定义了一些样式后,就各种问题。

于是又搜索相关的解决办法,有搜到一位博主的博客,url:https://blog.csdn.net/Ilson_/article/details/103960278,按照博主的方法,没有起左右,于是又花了C币去下载,还是不起作用。无奈之下,又不想自己写这个控件(我不会啊...),于是在源码基础上进行了一些猥琐的(聪明的)封装,最终实现了目的。

问题:

1、拖动一个滑块,另一个滑块轴的位置被span覆盖,而且两侧的颜色不特殊处理的话,颜色不对。sub-page和add-page要处理;

2、范围span的颜色控制不了,即使按照上面博主的修改方法也不行,当两侧的颜色有一定透明度时,也还是会把中间的span颜色蒙一层,UI走查的时候一眼就可以看出来,然后就该被深深低鄙视了,不就是改个颜色的事儿吗...

3、没有处理鼠标mousePressEvent,那么鼠标在某个位置按下的时候,滑块就不能定位到指定位置;

解决思路:

1、设置sub-page和add-page颜色一致,举例子:

QSlider#TwoSlider::sub-page:horizontal 
{
	background: rgba(0,0,0,0.1);
	height: 8px;
	border-radius: 4px;
}

QSlider#TwoSlider::add-page:horizontal 
{
	background: rgba(0,0,0,0.1);
	height: 8px;
	border-radius: 4px;
}

2、代码里设置颜色(没成功...),具体修改位置如下:

void QxtSpanSliderPrivate::drawSpan(QStylePainter* painter, const QRect& rect) const
{
    QStyleOptionSlider opt;
    initStyleOption(&opt);
    const QSlider* p = q_ptr;

    // area
    QRect groove = p->style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderGroove, p);
    if (opt.orientation == Qt::Horizontal)
        groove.adjust(0, 0, -1, 0);
    else
        groove.adjust(0, 0, 0, -1);

    // pen & brush
    /*painter->setPen(QPen(p->palette().color(QPalette::Dark).light(110), 0));
    if (opt.orientation == Qt::Horizontal)
        setupPainter(painter, opt.orientation, groove.center().x(), groove.top(), groove.center().x(), groove.bottom());
    else
        setupPainter(painter, opt.orientation, groove.left(), groove.center().y(), groove.right(), groove.center().y());*/

	// 相交得到的矩形宽度、高度少了1pixel,需要加上
	QRect rt = rect.intersected(groove);
	rt.adjust(0, -1, 1, 3);

	// 调用自写函数修改样式
	setupPainter(painter, opt.orientation, rt);

    // draw groove
    painter->drawRect(rt);
}

自己重载一个setupPainter函数

void QxtSpanSliderPrivate::setupPainter(QPainter* painter, Qt::Orientation orientation,QRect& rect) const
{
	painter->setBrush(QBrush(QColor(255, 96, 0)));
	painter->setPen(Qt::transparent);
}

这里面的颜色就是我想要的颜色。然而我这边的效果是被sub-page和add-page刷新覆盖了一层,如果sub-page和add-page是不带透明度的纯色,中间设置的就完全不起作用了。

而且,拖动一个滑块,另一个滑块坐标轴的位置就会被span覆盖掉,如果是同色还好,不同色的话就废了。

最后我想了一个猥琐的办法,增加三个单独的QWidget,覆盖在滑动条上,即两个滑块和中间span控件,并设置这个三个控件的显示不影响下面滑块控件的使用。 在滑动条刷新的时候,这三个控件也去更新位置和大小,效果还是相当完美的。

具体操作:

在滑块paintEvent里添加信号,方便我们封装的控件处理添加的三个控件

void QxtSpanSlider::paintEvent(QPaintEvent* event)
{
    Q_UNUSED(event);
    QStylePainter painter(this);

    // groove & ticks
    QStyleOptionSlider opt;
    d_ptr->initStyleOption(&opt);
    opt.sliderValue = 0;
    opt.sliderPosition = 0;
    opt.subControls = QStyle::SC_SliderGroove | QStyle::SC_SliderTickmarks;
    painter.drawComplexControl(QStyle::CC_Slider, opt);

    // handle rects
    opt.sliderPosition = d_ptr->lowerPos;
    const QRect lr = style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderHandle, this);
    const int lrv  = d_ptr->pick(lr.center());
    opt.sliderPosition = d_ptr->upperPos;
    const QRect ur = style()->subControlRect(QStyle::CC_Slider, &opt, QStyle::SC_SliderHandle, this);
    const int urv  = d_ptr->pick(ur.center());
	emit updateHandle(lr,ur);
...
...

最后的信号即是我添加的,方便知道两个滑块的具体位置,然后在我封装的控件里,进行如下处理:

connect(m_Slider, &QxtSpanSlider::updateHandle, this, [this](const QRect &lRect, const QRect &uRect)
	{
		m_Span->move(lRect.x() + m_lWgdt->width(), 10);
		m_Span->setFixedWidth(uRect.x() - lRect.x()-m_lWgdt->width());
		m_Span->raise();
		for (auto &time : m_ValueVector)
		{
			time.circleWidget->raise();
		}

		m_lWgdt->move(lRect.x(), lRect.y());
		m_uWgdt->move(uRect.x(), uRect.y());
		m_lWgdt->raise();
		m_uWgdt->raise();
	});

time.circleWidget无需理会,是我实现更复杂的功能需要显示和处理的。这样就解决了最头疼的第二个问题;

3、鼠标点击事件,这个就相对好处理多了。不过你得事先跟产品经理通个气,看看点击某个位置,两个滑块怎么处理,就比如在中间点一下,是小值变大,还是大值变小,blabla...

我最终实现的代码:

bool DoubleSlider::eventFilter(QObject *obj, QEvent *event)
{
	if (obj == m_Slider)
	{
		if (event->type() == QEvent::MouseButtonPress)
		{
			QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);
			if (mouseEvent->button() == Qt::LeftButton)
			{
				double pos = mouseEvent->pos().x() / (double)m_Slider->width();
				int value = (pos * (m_Slider->maximum() - m_Slider->minimum()) + m_Slider->minimum()) + 0.5;
				int lowerValue = m_Slider->lowerValue();
				int upperValue = m_Slider->upperValue();
				if (value < upperValue)
				{
					m_Slider->setLowerValue(value);
				}
				else if (value > upperValue)
				{
					m_Slider->setUpperValue(value);
				}
			}
		}
	}
	return QObject::eventFilter(obj, event);
}

最后就完美的解决问题了,给大家看下最后的效果图吧:

代码有时间整理后上传。

原创不易,且行且珍惜:

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值