QT的Model-View实现大批量数据展示

发布于:2024-08-09 ⋅ 阅读:(77) ⋅ 点赞:(0)

一、完整源代码

1.项目结构

2.各文件代码展示

define.h

#pragma once
#include <QVector>

//学生信息
typedef struct _STUDENT {
    QString name;   //姓名
    int score1;     //语文成绩
    int score2;     //数学成绩
    int score3;     //外语成绩
    _STUDENT()
    {
        name = "";
        score1 = score2 = score3 = 0;
    }
}STUDENT, *PSTUDENT;

//班级信息
typedef struct _CLASS {
    QString name;   //班级
    QVector<STUDENT*> students;
    _CLASS()
    {
        name = "";
    }
}CLASS;

mainwindow.h

#pragma once

#include <QtWidgets/QWidget>
#include "ui_mainwindow.h"
#include "define.h"

class TreeModel;
class TreeItem;

class MainWindow : public QWidget
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = Q_NULLPTR);


private slots:
    void on_btn1_clicked(); //QStandardItemModel
    void on_btn2_clicked(); //自定义model
    void SlotItemSelect(const QModelIndex &modelIndex);
    void SlotAddItem();
    void SlotRightMenu(const QPoint &pos);
    void SlotTreeMenuDelete();

private:
    Ui::MainWindowClass *ui;
    QVector<CLASS*> mClasses;   //模拟数据
    TreeModel *model;
};

mainwindow.cpp

#include "mainwindow.h"
#include "TreeItem.h"
#include "TreeModel.h"

#include <QMenu>
#include <QTreeView>
#include <QStandardItemModel>
#include <QStandardItem>
#include <QItemSelectionModel>

MainWindow::MainWindow(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::MainWindowClass())
{
    ui->setupUi(this);
    //初始化模拟数据:学生成绩
    //10个班级、每个班级10000个学生,共10W行记录
    int nClass = 10;
    int nStudent = 100000;
    for (int i = 0; i < nClass; i++)
    {
        CLASS* c = new CLASS;
        c->name = QString("class%1").arg(i);
        for (int j = 0; j < nStudent; j++)
        {
            STUDENT* s = new STUDENT;
            s->name = QString("name%1").arg(j);
            s->score1 = s->score2 = s->score3 = (j % 10) * 10;
            c->students.append(s);
        }
        mClasses.append(c);
    }
    connect(ui->btn1, &QPushButton::clicked, this, &MainWindow::on_btn1_clicked);
    connect(ui->btn2, &QPushButton::clicked, this, &MainWindow::on_btn2_clicked);
    connect(ui->treeView, &QTreeView::clicked, this, &MainWindow::SlotItemSelect);
    connect(ui->pushButton, &QPushButton::clicked, this, &MainWindow::SlotAddItem);
    connect(ui->treeView, &QTreeView::customContextMenuRequested, this, &MainWindow::SlotRightMenu);
    ui->treeView->setContextMenuPolicy(Qt::CustomContextMenu);
}

