自定义一个QAbstractScrollArea(二)

延续上一篇文章,本文以另一种方法实现QAbstractScrollArea。此例子是一个图片缩略图的列表控件,控件沿水平方向延伸。仅作为一个例子,因为实际的图片列表可以用QListWidget实现,并不需要自己写代码。下面是程序的运行截图:

头文件:

class MListWidget : public QAbstractScrollArea
{
    Q_OBJECT

public:
    MListWidget(QWidget* parent = 0);
    void append(const QPixmap& pixmap, const QString& name);
    int currentItem() const;

private:
    bool eventFilter(QObject* src, QEvent* event) override;
    bool viewportEvent(QEvent* event) override;
    void sizeChanged(const QSize &contentSize);

private slots:
    void verticalScrollBarValueChanged(int value);
    void horizontalScrollBarValueChanged(int value);

private:
    class MImageList;
    MImageList* lists;
};

class MListWidget::MImageList : public QWidget
{
    Q_OBJECT

public:
    MImageList(QWidget* parent = 0);
    void append(const QPixmap& pixmap, const QString& name);
    int currentItem() const;

private:
    void paintEvent(QPaintEvent* event) override;
    void mouseMoveEvent(QMouseEvent* event) override;
    void mousePressEvent(QMouseEvent* event) override;
    void mouseReleaseEvent(QMouseEvent* event) override;
    void enterEvent(QEvent* event) override;
    void leaveEvent(QEvent* event) override;

private:
    static const QSize imageSize;
    static const QSize textSize;
    static const int itemMargin;
    QVector<QPixmap> images;
    QStringList texts;
    QPoint pressPt;
    int hoverItem;
    int clickItem;
};

CPP文件。代码约200行太长我把它折叠了:

MListWidget::MListWidget(QWidget* parent) : 
    QAbstractScrollArea(parent)
{
    QScrollBar* vbar = verticalScrollBar();
    connect(vbar, &QScrollBar::valueChanged, this, &MListWidget::verticalScrollBarValueChanged);
    QScrollBar* hbar = horizontalScrollBar();
    connect(hbar, &QScrollBar::valueChanged, this, &MListWidget::horizontalScrollBarValueChanged);

    lists = new MImageList(viewport());
    lists->installEventFilter(this);
}

int MListWidget::currentItem() const
{
    return lists->currentItem();
}

bool MListWidget::eventFilter(QObject* src, QEvent* event)
{
    if (src == lists && event->type() == QEvent::Resize)
    {
        /* 内容Widget的大小变化 */
        QResizeEvent* rszEvent = dynamic_cast<QResizeEvent*>(event);
        sizeChanged(rszEvent->size());
    }
    return QAbstractScrollArea::eventFilter(src, event);
}

bool MListWidget::viewportEvent(QEvent* event)
{
    if (event->type() == QEvent::Resize)
    {
        /* viewport的大小变化 */
        sizeChanged(lists->size());
    }
    return QAbstractScrollArea::viewportEvent(event);
}

void MListWidget::append(const QPixmap& pixmap, const QString& name)
{
    lists->append(pixmap, name);
}

void MListWidget::verticalScrollBarValueChanged(int value)
{
    lists->move(lists->x(), -value);
}

void MListWidget::horizontalScrollBarValueChanged(int value)
{
    lists->move(-value, lists->y());
}

void MListWidget::sizeChanged(const QSize &contentSize)
{
    QScrollBar* vbar = verticalScrollBar();
    vbar->setMinimum(0);
    vbar->setMaximum(std::max(0, contentSize.height() - viewport()->height()));
    vbar->setPageStep(viewport()->height());
    QScrollBar* hbar = horizontalScrollBar();
    hbar->setMinimum(0);
    hbar->setMaximum(std::max(0, contentSize.width() - viewport()->width()));
    hbar->setPageStep(viewport()->width());
}

/////////////////////////////////////////////////////////////////////////////////////////

const QSize MListWidget::MImageList::imageSize(100, 80);
const QSize MListWidget::MImageList::textSize(100, 22);
const int MListWidget::MImageList::itemMargin = 6;

MListWidget::MImageList::MImageList(QWidget* parent) :
    QWidget(parent), pressPt(0xDEADBEEF, 0xDEADBEEF)
{
    setMouseTracking(true);
    hoverItem = -1;
    clickItem = -1;
}

