QT 自定义视图 ,继承 QAbstractItemView

QT 自定义视图 ,继承 QAbstractItemView

例子位置:Examples\Qt-xxx\widgets\itemviews\chart
QT 自定义视图 ,继承 QAbstractItemView_第1张图片

pieview.h

#ifndef PIEVIEW_H
#define PIEVIEW_H

#include 

//! [0]
class PieView : public QAbstractItemView
{
    Q_OBJECT

public:
    PieView(QWidget *parent = nullptr);

    //---------------------- 纯虚函数 ----------------------
    /*!
     * \brief 返回索引项在视图坐标系中的矩形
     * \param index
     * \return
     */
    QRect visualRect(const QModelIndex &index) const override;

    /*!
     * \brief 如有必要,可滚动视图以确保索引处的项可见
     * \param index
     * \param hint
     */
    void scrollTo(const QModelIndex &index, ScrollHint hint = EnsureVisible) override;

    /*!
     * \brief 返回项目在视图坐标点的模型索引
     * \param point
     * \return
     */
    QModelIndex indexAt(const QPoint &point) const override;

protected slots:
    /*!
     * \brief 数据改变,刷新视图
     * \param topLeft
     * \param bottomRight
     * \param roles
     */
    void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight,
                     const QVector<int> &roles = QVector<int>()) override;
    /*!
     * \brief 插入行时调用
     * \param parent
     * \param start
     * \param end
     */
    void rowsInserted(const QModelIndex &parent, int start, int end) override;

    /*!
     * \brief 准备删除行时被调用
     * \param parent
     * \param start
     * \param end
     */
    void rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) override;

protected:
    bool edit(const QModelIndex &index, EditTrigger trigger, QEvent *event) override;

    /*!
     * \brief 根据光标动作或者按键返回模型索引,提供导航功能
     * \param cursorAction
     * \param modifiers
     * \return
     */
    QModelIndex moveCursor(QAbstractItemView::CursorAction cursorAction,
                           Qt::KeyboardModifiers modifiers) override;

    /// 视图水平偏移
    int horizontalOffset() const override;
    /// 视图垂直偏移
    int verticalOffset() const override;
    /// 隐藏索引
    bool isIndexHidden(const QModelIndex &index) const override;

    /*!
     * \brief 按照SelectionFlags更新选中区域
     * \param rect  视图选中矩形
     * \param command 选中标志
     */
    void setSelection(const QRect&rect, QItemSelectionModel::SelectionFlags command) override;

    void mousePressEvent(QMouseEvent *event) override;
    void mouseMoveEvent(QMouseEvent *event) override;
    void mouseReleaseEvent(QMouseEvent *event) override;

    void paintEvent(QPaintEvent *event) override;
    void resizeEvent(QResizeEvent *event) override;
    void scrollContentsBy(int dx, int dy) override;

    /*!
     * \brief 选中的视图区域
     * \param selection
     * \return
     */
    QRegion visualRegionForSelection(const QItemSelection &selection) const override;

private:
    QRect itemRect(const QModelIndex &item) const;
    QRegion itemRegion(const QModelIndex &index) const;
    int rows(const QModelIndex &index = QModelIndex()) const;
    void updateGeometries() override;

    ///边框
    int margin = 0;
    ///饼图总像素
    int totalSize = 300;
    ///饼图大小
    int pieSize = totalSize - 2 * margin;
    ///有效项
    int validItems = 0;
    ///饼图单元数值统计
    double totalValue = 0.0;
    ///选中的矩形边界
    QRubberBand *rubberBand = nullptr;
    ///当前点
    QPoint origin;
};
//! [0]

#endif // PIEVIEW_H

pieview.cpp

#include "pieview.h"

#include 

PieView::PieView(QWidget *parent)
    : QAbstractItemView(parent)
{
    horizontalScrollBar()->setRange(0, 0);
    verticalScrollBar()->setRange(0, 0);
}

void PieView::dataChanged(const QModelIndex &topLeft,
                          const QModelIndex &bottomRight,
                          const QVector<int> &roles)
{
    QAbstractItemView::dataChanged(topLeft, bottomRight, roles);

    if (!roles.contains(Qt::DisplayRole))
        return;

    validItems = 0;
    totalValue = 0.0;

    for (int row = 0; row < model()->rowCount(rootIndex()); ++row) {

        QModelIndex index = model()->index(row, 1, rootIndex());
        double value = model()->data(index, Qt::DisplayRole).toDouble();

        if (value > 0.0) {
            totalValue += value;
            validItems++;
        }
    }
    viewport()->update();
}

bool PieView::edit(const QModelIndex &index, EditTrigger trigger, QEvent *event)
{
    if (index.column() == 0)
        return QAbstractItemView::edit(index, trigger, event);
    else
        return false;
}

/*
    Returns the item that covers the coordinate given in the view.
*/

QModelIndex PieView::indexAt(const QPoint &point) const
{
    if (validItems == 0)
        return QModelIndex();

    // Transform the view coordinates into contents widget coordinates.
    //转换视图坐标到窗体坐标
    int wx = point.x() + horizontalScrollBar()->value();
    int wy = point.y() + verticalScrollBar()->value();

    if (wx < totalSize) {//窗体坐标在饼图部分
        //圆心
        double cx = wx - totalSize / 2;
        double cy = totalSize / 2 - wy; // positive cy for items above the center

        // Determine the distance from the center point of the pie chart.
        double d = std::sqrt(std::pow(cx, 2) + std::pow(cy, 2));
        //圆心或者圆外部
        if (d == 0 || d > pieSize / 2)
            return QModelIndex();

        // Determine the angle of the point.
        //当前点的角度
        double angle = qRadiansToDegrees(std::atan2(cy, cx));
        if (angle < 0)
            angle = 360 + angle;

        // Find the relevant slice of the pie.
        // 查找当前点对应饼图的模型索引
        double startAngle = 0.0;
        for (int row = 0; row < model()->rowCount(rootIndex()); ++row) {

            QModelIndex index = model()->index(row, 1, rootIndex());
            double value = model()->data(index).toDouble();

            if (value > 0.0) {
                //饼图角度范围
                double sliceAngle = 360 * value / totalValue;
                //找到当前点角度在饼图角度范围内,返回当前饼图模型索引
                if (angle >= startAngle && angle < (startAngle + sliceAngle))
                    return model()->index(row, 1, rootIndex());

                startAngle += sliceAngle;
            }
        }
    } else {//窗体坐标在右边列表部分
        //视图字体高度
        double itemHeight = QFontMetrics(viewOptions().font).height();
        //当前y坐标对应的列表项
        int listItem = int((wy - margin) / itemHeight);

        int validRow = 0;
        for (int row = 0; row < model()->rowCount(rootIndex()); ++row) {

            QModelIndex index = model()->index(row, 1, rootIndex());
            if (model()->data(index).toDouble() > 0.0) {
                //找到当前项,返回对应的模型索引
                if (listItem == validRow)
                    return model()->index(row, 0, rootIndex());

                // Update the list index that corresponds to the next valid row.
                ++validRow;
            }
        }
    }

    return QModelIndex();
}

bool PieView::isIndexHidden(const QModelIndex & /*index*/) const
{
    return false;
}

/*
    Returns the rectangle of the item at position \a index in the
    model. The rectangle is in contents coordinates.
*/

QRect PieView::itemRect(const QModelIndex &index) const
{
    if (!index.isValid())
        return QRect();

    // Check whether the index's row is in the list of rows represented by slices.
    // valueIndex 表示Quantity的索引(index.column() == 1)
    QModelIndex valueIndex;
    if (index.column() != 1)
        valueIndex = model()->index(index.row(), 1, rootIndex());
    else
        valueIndex = index;

    //Quantity<=0.0不显示
    if (model()->data(valueIndex).toDouble() <= 0.0)
        return QRect();

    //统计当前索引对应的listItem数量
    int listItem = 0;
    for (int row = index.row()-1; row >= 0; --row) {
        if (model()->data(model()->index(row, 1, rootIndex())).toDouble() > 0.0)
            listItem++;
    }


    switch (index.column()) {
    case 0: {//原始模型中的column==0,Label
        const qreal itemHeight = QFontMetricsF(viewOptions().font).height();

        return QRect(totalSize,
                     qRound(margin + listItem * itemHeight),
                     totalSize - margin, qRound(itemHeight));//列表矩形
    }
    case 1://原始模型中的column==1,Quantity.
        return viewport()->rect();//paintEvent 视图区域
    }
    return QRect();
}

QRegion PieView::itemRegion(const QModelIndex &index) const
{
    if (!index.isValid())
        return QRegion();

    //非Quantity区域
    if (index.column() != 1)
        return itemRect(index);

    //Quantity<=0 不显示
    if (model()->data(index).toDouble() <= 0.0)
        return QRegion();

    //Quantity区域,找到当前索引,返回饼图区域
    double startAngle = 0.0;
    for (int row = 0; row < model()->rowCount(rootIndex()); ++row) {

        QModelIndex sliceIndex = model()->index(row, 1, rootIndex());
        double value = model()->data(sliceIndex).toDouble();

        if (value > 0.0) {
            //数值占的百分比角度
            double angle = 360 * value / totalValue;
            //找到当前索引,返回饼图区域
            if (sliceIndex == index) {
                //画饼图
                QPainterPath slicePath;
                slicePath.moveTo(totalSize / 2, totalSize / 2);
                slicePath.arcTo(margin, margin, margin + pieSize, margin + pieSize,
                                startAngle, angle);
                slicePath.closeSubpath();

                return QRegion(slicePath.toFillPolygon().toPolygon());
            }
            //继续查找
            startAngle += angle;
        }
    }

    return QRegion();
}

int PieView::horizontalOffset() const
{
    return horizontalScrollBar()->value();
}

void PieView::mousePressEvent(QMouseEvent *event)
{//显示选中
    QAbstractItemView::mousePressEvent(event);
    origin = event->pos();
    if (!rubberBand)
        rubberBand = new QRubberBand(QRubberBand::Rectangle, viewport());
    rubberBand->setGeometry(QRect(origin, QSize()));
    rubberBand->show();
}

void PieView::mouseMoveEvent(QMouseEvent *event)
{//显示选中的矩形
    if (rubberBand)
        rubberBand->setGeometry(QRect(origin, event->pos()).normalized());
    QAbstractItemView::mouseMoveEvent(event);
}

void PieView::mouseReleaseEvent(QMouseEvent *event)
{//隐藏选中矩形
    QAbstractItemView::mouseReleaseEvent(event);
    if (rubberBand)
        rubberBand->hide();
    viewport()->update();
}

QModelIndex PieView::moveCursor(QAbstractItemView::CursorAction cursorAction,
                                Qt::KeyboardModifiers /*modifiers*/)
{
    QModelIndex current = currentIndex();

    switch (cursorAction) {
        case MoveLeft:
        case MoveUp://上移
            if (current.row() > 0)
                current = model()->index(current.row() - 1, current.column(),
                                         rootIndex());
            else
                current = model()->index(0, current.column(), rootIndex());
            break;
        case MoveRight:
        case MoveDown://下移
            if (current.row() < rows(current) - 1)
                current = model()->index(current.row() + 1, current.column(),
                                         rootIndex());
            else
                current = model()->index(rows(current) - 1, current.column(),
                                         rootIndex());
            break;
        default:
            break;
    }
    //paintEvent 视图区域
    viewport()->update();
    return current;
}

