模型视图(103):【例】Chart Example[官翻]

Chart Example

图表示例展示了如何为模型/视图框架创建自定义视图。

img

在本例中,表模型中的项表示为饼图中的切片,依赖于模型/视图架构的灵活性来处理自定义编辑和选择特征。

请注意,只有在数据需要专门化表示的情况下,才需要创建新的视图类。如果你需要用一种特殊的方式表示数据,你应该首先考虑使用标准的QListView, QTableView,或者QTreeView和自定义的QItemDelegate子类。

main.cpp

#include "MainWindow.h"

#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();
    return a.exec();
}

MainWindow类

MainWindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

QT_BEGIN_NAMESPACE
class QAbstractItemModel;
class QAbstractItemView;
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);

private slots:
    void openFile();
    void saveFile();

private:
    void setupModel();
    void setupViews();
    void loadFile(const QString &path);

    QAbstractItemModel *model = nullptr;
    QAbstractItemView *pieChart = nullptr;
};
#endif // MAINWINDOW_H

MainWindow.cpp

#include "PieView.h"
#include "MainWindow.h"

#include <QtWidgets>

// 构造函数
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    // 文件菜单
    QMenu *fileMenu = new QMenu(tr("&File"), this);

    QAction *openAction = fileMenu->addAction(tr("&Open..."));// 打开项
    openAction->setShortcuts(QKeySequence::Open); // 打开动作快捷键
    QAction *saveAction = fileMenu->addAction(tr("&Save As...")); // 保存项
    saveAction->setShortcuts(QKeySequence::SaveAs);  // 保存项快捷键
    QAction *quitAction = fileMenu->addAction(tr("E&xit"));  // 退出项
    quitAction->setShortcuts(QKeySequence::Quit);  // 退出项快捷键

    setupModel();  // 设置模型
    setupViews();  // 设置视图

    connect(openAction, &QAction::triggered, this, &MainWindow::openFile);
    connect(saveAction, &QAction::triggered, this, &MainWindow::saveFile);
    connect(quitAction, &QAction::triggered, qApp, &QCoreApplication::quit);

    menuBar()->addMenu(fileMenu);
    statusBar();

    loadFile(":/Charts/qtdata.cht");

    setWindowTitle(tr("Chart"));
    resize(870, 550);
}

// 设置模型
void MainWindow::setupModel()
{
    // 标准项目模型
    model = new QStandardItemModel(8, 2, this);
    model->setHeaderData(0, Qt::Horizontal, tr("Label"));    //设置头数据0列
    model->setHeaderData(1, Qt::Horizontal, tr("Quantity")); //设置头数据1列
}

// 设置界面
void MainWindow::setupViews()
{
    // 分裂期作为中央控件
    QSplitter *splitter = new QSplitter;
    QTableView *table = new QTableView;  // 表视图
    pieChart = new PieView;              // 自定义视图
    splitter->addWidget(table);          // 左边是表格
    splitter->addWidget(pieChart);       // 右边是饼视图
    splitter->setStretchFactor(0, 0);    // 0列不拉伸
    splitter->setStretchFactor(1, 1);    // 1列拉伸

    table->setModel(model);             // 两个视图设置同一个数据模型
    pieChart->setModel(model);

    QItemSelectionModel *selectionModel = new QItemSelectionModel(model); // 选择模型
    table->setSelectionModel(selectionModel);    // 两个视图设置同一个选择模型
    pieChart->setSelectionModel(selectionModel);

    QHeaderView *headerView = table->horizontalHeader();  // 表视图最后一列拉伸
    headerView->setStretchLastSection(true);

    setCentralWidget(splitter);
}
// 打开文件
void MainWindow::openFile()
{
    const QString fileName =
        QFileDialog::getOpenFileName(this, tr("Choose a data file"), "", "*.cht");
    if (!fileName.isEmpty())
        loadFile(fileName);
}
// 加载文件
void MainWindow::loadFile(const QString &fileName)
{
    QFile file(fileName);
    if (!file.open(QFile::ReadOnly | QFile::Text))
        return;

    QTextStream stream(&file);
    // 清空模型
    model->removeRows(0, model->rowCount(QModelIndex()), QModelIndex());

    int row = 0;
    while (!stream.atEnd()) {
        const QString line = stream.readLine();  // 读取文件中一行字符串
        if (!line.isEmpty()) {
            model->insertRows(row, 1, QModelIndex());  // 模型添加一列

            const QStringList pieces = line.split(QLatin1Char(','), Qt::SkipEmptyParts);  // 分割字符串
            if (pieces.size() < 3)
                continue;
            // 模型设置项数据
            model->setData(model->index(row, 0, QModelIndex()),pieces.value(0));//默认Qt::EditRole
            model->setData(model->index(row, 1, QModelIndex()),pieces.value(1));//默认Qt::EditRole
            model->setData(model->index(row, 0, QModelIndex()),
                           QColor(pieces.value(2)), Qt::DecorationRole);
            row++;
        }
    };

    file.close();
    statusBar()->showMessage(tr("Loaded %1").arg(fileName), 2000); //状态栏显示信息
}
// 保存文件
void MainWindow::saveFile()
{
    QString fileName = QFileDialog::getSaveFileName(this,
        tr("Save file as"), "", "*.cht");

    if (fileName.isEmpty())
        return;

    QFile file(fileName);
    if (!file.open(QFile::WriteOnly | QFile::Text))
        return;

    QTextStream stream(&file);
    for (int row = 0; row < model->rowCount(QModelIndex()); ++row) {

        QStringList pieces;

        pieces.append(model->data(model->index(row, 0, QModelIndex()),
                                  Qt::DisplayRole).toString());
        pieces.append(model->data(model->index(row, 1, QModelIndex()),
                                  Qt::DisplayRole).toString());
        pieces.append(model->data(model->index(row, 0, QModelIndex()),
                                  Qt::DecorationRole).toString());

        stream << pieces.join(',') << "\n";
    }

    file.close();
    statusBar()->showMessage(tr("Saved %1").arg(fileName), 2000);
}

PieView类

PieView.h

自定义视图比较复杂,很多函数都不知道为啥要使用。注释了一点

#ifndef PIEVIEW_H
#define PIEVIEW_H

#include <QAbstractItemView>

class PieView : public QAbstractItemView
{
public:
    PieView(QWidget *parent = nullptr);

    QRect visualRect(const QModelIndex &index) const override;
    void scrollTo(const QModelIndex &index, ScrollHint hint = EnsureVisible) override;
    QModelIndex indexAt(const QPoint &point) const override;

protected slots:
    void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles = QVector<int>()) override;
    void rowsInserted(const QModelIndex &parent, int start, int end) override;
    void rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) override;

protected:
    bool edit(const QModelIndex &index, EditTrigger trigger, QEvent *event) override;
    QModelIndex moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers) override;
    int horizontalOffset() const override;
    int verticalOffset() const override;

    bool isIndexHidden(const QModelIndex &index) const override;

    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;

    QRegion visualRegionForSelection(const QItemSelection &selection) const override;

private:
    QRect itemRect(const QModelIndex &index) 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;
};

#endif // PIEVIEW_H

PieView.cpp

#include "PieView.h"
#include <QtWidgets>

// 构造函数
PieView::PieView(QWidget *parent)
    : QAbstractItemView(parent)
{
    horizontalScrollBar()->setRange(0,0);
    verticalScrollBar()->setRange(0,0);
}
// 滚动视图保证索引执行的项可见
void PieView::scrollTo(const QModelIndex &index, QAbstractItemView::ScrollHint /*hint*/)
{
    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()));
//    }

    horizontalScrollBar()->setValue(
        horizontalScrollBar()->value() + 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()));
//    }

    verticalScrollBar()->setValue(
        verticalScrollBar()->value() + rect.top() - area.top());

    update();
}

// 获取point所在的项目索引
QModelIndex PieView::indexAt(const QPoint &point) const
{
    if(validItems == 0)
        return QModelIndex();

    // 将视图坐标转化为widget的内容坐标
    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;

        double d = std::sqrt(std::pow(cx,2) + std::pow(cy,2));

        if(d == 0 || d > pieSize / 2)
            return QModelIndex();

        double angle = qRadiansToDegrees(std::atan2(cy,cx));
        if(angle < 0)
            angle = 360 + angle;

        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();
        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());
                ++validRow;
            }
        }
    }
    return QModelIndex();
}
// 项数据变动
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();
}
// 视图插入新行
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);
}

// 视图编辑
bool PieView::edit(const QModelIndex &index, QAbstractItemView::EditTrigger trigger, QEvent *event)
{
    if(index.column() == 0)
        return QAbstractItemView::edit(index,trigger,event);
    else
        return false;
}
// 鼠标移动返回下一个模型索引,纯虚
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;
    }

    viewport()->update();
    return current;
}
// 水平偏移
int PieView::horizontalOffset() const
{
    return horizontalScrollBar()->value();
}
// 索引项是否隐藏
bool PieView::isIndexHidden(const QModelIndex &/*index*/) const
{
    return  false;
}
// 鼠标按下,创建一个橡皮筋
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();
}
// 重绘事件
void PieView::paintEvent(QPaintEvent *event)
{
    QItemSelectionModel *selections = selectionModel();
    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();
}

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

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.
    QModelIndex valueIndex;

    if (index.column() != 1)
        valueIndex = model()->index(index.row(), 1, rootIndex());
    else
        valueIndex = index;

    if (model()->data(valueIndex).toDouble() <= 0.0)
        return QRect();

    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: {
        const qreal itemHeight = QFontMetricsF(viewOptions().font).height();

        return QRect(totalSize,
                     qRound(margin + listItem * itemHeight),
                     totalSize - margin, qRound(itemHeight));
    }
    case 1:
        return viewport()->rect();
    }
    return QRect();
}

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

    if (index.column() != 1)
        return itemRect(index);

    if (model()->data(index).toDouble() <= 0.0)
        return QRegion();

    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::rows(const QModelIndex &index) const
{
    return model()->rowCount(model()->parent(index));
}
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;

    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);
            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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值