在开发项目时,有需求要做双滑块的滑动条,本着能不造轮子就不造轮子的原则,去网上搜了一番,果然有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);
}
最后就完美的解决问题了,给大家看下最后的效果图吧:
代码有时间整理后上传。
原创不易,且行且珍惜: