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

最後就完美的解決問題了,給大家看下最後的效果圖吧:

代碼有時間整理後上傳。

原創不易,且行且珍惜:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章