Qt——自定义模型类

1.QStandardItemModel是一个通用的模型类

  • 能够以任意方式组织数据(线性、非线性)
  • 数据组织的基本单位为数据项(QStandardItem)
  • 每一个数据项能够存储多个具体数据(附加数据角色)
  • 每一个数据项能够对数据状态进行控制(可编辑、可选...)

2.Qt的变体类型QVariant

  • QVariant是一个用于封装的类型

  • QVariant能够表示大多数常见的值类型

  • QVariant每次只能封装单一类型的值

  • QVariant的意义在于能够设计"返回类型可变的函数"

    #include
    #include
    #include
    #include

    QVariant func()
    {
    QVariant ret;
    return ret;
    }

    int main(int argc, char *argv[])
    {
    QCoreApplication a(argc, argv);

    复制代码
      QVariant v1(1);
      QVariant v2(3.14);
      QVariant v3("520");
      QVariant v4(QPoint(15, 15));
      QVariant v5;
    
      qDebug() << v1.type();
      qDebug() << v1.typeName();
      qDebug() << v1.toInt();
      qDebug() << endl;
      qDebug() << v2.type();
      qDebug() << v2.typeName();
      qDebug() << v2.toInt();
      qDebug() << v2.toDouble();
      qDebug() << v2.value<double>();
      qDebug() << endl;
      qDebug() << v3.type();
      qDebug() << v3.typeName();
      qDebug() << v3.toInt();
      qDebug() << v3.value<QString>();
      qDebug() << endl;
      bool ok = true;
      qDebug() << v4.type();
      qDebug() << v4.typeName();
      qDebug() << v4.toInt(&ok);
      qDebug() << ok;
      qDebug() << v4.toPoint();
      qDebug() << endl;
    
      qDebug() <<v5.isValid();
    
      return QCoreApplication::exec();

    }

运行结果:

QVariant::int

int

1

QVariant::double

double

3

3.14

3.14

QVariant::QString

QString

520

"520"

QVariant::QPoint

QPoint

0

false

QPoint(15,15)

false

3.工程中的常见模型设计

  • 解析数据源中的数据(数据库、网络、串口等)
  • 将解析后的数据存入QStandardItem对象中
  • 根据数据间的关系在QStandardItemModel对象中组织数据项
  • 选择合适的视图显示数据值

例题:在文件中以行的形式存储考试成绩(ID,Name,Score)

开发GUI程序显示文件中的信息:

  • 计算平均成绩
  • 查找最好成绩和最差成绩
  • 可刷新显示的内容和删除内容

工程中常用数据应用架构为四层结构:数据层(DataSource)、数据表示层(ScoreInfo)、数据组织层(ScoreInfoScore)、数据显示层(QTableView)

(1)DataSource类:设置数据源并读取数据,对数据解析后生成数据对象

(2)ScoreInfo类:封装数据源中的一组完整数据,提供返回具体数据值的接口函数

(3)ScoreInfoModel类:使用标准模型类QStandardItemModel作为成员,以ScoreInfo类对象为最小单位进行数据组织

(4)QTableView类

┌─────────────────────────────────────────────────────────────┐

│ 界面层 (View) │

│ QTableView │

└─────────────────────────────────────────────────────────────┘

│ setModel()

┌─────────────────────────────────────────────────────────────┐

│ 数据组织层 (ViewModel) │

│ ScoreInfoModel │

│ 继承自 QStandardItemModel │

│ 职责:管理数据、提供显示、处理用户交互 │

│ ↓ 包含多个 │

└─────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────┐

│ 实体层 (Entity) │

│ ScoreInfo │

│ 职责:定义单个成绩记录的数据结构 │

│ - id (学号) │

│ - name (姓名) │

│ - score (分数) │

└─────────────────────────────────────────────────────────────┘

│ 创建和填充

┌─────────────────────────────────────────────────────────────┐

│ 数据源层 (Data Source) │

│ DataSource │

│ 职责:从文件读取数据、解析CSV、数据验证 │

└─────────────────────────────────────────────────────────────┘

4.右键上下文菜单的实现

  • 定义菜单对象(QMenu)
  • 连接菜单中的QAction对象到槽函数
  • 定义事件过滤器,并处理ContextMenu事件
  • 在当前鼠标的位置打开菜单对象

上下文菜单就是右键菜单( ContextMenu**)**

Widget.h

复制代码
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QTableView>
#include <QPushButton>
#include <QMenu>
#include "ScoreInfoModel.h"

class Widget : public QWidget
{
    Q_OBJECT

    ScoreInfoModel m_model;
    QTableView m_view;
    QPushButton m_refreshBtn;
    QPushButton m_clearBtn;
    QPushButton m_scoreBtn;
    QMenu m_menu;
private slots:
    void onRefreshBtnClicked();
    void onClearBtnClicked();
    void onScoreBtnClicked();
    void onDeleteActionClicked();

public:
    explicit Widget(QWidget *parent = nullptr);
    bool eventFilter(QObject* obj, QEvent* evt);
    ~Widget() override;
};
#endif // WIDGET_H

Widget.cpp

复制代码
#include "Widget.h"
#include "ScoreInfo.h"
#include "DataSource.h"
#include <QDebug>
#include "ScoreInfoModel.h"
#include <QMessageBox>
#include <QEvent>

Widget::Widget(QWidget *parent) : QWidget(parent)
{
    m_view.setParent(this);
    m_view.move(10, 10);
    m_view.resize(345, 180);
    m_view.installEventFilter(this);
    m_refreshBtn.setParent(this);
    m_refreshBtn.move(10, 200);
    m_refreshBtn.resize(95, 30);
    m_refreshBtn.setText("Refresh");
    connect(&m_refreshBtn, SIGNAL(clicked()), this, SLOT(onRefreshBtnClicked()));


    m_clearBtn.setParent(this);
    m_clearBtn.move(135, 200);
    m_clearBtn.resize(95, 30);
    m_clearBtn.setText("Clear");
    connect(&m_clearBtn, SIGNAL(clicked()), this, SLOT(onClearBtnClicked()));

    m_scoreBtn.setParent(this);
    m_scoreBtn.move(240, 200);
    m_scoreBtn.resize(95, 30);
    m_scoreBtn.setText("Score");
    m_model.setView(m_view);
    connect(&m_scoreBtn, SIGNAL(clicked()), this, SLOT(onScoreBtnClicked()));

    onRefreshBtnClicked();

    m_menu.addAction("Delete");
    connect(m_menu.actions()[0], SIGNAL(triggered()), this, SLOT(onDeleteActionClicked()));
}
void Widget::onDeleteActionClicked()
{
    m_model.remove(m_view.currentIndex().row());
}
bool Widget::eventFilter(QObject* obj, QEvent* evt)
{
    if( (obj == &m_view) && (evt->type() == QEvent::ContextMenu) )
    {
        m_menu.exec(cursor().pos()); //显示上下文菜单

    }
    return QWidget::eventFilter(obj, evt);
}

void Widget::onRefreshBtnClicked()
{
    DataSource ds;
    m_model.clear();
    if( ds.setDataPath("‪C:/Users/聪/Desktop/1.txt") )
    {
        m_model.add(ds.fetchData());
    }
    else
    {
        QMessageBox::critical(this, "Error", "Data source read error", QMessageBox::Ok);
    }
}
void Widget::onClearBtnClicked()
{
    m_model.clear();
}
void Widget::onScoreBtnClicked()
{
    int min = 256;
    int max = 0;
    int average = 0;

    if( m_model.count() >= 0 )
    {
        for(int i=0; i<m_model.count(); i++)
        {
            ScoreInfo info = m_model.getItem(i);
            if( info.score() < min )
            {
                min = info.score();
            }
            if( info.score() > max )
            {
                max = info.score();
            }
            average += info.score();
        }
        average /= m_model.count();
        QMessageBox::information(this, "Statistic", QString().sprintf("Min: %d\nMax:%d\nAverage: %d", min, max, average), QMessageBox::Ok);
    }
    else
    {
        QMessageBox::information(this, "Statistic", "No data record!", QMessageBox::Ok);
    }
}

Widget::~Widget() = default;

DataSource.h

复制代码
#ifndef DATASOURCE_H
#define DATASOURCE_H

#include <QObject>
#include <QList>
#include "ScoreInfo.h"

class DataSource : public QObject
{
    Q_OBJECT

    QList<ScoreInfo> m_data;
    bool parse(QString line, ScoreInfo& info); //解析一行文本数据,QString line: 输入的一行原始数据,ScoreInfo& info: 输出参数

public:
    explicit DataSource(QObject *parent = 0);
    bool setDataPath(QString path); //设置数据源文件路径
    QList<ScoreInfo> fetchData(); //返回所有数据的副本
    int count(); //返回 m_data 中元素的个数
};

#endif // DATASOURCE_H

DataSource.cpp

复制代码
#include "DataSource.h"
#include <QFile>
#include <QTextStream>
#include <QStringList>
#include <QDebug>

DataSource::DataSource(QObject* parent) : QObject(parent)
{

}

bool DataSource::setDataPath(QString path) //设置数据源文件路径
{
    bool ret = true;
    QFile file(path); //创建文件对象
    if( file.open(QIODevice::ReadOnly | QIODevice::Text) )
    {
        // QTextStream in(&file);
        // while( !in.atEnd() )
        // {
        //     ScoreInfo info;
        //     if( parse(in.readLine(), info) )
        //     {
        //         m_data.append(info);
        //     }
        // }

        QString data(file.readLine());
        while(data.length()) {

            ScoreInfo info;
            if( parse(data, info) )
            {
                m_data.append(info);
            }
            data = file.readLine();
        }

        file.close();
    }
    else
    {
        ret = false;
    }

    qDebug() << "ret: " << ret;
    qDebug() << "path: " << path << file.exists();
    qDebug() << "file.open(QIODevice::ReadOnly | QIODevice::Text): " << file.open(QIODevice::ReadOnly | QIODevice::Text);
    return ret;
}
bool DataSource::parse(QString line, ScoreInfo& info)
{
    bool ret = true;
    QStringList list = line.split(",", QString::SkipEmptyParts); // 按逗号分割字符串,忽略空部分

    if( list.count() == 3 )
    {
        QString id = list[0].trimmed();
        QString name = list[1].trimmed();
        QString score = list[2].trimmed();
        int value = score.toInt(&ret);

        qDebug()<<"list:"<<line<<list;

        if( ret && (0 <= value) && (value <= 150) )
        {
            info = ScoreInfo(id, name, value);
        }
        else
        {
            ret = false;
        }
    }
    else
    {
        ret = false;
    }
    return ret;
}
QList<ScoreInfo> DataSource::fetchData() //返回所有数据的副本
{
    QList<ScoreInfo> ret = m_data; // 复制数据
    m_data.clear(); // 清空内部数据
    return ret;
}
int DataSource::count() //返回 m_data 中元素的个数
{
    return m_data.count();
}

ScoreInfo.h

复制代码
#ifndef SCOREINFO_H
#define SCOREINFO_H

#include <QObject>

class ScoreInfo : public QObject
{
    Q_OBJECT

    QString m_id;
    QString m_name;
    int m_score;

public:
    explicit ScoreInfo(QObject *parent = nullptr);
    explicit ScoreInfo(QString id, QString name, int score, QObject *parent = nullptr);
    ScoreInfo(const ScoreInfo& obj);
    ScoreInfo& operator= (const ScoreInfo& obj);
    QString id();
    QString name();
    int score();

signals:
};

#endif // SCOREINFO_H

ScoreInfo.cpp

复制代码
#include "ScoreInfo.h"

ScoreInfo::ScoreInfo(QObject *parent) : QObject{parent}
{
    m_id = "NULL";
    m_name = "NULL";
    m_score = -1;
}

ScoreInfo::ScoreInfo(QString id, QString name, int score, QObject *parent)
{
    m_id = id;
    m_name = name;
    m_score = score;
}
ScoreInfo::ScoreInfo(const ScoreInfo& obj)
{
    m_id = obj.m_id;
    m_name = obj.m_name;
    m_score = obj.m_score;

}
ScoreInfo& ScoreInfo::operator= (const ScoreInfo& obj)
{
    if( this != &obj )
    {
        m_id = obj.m_id;
        m_name = obj.m_name;
        m_score = obj.m_score;
    }
    return *this;
}
QString ScoreInfo::id()
{
    return m_id;
}
QString ScoreInfo::name()
{
    return m_name;
}
int ScoreInfo::score()
{
    return m_score;
}

ScoreInfoModel.h

复制代码
#ifndef SCOREINFOMODEL_H
#define SCOREINFOMODEL_H

#include <QObject>
#include <QStandardItem>
#include "ScoreInfo.h"
#include <QTableView>
#include <QList>

class ScoreInfoModel : public QObject
{
    Q_OBJECT

    QStandardItemModel m_model;
public:
    explicit ScoreInfoModel(QObject *parent = nullptr);
    bool add(ScoreInfo info); //加入数据对象
    bool remove(int i);
    bool add(QList<ScoreInfo> list);
    ScoreInfo getItem(int i);
    int count();
    void clear();
    void setView(QTableView& view);


signals:
};

#endif // SCOREINFOMODEL_H

ScoreInfoModel.cpp

复制代码
#include "ScoreInfoModel.h"

ScoreInfoModel::ScoreInfoModel(QObject *parent) : QObject{parent}
{

}
bool ScoreInfoModel::add(ScoreInfo info)
{
    QStandardItem* root = m_model.invisibleRootItem();
    QStandardItem* item0 = new QStandardItem();
    QStandardItem* item1 = new QStandardItem();
    QStandardItem* item2 = new QStandardItem();
    bool ret = true;
    if( m_model.rowCount()==0 )
    {
        QStringList list;

        list.append("ID");
        list.append("Name");
        list.append("Score");

        m_model.setHorizontalHeaderLabels(list); //设置表格的水平表头标签
    }
    if( (root != NULL) && (item0 != NULL) && (item1 != NULL) && (item2 != NULL) )
    {
        item0->setData(info.id(), Qt::DisplayRole);
        item1->setData(info.name(), Qt::DisplayRole);
        item2->setData(info.score(), Qt::DisplayRole);

        //设置不可编辑
        item0->setEditable(false);
        item1->setEditable(false);
        item2->setEditable(false);

        int newRow = count(); //新索引行

        root->setChild(newRow, 0, item0);
        root->setChild(newRow, 1, item1);
        root->setChild(newRow, 2, item2);
    }
    else
    {
        ret = false;
    }
    return ret;
}
bool ScoreInfoModel::remove(int i)
{
    bool ret = true;

    if( (0 <= i) && (i < count()) )
    {
        m_model.removeRow(i);
    }
    else
    {
        ret = false;
    }
    return ret;
}
ScoreInfo ScoreInfoModel::getItem(int i)
{
    ScoreInfo ret;
    if( (0 <= i) && (i < count()) )
    {
        QModelIndex index0 = m_model.index(i, 0, QModelIndex());
        QModelIndex index1 = m_model.index(i, 1, QModelIndex());
        QModelIndex index2 = m_model.index(i, 2, QModelIndex());
        QVariant v0 = index0.data(Qt::DisplayRole); //获取学号
        QVariant v1 = index1.data(Qt::DisplayRole); //获取姓名
        QVariant v2 = index2.data(Qt::DisplayRole); //获取分数

        ret = ScoreInfo(v0.toString(), v1.toString(), v2.toInt());
    }
    return ret;
}
int ScoreInfoModel::count()
{
    return m_model.rowCount();
}
void ScoreInfoModel::setView(QTableView& view)
{
    view.setModel(&m_model); // 将内部模型设置给表格视图
}
bool ScoreInfoModel::add(QList<ScoreInfo> list)
{
    bool ret = true;

    for(int i=0; i<list.count(); i++)
    {
        ret = ret && add(list[i]);
    }

    return ret;
}
void ScoreInfoModel::clear()
{
    m_model.clear();
}

运行结果:

相关推荐
MATLAB代码顾问1 小时前
Python数据分析项目实战:销售数据仪表盘
开发语言·python·数据分析
码云骑士1 小时前
07-Python装饰器从入门到源码(下)-带参数装饰器与wraps
开发语言·python
LAM LAB1 小时前
【Web】网页如何模拟移动端获取定位\定位模拟测试
开发语言·前端·javascript
小糯米6011 小时前
C语言文件操作
c语言·开发语言·数据结构
caimouse1 小时前
Reactos 第 9 章 设备驱动 — 9.4 内核劳务线程
开发语言·windows
Doker 多克1 小时前
Spring AI Alibaba—快速构建ReactAgent
java·开发语言·前端·ai编程
张忠琳1 小时前
【Go 1.26.4】Golang Slice 深度解析
开发语言·后端·golang
码云骑士2 小时前
09-Python模块导入机制-sys.path与循环导入的死锁式排查
开发语言·python
星恒随风2 小时前
C++ 模板初阶:从泛型编程、函数模板到类模板,一篇打通基础概念
开发语言·c++·笔记·学习