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();
}

运行结果:

相关推荐
Scott9999HH7 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
码智社8 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海8 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖9 小时前
JDK 26 新特性详解
java·开发语言
马优晨9 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
人邮异步社区11 小时前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大12 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai12 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
এ慕ོ冬℘゜12 小时前
前端基础:什么是时间戳?JS获取时间戳三种方法与实战用途
开发语言·前端·javascript
执明wa12 小时前
LayoutInflater详解: XML是如何变成View的?
android·xml·开发语言·android studio