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