void MainWindow::on_btn1_clicked()
{
    //1,QTreeView常用设置项
    QTreeView* t = ui->treeView;
    //    t->setEditTriggers(QTreeView::NoEditTriggers);			//单元格不能编辑
    t->setSelectionBehavior(QTreeView::SelectRows);			//一次选中整行
    t->setSelectionMode(QTreeView::SingleSelection);        //单选,配合上面的整行就是一次选单行
//    t->setAlternatingRowColors(true);                       //每间隔一行颜色不一样,当有qss时该属性无效
    t->setFocusPolicy(Qt::NoFocus);                         //去掉鼠标移到单元格上时的虚线框

    //2,列头相关设置
    t->header()->setHighlightSections(true);                //列头点击时字体变粗,去掉该效果
    t->header()->setDefaultAlignment(Qt::AlignCenter);      //列头文字默认居中对齐
    t->header()->setDefaultSectionSize(100);                //默认列宽100
    t->header()->setStretchLastSection(true);               //最后一列自适应宽度
    t->header()->setSortIndicator(0, Qt::AscendingOrder);    //按第1列升序排序
    //3,构造Model
    QStringList headers;
    headers << QStringLiteral("班级/姓名")
        << QStringLiteral("语文")
        << QStringLiteral("数学")
        << QStringLiteral("外语")
        << QStringLiteral("总分")
        << QStringLiteral("平均分")
        << QStringLiteral("是否合格")
        << QStringLiteral("是否评优");
    QStandardItemModel* model = new QStandardItemModel(ui->treeView);
    model->setHorizontalHeaderLabels(headers);
    foreach(CLASS* c, mClasses)
    {
        QStandardItem *pItemClass = new QStandardItem(c->name);
        model->appendRow(pItemClass);
        foreach(STUDENT* s, c->students)
        {
            //二级节点:学生信息
            int score1 = s->score1;
            int score2 = s->score2;
            int score3 = s->score3;
            int nTotal = score1 + score2 + score3;
            int nAverage = nTotal / 3;
            bool bPass = true;
            if (score1 < 60 || score2 < 60 || score3 < 60)
            {
                //任意一门课不合格则不合格
                bPass = false;
            }
            bool bGood = false;
            if (score1 >= 90 && score2 >= 90 && score3 >= 90)
            {
                //每门课都达到90分以上评优
                bGood = true;
            }

            QList<QStandardItem*> items;
            QStandardItem* item0 = new QStandardItem(s->name);
            QStandardItem* item1 = new QStandardItem(QString::number(score1));
            QStandardItem* item2 = new QStandardItem(QString::number(score2));
            QStandardItem* item3 = new QStandardItem(QString::number(score3));
            QStandardItem* item4 = new QStandardItem(QString::number(nTotal));
            QStandardItem* item5 = new QStandardItem(QString::number(nAverage));
            QStandardItem* item6 = new QStandardItem(bPass ? QStringLiteral("合格") : QStringLiteral("不合格"));
            QStandardItem* item7 = new QStandardItem(bGood ? QStringLiteral("优秀") : QStringLiteral("-"));
            items << item0 << item1 << item2 << item3 << item4 << item5 << item6 << item7;
            pItemClass->appendRow(items);
        }
    }
    t->setModel(model);
}

void MainWindow::on_btn2_clicked()
{
    //1,QTreeView常用设置项
    QTreeView* t = ui->treeView;
    //    t->setEditTriggers(QTreeView::NoEditTriggers);			//单元格不能编辑
    t->setSelectionBehavior(QTreeView::SelectRows);			//一次选中整行
    t->setSelectionMode(QTreeView::SingleSelection);        //单选,配合上面的整行就是一次选单行
//    t->setAlternatingRowColors(true);                       //每间隔一行颜色不一样,当有qss时该属性无效
    t->setFocusPolicy(Qt::NoFocus);                         //去掉鼠标移到单元格上时的虚线框

    //2,列头相关设置
    t->header()->setHighlightSections(true);                //列头点击时字体变粗,去掉该效果
    t->header()->setDefaultAlignment(Qt::AlignCenter);      //列头文字默认居中对齐
    t->header()->setDefaultSectionSize(100);                //默认列宽100
    t->header()->setStretchLastSection(true);               //最后一列自适应宽度
    t->header()->setSortIndicator(0, Qt::AscendingOrder);    //按第1列升序排序
    //3,构造Model
    QStringList headers;
    headers << QStringLiteral("班级/姓名")
        << QStringLiteral("语文")
        << QStringLiteral("数学")
        << QStringLiteral("外语")
        << QStringLiteral("总分")
        << QStringLiteral("平均分")
        << QStringLiteral("是否合格")
        << QStringLiteral("是否评优");
    model = new TreeModel(headers, ui->treeView);
    TreeItem *pRootItem = model->root();
    foreach(CLASS* c, mClasses)
    {
        TreeItem *pClass = new TreeItem(pRootItem);
        pClass->setLevel(1);
        pClass->setPtr(c);
        pRootItem->appendChild(pClass);
        foreach(STUDENT* s, c->students)
        {
            TreeItem* itemStudent = new TreeItem(pClass);
            itemStudent->setLevel(2);   //设为一级节点,供显示时判断节点层级来转换数据指针类型
            itemStudent->setPtr(s);     //保存STUDENT* s为其数据指针,显示时从STUDENT*取内容显示
            pClass->appendChild(itemStudent);
        }
    }
    t->setModel(model);
}

