1.QStandardItemModel是一个通用的模型类
- 能够以任意方式组织数据(线性、非线性)
- 数据组织的基本单位为数据项(QStandardItem)
- 每一个数据项能够存储多个具体数据(附加数据角色)
- 每一个数据项能够对数据状态进行控制(可编辑、可选...)
2.Qt的变体类型QVariant
-
QVariant是一个用于封装的类型
-
QVariant能够表示大多数常见的值类型
-
QVariant每次只能封装单一类型的值
-
QVariant的意义在于能够设计"返回类型可变的函数"
#include
#include
#include
#includeQVariant 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();
}
运行结果:
