自定义一个QAbstractScrollArea(一)

本文以一个简单的列表控件为例,展示如何自己实现一个QAbstractScrollArea。此控件由viewport、水平滚动条和垂直滚动条组成。可选的还可以设置一个Corner控件,它在QAbstractScrollArea的右下角。viewport是用来显示内容的地方,是我们最关注的。请注意viewport是显示内容的,不要编码移动它的位置(因为它的位置由QAbstractScrollArea管理),会造成显示问题。一般地,自定义QAbstractScrollArea有以下两种方法:

  1. 直接在viewport上绘制内容,自己控制绘制的水平和垂直偏移量;
  2. 自己实现一个QWidget,在此Widget上绘制内容。将此Widget设置为viewport的子控件。然后根据水平、垂直滚动条的位置移动Widget。

本例采用的是第一种方法。运行环境是VS2017和Qt5.9。列表控件的UI效果如下:

头文件:

class MListWidget : public QAbstractScrollArea
{
    Q_OBJECT

public:
    MListWidget(QWidget* parent = 0);
    MListWidget(const QStringList& strs, QWidget* parent = 0);
    void setItems(const QStringList& strs);
    QStringList items() const;

private slots:
    void verticalScrollBarValueChanged(int value);

private:
    bool viewportEvent(QEvent *event) override;

private:
    int hoverRow;
    int offsety;
    QStringList texts;
};

CPP文件:

MListWidget::MListWidget(QWidget* parent) : 
    QAbstractScrollArea(parent)
{
    hoverRow = -1;
    offsety = 0;
    QScrollBar* vbar = verticalScrollBar();
    connect(vbar, &QScrollBar::valueChanged, this, &MListWidget::verticalScrollBarValueChanged);
    viewport()->setMouseTracking(true);
}

MListWidget::MListWidget(const QStringList& strs, QWidget* parent) :
    QAbstractScrollArea(parent)
{    
    hoverRow = -1;
    offsety = 0;
    QScrollBar* vbar = verticalScrollBar();
    connect(vbar, &QScrollBar::valueChanged, this, &MListWidget::verticalScrollBarValueChanged);
    viewport()->setMouseTracking(true);
    setItems(strs);
}

void MListWidget::setItems(const QStringList& strs)
{
    texts = strs;
    viewport()->update();
}

QStringList MListWidget::items() const
{
    return texts;
}

void MListWidget::verticalScrollBarValueChanged(int value)
{
    offsety = -value;
    viewport()->update();
}

bool MListWidget::viewportEvent(QEvent *event)
{
    if (event->type() == QEvent::Resize)
    {
        QResizeEvent* rszEvent = dynamic_cast<QResizeEvent*>(event);
        int desiredHeight = 24 * texts.size();
        QScrollBar* vbar = verticalScrollBar();
        vbar->setMinimum(0);
        vbar->setMaximum(std::max(0, desiredHeight - rszEvent->size().height()));
        vbar->setPageStep(rszEvent->size().height());
    }
    else if (event->type() == QEvent::Paint)
    {
        QPainter painter(viewport());
        painter.fillRect(rect(), Qt::white);
        painter.setPen(Qt::black);
        int count = texts.size();
        for (int i = 0; i < count; i++)
        {
            QRect rect(0, i * 24 + offsety, viewport()->width(), 24);
            if (hoverRow == i)
            {
                painter.fillRect(rect, QColor(197, 214, 243)); /* 鼠标悬停的行 */
            }
            painter.drawText(rect, Qt::AlignVCenter, texts[i]);
        }
    }
    else if (event->type() == QEvent::MouseMove)
    {
        QMouseEvent* mouse = dynamic_cast<QMouseEvent*>(event);
        hoverRow = (mouse->pos().y() - offsety) / 24;
        viewport()->update();
    }
    else if (event->type() == QEvent::Enter)
    {
        // nothing.
    }
    else if (event->type() == QEvent::Leave)
    {
        hoverRow = -1;
        viewport()->update();
    }
    else if (event->type() == QEvent::Wheel)
    {
        QScrollBar *vbar = verticalScrollBar();
        QApplication::sendEvent(vbar, event);
    }
    return false;
}

请特别注意viewportEvent(...)这个函数。QAbstractScrollArea默认会通过此函数拦截viewport的所有事件。如果你使用了自己的viewport替换默认的viewport,那么务必要重写此事件函数,否则会造成viewport接收不到任何事件。此注意事项在Qt帮助中有说明。

posted @ 2024-02-19 08:28  兜尼完  阅读(128)  评论(0编辑  收藏  举报