void PieView::paintEvent(QPaintEvent *event)
{
    //QItemSelectionModel在一个视图中跟踪所选项目,或者在同一模型的多个视图中跟踪所选项目
    QItemSelectionModel *selections = selectionModel();
    //QStyleOptionViewItem类描述在视图中绘制项的参数
    QStyleOptionViewItem option = viewOptions();

    QBrush background = option.palette.base();
    QPen foreground(option.palette.color(QPalette::WindowText));

    QPainter painter(viewport());
    painter.setRenderHint(QPainter::Antialiasing);

    painter.fillRect(event->rect(), background);
    painter.setPen(foreground);

    // Viewport rectangles
    QRect pieRect = QRect(margin, margin, pieSize, pieSize);

    if (validItems <= 0)
        return;

    painter.save();
    //转换到视图坐标
    painter.translate(pieRect.x() - horizontalScrollBar()->value(),
                      pieRect.y() - verticalScrollBar()->value());
    //绘制饼图圆
    painter.drawEllipse(0, 0, pieSize, pieSize);

    //------------------- 绘制饼图 -------------------
    double startAngle = 0.0;
    int row;
    for (row = 0; row < model()->rowCount(rootIndex()); ++row) {
        QModelIndex index = model()->index(row, 1, rootIndex());
        double value = model()->data(index).toDouble();

        if (value > 0.0) {
            //当前模型项对应饼图的角度
            double angle = 360 * value / totalValue;
            //获取模型项颜色
            QModelIndex colorIndex = model()->index(row, 0, rootIndex());
            QColor color = QColor(model()->data(colorIndex, Qt::DecorationRole).toString());
            //画刷风格
            if (currentIndex() == index)
                painter.setBrush(QBrush(color, Qt::Dense4Pattern));
            else if (selections->isSelected(index))
                painter.setBrush(QBrush(color, Qt::Dense3Pattern));
            else
                painter.setBrush(QBrush(color));

            //绘制饼图
            painter.drawPie(0, 0, pieSize, pieSize, int(startAngle*16), int(angle*16));

            startAngle += angle;
        }
    }
    painter.restore();

    //------------------- 绘制列表 -------------------
    int keyNumber = 0;
    for (row = 0; row < model()->rowCount(rootIndex()); ++row) {
        QModelIndex index = model()->index(row, 1, rootIndex());
        double value = model()->data(index).toDouble();

        if (value > 0.0) {
            QModelIndex labelIndex = model()->index(row, 0, rootIndex());

            QStyleOptionViewItem option = viewOptions();
            option.rect = visualRect(labelIndex);
            if (selections->isSelected(labelIndex))
                option.state |= QStyle::State_Selected;
            if (currentIndex() == labelIndex)
                option.state |= QStyle::State_HasFocus;
            itemDelegate()->paint(&painter, option, labelIndex);

            ++keyNumber;
        }
    }
}

void PieView::resizeEvent(QResizeEvent * /* event */)
{
    updateGeometries();
}

int PieView::rows(const QModelIndex &index) const
{
    return model()->rowCount(model()->parent(index));
}

void PieView::rowsInserted(const QModelIndex &parent, int start, int end)
{
    for (int row = start; row <= end; ++row) {
        QModelIndex index = model()->index(row, 1, rootIndex());
        double value = model()->data(index).toDouble();

        if (value > 0.0) {
            totalValue += value;
            ++validItems;
        }
    }

    QAbstractItemView::rowsInserted(parent, start, end);
}

void PieView::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end)
{
    for (int row = start; row <= end; ++row) {
        QModelIndex index = model()->index(row, 1, rootIndex());
        double value = model()->data(index).toDouble();
        if (value > 0.0) {
            totalValue -= value;
            --validItems;
        }
    }

    QAbstractItemView::rowsAboutToBeRemoved(parent, start, end);
}

void PieView::scrollContentsBy(int dx, int dy)
{
    viewport()->scroll(dx, dy);
}