void MainWindow::SlotItemSelect(const QModelIndex &modelIndex)
{
    TreeItem *pItem = static_cast<TreeItem*>(modelIndex.internalPointer());

    QString strText = QString::number(pItem->level());
    strText += "\n" + QString::number(pItem->row());
    ui->plainTextEdit->setPlainText(strText);
}

void MainWindow::SlotAddItem()
{
    QItemSelectionModel *pSelectionModel = ui->treeView->selectionModel();
    if (pSelectionModel->hasSelection()) {
        // 获取选中的索引列表
        QModelIndexList selectedIndexes = pSelectionModel->selectedIndexes();

        // 一般情况下,QTreeView 是单选,所以可以直接获取第一个选中的索引
        QModelIndex selectedIndex = selectedIndexes.first();
        // 如果需要处理特定列的数据,可以通过 QModelIndex 访问模型中的数据
        //QVariant data = selectedIndex.data(Qt::DisplayRole);
        TreeItem *pItem = static_cast<TreeItem*>(selectedIndex.internalPointer());
        TreeItem *pParentItem = pItem->parentItem();
        STUDENT* s = new STUDENT;
        s->name = QString("name10086");
        s->score1 = s->score2 = s->score3 = 10086;
        TreeItem *pAddItem = new TreeItem(pParentItem);
        pAddItem->setPtr(s);
        pAddItem->setLevel(2);
        pParentItem->appendChild(pAddItem);
        ui->treeView->setModel(model);
        // 方法1,递归方法
        //QModelIndex pParentIndex = model->indexOfItem(pParentItem);
        // 方法2,通过createIndex
        QModelIndex pParentIndex = model->indexFromItem(pParentItem);
        ui->treeView->collapse(pParentIndex);
        ui->treeView->expand(pParentIndex);
    }
}

void MainWindow::SlotRightMenu(const QPoint &pos)
{
    //创建右键菜单
    QMenu menu;

    //添加action
    QAction *actionDelete = new QAction(QStringLiteral("删除"));
    menu.addAction(actionDelete);
    connect(actionDelete, &QAction::triggered, this, &MainWindow::SlotTreeMenuDelete);
    menu.exec(ui->treeView->viewport()->mapToGlobal(pos));
}

void MainWindow::SlotTreeMenuDelete()
{
    QItemSelectionModel *pSelectionModel = ui->treeView->selectionModel();
    if (pSelectionModel->hasSelection())
    {
        QModelIndexList selectedIndexes = pSelectionModel->selectedIndexes();
        QModelIndex selectedIndex = selectedIndexes.first();
        TreeItem *pItem = static_cast<TreeItem*>(selectedIndex.internalPointer());
        TreeItem *pParentItem = pItem->parentItem();
        pParentItem->removeChild(pItem->row());
        for (int i = 0; i < pParentItem->getChildList().size(); i++)
        {
            TreeItem *pItemTemp = pParentItem->getChildList().at(i);
            pItemTemp->setRow(i);
        }
        QModelIndex pParentIndex = model->indexFromItem(pParentItem);
        ui->treeView->collapse(pParentIndex);
        ui->treeView->expand(pParentIndex);
    }
}

TreeItem.h

