自定义的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;
    }
}

 

posted @ 2022-11-08 19:41  兜尼完  阅读(405)  评论(0编辑  收藏  举报