void PieView::scrollTo(const QModelIndex &index, ScrollHint)
{
    //视图区域
    QRect area = viewport()->rect();
    //当前索引矩形区域
    QRect rect = visualRect(index);

    if (rect.left() < area.left()) {//左移
        horizontalScrollBar()->setValue(
            horizontalScrollBar()->value() + rect.left() - area.left());
    } else if (rect.right() > area.right()) {//右移
        horizontalScrollBar()->setValue(
            horizontalScrollBar()->value() + qMin(
                rect.right() - area.right(), rect.left() - area.left()));
    }

    if (rect.top() < area.top()) {
        verticalScrollBar()->setValue(
            verticalScrollBar()->value() + rect.top() - area.top());
    } else if (rect.bottom() > area.bottom()) {
        verticalScrollBar()->setValue(
            verticalScrollBar()->value() + qMin(
                rect.bottom() - area.bottom(), rect.top() - area.top()));
    }

    update();
}

/*
    Find the indices corresponding to the extent of the selection.
*/

void PieView::setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags command)
{
    // Use content widget coordinates because we will use the itemRegion()
    // function to check for intersections.
    //选中的窗体坐标矩形
    QRect contentsRect = rect.translated(
                            horizontalScrollBar()->value(),
                            verticalScrollBar()->value()).normalized();

    int rows = model()->rowCount(rootIndex());
    int columns = model()->columnCount(rootIndex());
    QModelIndexList indexes;

    //遍历查找所有与当前选中矩形contentsRect相交的索引
    for (int row = 0; row < rows; ++row) {
        for (int column = 0; column < columns; ++column) {
            QModelIndex index = model()->index(row, column, rootIndex());
            QRegion region = itemRegion(index);
            //找到区域与contentsRect相交
            if (region.intersects(contentsRect))
                indexes.append(index);
        }
    }

    if (indexes.size() > 0) {
        int firstRow = indexes.at(0).row();
        int lastRow = firstRow;
        int firstColumn = indexes.at(0).column();
        int lastColumn = firstColumn;

        //获取选中的范围
        for (int i = 1; i < indexes.size(); ++i) {
            firstRow = qMin(firstRow, indexes.at(i).row());
            lastRow = qMax(lastRow, indexes.at(i).row());
            firstColumn = qMin(firstColumn, indexes.at(i).column());
            lastColumn = qMax(lastColumn, indexes.at(i).column());
        }

        //选中模型项
        QItemSelection selection(
            model()->index(firstRow, firstColumn, rootIndex()),
            model()->index(lastRow, lastColumn, rootIndex()));
        selectionModel()->select(selection, command);
    } else {
        QModelIndex noIndex;
        QItemSelection selection(noIndex, noIndex);
        selectionModel()->select(selection, command);
    }

    //更新视图
    update();
}

void PieView::updateGeometries()
{
    horizontalScrollBar()->setPageStep(viewport()->width());
    horizontalScrollBar()->setRange(0, qMax(0, 2 * totalSize - viewport()->width()));
    verticalScrollBar()->setPageStep(viewport()->height());
    verticalScrollBar()->setRange(0, qMax(0, totalSize - viewport()->height()));
}

int PieView::verticalOffset() const
{
    return verticalScrollBar()->value();
}

/*
    Returns the position of the item in viewport coordinates.
*/

QRect PieView::visualRect(const QModelIndex &index) const
{
    QRect rect = itemRect(index);
    if (!rect.isValid())
        return rect;

    return QRect(rect.left() - horizontalScrollBar()->value(),
                 rect.top() - verticalScrollBar()->value(),
                 rect.width(), rect.height());
}

/*
    Returns a region corresponding to the selection in viewport coordinates.
*/

QRegion PieView::visualRegionForSelection(const QItemSelection &selection) const
{
    int ranges = selection.count();

    if (ranges == 0)
        return QRect();

    QRegion region;
    for (int i = 0; i < ranges; ++i) {
        const QItemSelectionRange &range = selection.at(i);
        for (int row = range.top(); row <= range.bottom(); ++row) {
            for (int col = range.left(); col <= range.right(); ++col) {
                QModelIndex index = model()->index(row, col, rootIndex());
                region += visualRect(index);
            }
        }
    }
    return region;
}

你可能感兴趣的:(#,MVC,qt)