#pragma once
#include <QVariant>
#include <QList>
class TreeItem
{
public:
    explicit TreeItem(TreeItem *parentItem = nullptr);
    ~TreeItem();
    int childCount() const;                 //子节点计数
    int row() const;                        //获取该节点是父节点的第几个子节点
    void appendChild(TreeItem *item);
    void removeChild(int index);
    QList<TreeItem*> getChildList();
    TreeItem *parentItem();                 //获取父节点指针
    TreeItem *child(int row);               //获取第row个子节点指针
    
    //核心函数:获取节点第column列的数据
    QVariant data(int column) const;

    //设置、获取节点是几级节点(就是树的层级)
    int level() { return m_nLevel; }
    void setLevel(int level) { m_nLevel = level; }

    //设置、获取节点存的数据指针
    void setPtr(void* p) { m_pPtr = p; }
    void* ptr() { return m_pPtr; }

    //保存该节点是其父节点的第几个子节点,查询优化所用
    void setRow(int row) {
        m_nRow = row;
    }

private:
    TreeItem *m_pParentItem;
    QList<TreeItem*> m_listChildItems;
    int m_nLevel;
    void *m_pPtr;
    int m_nRow;

};

TreeItem.cpp

#include "TreeItem.h"
#include "define.h"
TreeItem::TreeItem(TreeItem *parentItem /*= nullptr*/)
{
    m_pParentItem = parentItem;
}

TreeItem::~TreeItem()
{

}

int TreeItem::childCount() const
{
    return m_listChildItems.count();
}

int TreeItem::row() const
{
    return m_nRow;
}

void TreeItem::appendChild(TreeItem *item)
{
    item->setRow(m_listChildItems.size());   //item存自己是第几个,可以优化效率
    m_listChildItems.append(item);
}

void TreeItem::removeChild(int index)
{
    delete m_listChildItems.takeAt(index); // 删除并释放元素的内存
}

QList<TreeItem*> TreeItem::getChildList()
{
    return m_listChildItems;
}

TreeItem * TreeItem::parentItem()
{
    return m_pParentItem;
}

TreeItem * TreeItem::child(int row)
{
    return m_listChildItems.value(row);
}

QVariant TreeItem::data(int column) const
{
    if (m_nLevel == 1)
    {
        //一级节点,班级
        if (column == 0)
        {
            CLASS* c = (CLASS*)m_pPtr;
            return c->name;
        }
    }
    else if (m_nLevel == 2)
    {
        //二级节点学生信息
        STUDENT* s = (STUDENT*)m_pPtr;
        switch (column)
        {
        case 0: return s->name;
        case 1: return QString::number(s->score1);
        case 2: return QString::number(s->score2);
        case 3: return QString::number(s->score3);
        case 4: return QString::number(s->score1 + s->score2 + s->score3);
        case 5: return QString::number((s->score1 + s->score2 + s->score3) / 3);
        case 6:
        {
            if (s->score1 < 60 || s->score2 < 60 || s->score3 < 60)
            {
                //任意一门课不合格则不合格
                return QStringLiteral("不合格");
            }
            else
            {
                return QStringLiteral("合格");
            }
        }
        case 7:
        {
            if (s->score1 >= 90 && s->score2 >= 90 && s->score3 >= 90)
            {
                //每门课都达到90分以上评优
                return QStringLiteral("优秀");
            }
            else
            {
                return QStringLiteral("-");
            }
        }
        default:
            return QVariant();
        }
    }
    return QVariant();
}

TreeModel.h

