自定义的Qt数值区间选择控件
做一个控件,可以选择数值区间范围。效果如下图,是图中带有红色滑块的控件:
参考下面的代码,可知它公开的函数不多,使用简单。设置滑动条最大范围,使用setLimit函数;设置当前控件的值区间,使用setValue函数。监控控件值的变化,连接valueChanged信号。该类在VS2015和Qt5.9上测试可用(并没有经过详细严格的测试)。其它东西不多描述。直接上代码,头文件:
struct Range { int start; int end; }; class MRangeSlider : public QWidget { Q_OBJECT public: MRangeSlider(QWidget* parent = 0); void setLimit(const Range& limit); void setValue(const Range& value); Range value() const; signals: void valueChanged(const Range& value); private: void resizeEvent(QResizeEvent *event) override; void paintEvent(QPaintEvent *event) override; void mousePressEvent(QMouseEvent *event) override; void mouseReleaseEvent(QMouseEvent *event) override; void mouseMoveEvent(QMouseEvent *event) override; QRect toScreenRect(const Range& range, int value); int toValue(const Range& range, const QRect& screen); void updateToolTip(); void moveRect(int delta, int minx, int maxx, QRect& rect); int leftLimit() const; int rightLimit() const; enum HitButton { NONE, HIT_MIN, HIT_MAX, }; private: const static int radius; const static int lineWidth; int hitTest; QPoint lastPos; Range ivalue; Range limitRange; QRect minr; QRect maxr; };
CPP文件:
inline bool operator==(const Range& a, const Range& b) { return a.start == b.start && a.end == b.end; } inline bool operator!=(const Range& a, const Range& b) { return !(a == b); } ///////////////////////////////////////////////////////////////////////////////////////// const int MRangeSlider::radius = 6; const int MRangeSlider::lineWidth = 5; MRangeSlider::MRangeSlider(QWidget* parent) : QWidget(parent), hitTest(NONE) { limitRange = { 0, 0 }; ivalue = { 0, 0 }; } inline int MRangeSlider::leftLimit() const { return radius; } inline int MRangeSlider::rightLimit() const { /* 控件右侧多减2像素,要不然绘制圆形时会出界 */ return width() - radius - 2; } #define LIMIT(x, a, b) ((x) < (a) ? (a) : ((x) > (b) ? (b) : (x))) void MRangeSlider::setLimit(const Range& limit) { Q_ASSERT(limit.end >= limit.start); limitRange = limit; Range limited; limited.start = LIMIT(ivalue.start, limitRange.start, limitRange.end); limited.end = LIMIT(ivalue.end, limitRange.start, limitRange.end); minr = toScreenRect(limitRange, limited.start); maxr = toScreenRect(limitRange, limited.end); if (limited != ivalue) { ivalue = limited; updateToolTip(); emit valueChanged(ivalue); } update(); } void MRangeSlider::setValue(const Range& value) { Q_ASSERT(value.end >= value.start); Range limited; limited.start = LIMIT(value.start, limitRange.start, limitRange.end); limited.end = LIMIT(value.end, limitRange.start, limitRange.end); if (limited != ivalue) { minr = toScreenRect(limitRange, limited.start); maxr = toScreenRect(limitRange, limited.end); ivalue = limited; updateToolTip(); emit valueChanged(ivalue); update(); } } #undef LIMIT Range MRangeSlider::value() const { return ivalue; } void MRangeSlider::updateToolTip() { setToolTip(QString(u8"{%1,%2}"). arg(ivalue. start).arg(ivalue. end)); } QRect MRangeSlider::toScreenRect(const Range& range, int value) { int ctll = leftLimit(); int ctlr = rightLimit(); int pos = ctll; if (range.end > range.start) { pos += (ctlr - ctll) * (value - range.start) / (range.end - range.start); } QRect rect(0, 0, 2 * radius, 2 * radius); /* 用moveCenter()主要为确保矩形中心在pos上 */ rect.moveCenter(QPoint(pos, height() / 2 - 1)); return rect; } int MRangeSlider::toValue(const Range& range, const QRect& screen) { int ctll = leftLimit(); int ctlr = rightLimit(); int x = range.start; if (ctlr > ctll) { x += (range.end - range.start) * (screen.center().x() - ctll) / (ctlr - ctll); } return x; } void MRangeSlider::moveRect(int delta, int minx, int maxx, QRect& rect) { int cx = rect.center().x(); if (delta < minx - cx) { delta = minx - cx; } else if (delta > maxx - cx) { delta = maxx - cx; } rect.adjust(delta, 0, delta, 0); } void MRangeSlider::resizeEvent(QResizeEvent *) { minr = toScreenRect(limitRange, ivalue.start); maxr = toScreenRect(limitRange, ivalue.end); } void MRangeSlider::paintEvent(QPaintEvent *) { QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing); QLine bkLine(radius, height() / 2, width() - radius, height() / 2); painter.setPen(QPen(QColor(173, 173, 173), lineWidth, Qt::SolidLine, Qt::RoundCap)); painter.drawLine(bkLine); QLine valueLine(minr.center().x(), height() / 2, maxr.center().x(), height() / 2); painter.setPen(QPen(QColor(243, 173, 173), lineWidth, Qt::SolidLine, Qt::RoundCap)); painter.drawLine(valueLine); painter.setPen(QColor(173, 63, 63)); painter.setBrush(QColor(243, 32, 32)); painter.drawEllipse(minr); painter.drawEllipse(maxr); } void MRangeSlider::mousePressEvent(QMouseEvent *event) { QPoint p = event->pos(); lastPos = p; if (minr.contains(p)) { hitTest |= int(HIT_MIN); } if (maxr.contains(p)) { hitTest |= int(HIT_MAX); } if (hitTest == (HIT_MIN | HIT_MAX)) { int maxx = maxr.center().x(); int minx = minr.center().x(); hitTest = (maxx + minx > width() ? HIT_MIN : HIT_MAX); } } void MRangeSlider::mouseReleaseEvent(QMouseEvent *) { if (hitTest != NONE) { hitTest = NONE; emit valueChanged(ivalue); } } void MRangeSlider::mouseMoveEvent(QMouseEvent *event) { int delta = event->pos().x() - lastPos.x(); switch (hitTest) { case HIT_MIN: moveRect(delta, leftLimit(), maxr.center().x(), minr); ivalue.start = toValue(limitRange, minr); updateToolTip(); lastPos = event->pos(); update(); break; case HIT_MAX: moveRect(delta, minr.center().x(), rightLimit(), maxr); ivalue.end = toValue(limitRange, maxr); updateToolTip(); lastPos = event->pos(); update(); break; default: /* nothing to do. */ break; } }