void MListWidget::MImageList::append(const QPixmap& pixmap, const QString& name)
{
    images.push_back(pixmap);
    texts.push_back(name);
    int count = images.size();
    int w = count * (imageSize.width() + 2 * itemMargin);
    int h = imageSize.height() + textSize.height() + itemMargin;
    setFixedSize(w, h);
    update();
}

void MListWidget::MImageList::paintEvent(QPaintEvent* event)
{
    QPainter painter(this);
    for (int i = 0; i < images.size(); i++)
    {
        int x = i * (imageSize.width() + 2 * itemMargin) + itemMargin;
        int y = itemMargin;
        painter.drawPixmap(QRect(QPoint(x, y), imageSize), images[i]);
        y += imageSize.height();
        painter.drawText(QRect(QPoint(x, y), textSize), Qt::AlignCenter, texts[i]);
        if (clickItem == i)
        {
            int itemx = x - itemMargin;
            int itemy = 0;
            int itemw = imageSize.width() + 2 * itemMargin - 1;
            int itemh = imageSize.height() + textSize.height() + itemMargin - 1;
            painter.save();
            painter.setPen(QColor(97, 142, 229, 97));
            painter.setBrush(QColor(97, 172, 249, 97));
            painter.drawRect(QRect(itemx, itemy, itemw, itemh));
            painter.restore();
        }
        else if (hoverItem == i)
        {
            int itemx = x - itemMargin;
            int itemy = 0;
            int itemw = imageSize.width() + 2 * itemMargin - 1;
            int itemh = imageSize.height() + textSize.height() + itemMargin - 1;
            painter.save();
            painter.setPen(QColor(97, 142, 229, 63));
            painter.setBrush(QColor(97, 172, 249, 63));
            painter.drawRect(QRect(itemx, itemy, itemw, itemh));
            painter.restore();
        }
    }
}

void MListWidget::MImageList::mouseMoveEvent(QMouseEvent* event)
{
    int itemw = imageSize.width() + 2 * itemMargin;
    int itemh = imageSize.height() + textSize.height();
    if (event->pos().y() < itemh)
    {
        hoverItem = event->pos().x() / itemw;
        update();
    }
}

void MListWidget::MImageList::mousePressEvent(QMouseEvent* event)
{
    if (hoverItem >= 0)
    {
        pressPt = event->pos();
        update();
    }
}

void MListWidget::MImageList::mouseReleaseEvent(QMouseEvent* event)
{
    if ((event->pos() - pressPt).manhattanLength() <= 2)
    {
        pressPt = QPoint(0xDEADBEEF, 0xDEADBEEF);
        clickItem = hoverItem;
        update();
    }
}

void MListWidget::MImageList::enterEvent(QEvent* event)
{
    // do nothing.
}

void MListWidget::MImageList::leaveEvent(QEvent* event)
{
    if (hoverItem >= 0)
    {
        hoverItem = -1;
        update();
    }
}

int MListWidget::MImageList::currentItem() const
{
    return clickItem;
}
View Code

在主窗口添加几个项即可。下方代码中,QtTest是主窗口类,ui.ivSelect是MListWidget类:

QtTest::QtTest(QWidget *parent)
    : QMainWindow(parent)
{
    ui.setupUi(this);

    QPixmap myImage(200, 200);
    myImage.fill(Qt::green);
    ui.ivSelect->append(myImage, u8"图像1");
    ui.ivSelect->append(myImage, u8"图像2");
    ui.ivSelect->append(myImage, u8"图像3");
    ui.ivSelect->append(myImage, u8"图像4");
    ui.ivSelect->append(myImage, u8"图像5");
    ui.ivSelect->append(myImage, u8"图像6");
    ui.ivSelect->append(myImage, u8"图像7");
    ui.ivSelect->append(myImage, u8"图像8");
    ui.ivSelect->append(myImage, u8"图像9");
    ui.ivSelect->append(myImage, u8"图像A");
}

:经过查找Qt帮助文档。我发现上方的两个处理滚动条值改变的槽函数是多余的。Qt默认已经响应了滚动条的事件,封装成虚函数QAbstractScrollArea::scrollContentsBy(...)。只需要重写这个虚函数,根据此函数传入的水平垂直偏移增量计算新的显示内容就行了。不用手动连接滚动条的信号。

posted @ 2024-02-24 16:15  兜尼完  阅读(44)  评论(0编辑  收藏  举报