#pragma once
#include "TreeItem.h"
#include <QAbstractItemModel>
class TreeModel : public QAbstractItemModel
{
    Q_OBJECT
public:
    explicit TreeModel(QStringList headers,QObject *parent = nullptr);
    ~TreeModel();
    //以下为自定义model需要实现的一些虚函数,将会被Qt在查询model数据时调用
    //headerData: 获取表头第section列的数据
    //data: 核心函数,获取某个索引index的元素的各种数据
    //      role决定获取哪种数据,常用有下面几种:
    //      DisplayRole(默认):就是界面显示的文本数据
    //      TextAlignmentRole:就是元素的文本对齐属性
    //      TextColorRole、BackgroundRole:分别指文本颜色、单元格背景色
    //flags: 获取index的一些标志,一般不怎么改
    //index: Qt向你的model请求一个索引为parent的节点下面的row行column列子节点的元素,在本函数里你需要返回该元素的正确索引
    //parent:获取指定元素的父元素
    //rowCount: 获取指定元素的子节点个数(下一级行数)
    //columnCount: 获取指定元素的列数
    Qt::ItemFlags flags(const QModelIndex &index) const override;
    QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
    QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
    QModelIndex parent(const QModelIndex &child) const override;
    int rowCount(const QModelIndex &parent = QModelIndex()) const override;
    int columnCount(const QModelIndex &parent = QModelIndex()) const override;

public:
    TreeItem *itemFromIndex(const QModelIndex &index) const;
    QModelIndex indexFromItem(const TreeItem *item) const;
    QModelIndex indexOfItem(TreeItem *item) const;
    QModelIndex recursiveIndexSearch(TreeItem *currentItem, TreeItem *targetItem) const;
    TreeItem *root();

private:
    QStringList m_listHeaders;
    TreeItem *m_pRootItem;
};

TreeModel.cpp

#include "TreeModel.h"

TreeModel::TreeModel(QStringList headers, QObject *parent /*= nullptr*/):QAbstractItemModel(parent)
{
    m_listHeaders = headers;
    m_pRootItem = new TreeItem;
}

TreeModel::~TreeModel()
{

}

Qt::ItemFlags TreeModel::flags(const QModelIndex &index) const
{
    if (!index.isValid())
    {
        return 0;
    }
    return QAbstractItemModel::flags(index);
}

// 返回表头第section列的数据
QVariant TreeModel::headerData(int section, Qt::Orientation orientation, int role /*= Qt::DisplayRole*/) const
{
    if (orientation == Qt::Horizontal)
    {
        if (role == Qt::DisplayRole)
        {
            return m_listHeaders.at(section);
        }
    }
    return QVariant();
}

QVariant TreeModel::data(const QModelIndex &index, int role /*= Qt::DisplayRole*/) const
{
    if (!index.isValid())
    {
        return QVariant();
    }

    TreeItem *item = static_cast<TreeItem*>(index.internalPointer());
    if (role == Qt::DisplayRole)
    {
        return item->data(index.column());
    }
    return QVariant();
}

QModelIndex TreeModel::index(int row, int column, const QModelIndex &parent /*= QModelIndex()*/) const
{
    if (!hasIndex(row, column, parent))
        return QModelIndex();
    TreeItem *parentItem;
    if (!parent.isValid())
    {
        parentItem = m_pRootItem;
    }
    else
    {
        parentItem = static_cast<TreeItem *>(parent.internalPointer());
    }
    TreeItem *pChildItem = parentItem->child(row);
    if (pChildItem)
    {
        return createIndex(row,column,pChildItem);
    }
    else
    {
        return QModelIndex();
    }
}

QModelIndex TreeModel::parent(const QModelIndex &child) const
{
    if (!child.isValid())
        return QModelIndex();
    TreeItem *childItem = static_cast<TreeItem*>(child.internalPointer());
    TreeItem *parentItem = childItem->parentItem();
    if (parentItem == m_pRootItem)
        return QModelIndex();
    return createIndex(parentItem->row(), 0, parentItem);
}

int TreeModel::rowCount(const QModelIndex &parent /*= QModelIndex()*/) const
{
    TreeItem *parentItem;
    if (parent.column() > 0)
        return 0;

    if (!parent.isValid())
        parentItem = m_pRootItem;
    else
        parentItem = static_cast<TreeItem*>(parent.internalPointer());

    return parentItem->childCount();
}

