因为工作的原因,我们需要在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;
}
目前想到的最主要的就是这些,后续有什么想到的我会再补充。