int TreeModel::columnCount(const QModelIndex &parent /*= QModelIndex()*/) const
{
    return m_listHeaders.size();;
}

TreeItem * TreeModel::itemFromIndex(const QModelIndex &index) const
{
    if (!index.isValid())
        return nullptr;
    TreeItem *item = static_cast<TreeItem *>(index.internalPointer());
    return item;
}

QModelIndex TreeModel::indexFromItem(const TreeItem *item) const
{
    TreeItem *pItem = const_cast<TreeItem *>(item);
    if (pItem->parentItem() == m_pRootItem)
    {
        return createIndex(pItem->row(), 0, pItem);
    }
    return QModelIndex();
}

QModelIndex TreeModel::indexOfItem(TreeItem *item) const
{
    // 如果模型是树形结构,可以递归遍历查找
    return recursiveIndexSearch(m_pRootItem, item);
}

QModelIndex TreeModel::recursiveIndexSearch(TreeItem *currentItem, TreeItem *targetItem) const
{
    if (currentItem == targetItem) {
        // 如果找到了目标项,返回其索引
        return createIndex(currentItem->row(), 0, currentItem);
    }

    // 递归搜索子项
    for (int i = 0; i < currentItem->childCount(); ++i) {
        QModelIndex idx = recursiveIndexSearch(currentItem->child(i), targetItem);
        if (idx.isValid()) {
            return idx;
        }
    }

    return QModelIndex();
}

TreeItem * TreeModel::root()
{
    return m_pRootItem;
}

3.界面


二、重要部分代码解释

1.TreeModel.cpp

在TreeMode中,最重要的是data()函数,该函数决定数据是否能够直接在View层进行显示。该接口中的QModelIndex是树中哪个结点下哪一行哪一列的索引(Model-View主要依赖于QModelIndex来取数据显示)。QModelIndex详解看:QT模型视图MVC系列教程(2)-模型数据索引QModelIndex详解-CSDN博客

QVariant TreeModel::data(const QModelIndex &index, int role /*= Qt::DisplayRole*/) const
{
    if (!index.isValid())
    {
        return QVariant();
    }

    TreeItem *item = static_cast<TreeItem*>(index.internalPointer());
    if (role == Qt::DisplayRole)
    {
        return item->data(index.column());
    }
    return QVariant();
}
2.TreeItem.cpp

TreeItem是自定义的一个item类,也就是树中显示的每一行数据在Model中的存储。View层会通过data函数来取要显示的数据。比如TreeModel::data中的return item->data(index.column());

QVariant TreeItem::data(int column) const
{
    if (m_nLevel == 1)
    {
        //一级节点,班级
        if (column == 0)
        {
            CLASS* c = (CLASS*)m_pPtr;
            return c->name;
        }
    }
    else if (m_nLevel == 2)
    {
        //二级节点学生信息
        STUDENT* s = (STUDENT*)m_pPtr;
        switch (column)
        {
        case 0: return s->name;
        case 1: return QString::number(s->score1);
        case 2: return QString::number(s->score2);
        case 3: return QString::number(s->score3);
        case 4: return QString::number(s->score1 + s->score2 + s->score3);
        case 5: return QString::number((s->score1 + s->score2 + s->score3) / 3);
        case 6:
        {
            if (s->score1 < 60 || s->score2 < 60 || s->score3 < 60)
            {
                //任意一门课不合格则不合格
                return QStringLiteral("不合格");
            }
            else
            {
                return QStringLiteral("合格");
            }
        }
        case 7:
        {
            if (s->score1 >= 90 && s->score2 >= 90 && s->score3 >= 90)
            {
                //每门课都达到90分以上评优
                return QStringLiteral("优秀");
            }
            else
            {
                return QStringLiteral("-");
            }
        }
        default:
            return QVariant();
        }
    }
    return QVariant();
}