需求
- 在本地文件中读取学生分数信息,按照一定规则显示到界面中
- 支持数据动态加载,即本地文件更改后,可以动态地在界面上显示
- ......支持分数平均等小功能
本案例采用MVC设计模式,采用四层架构,从下往上即数据层(Data Source)、数据表示层(Data Object)、数据组织层(Model)、数据显示层(View)
系统架构图:
核心类图:
DataSource类设计:
——设置数据源并读取数据
——对数据解析后生成数据对象
DataSource.h
#ifndef DATASOURCE_H /* *数据源 */ #ifndef DATASOURCE_H #define DATASOURCE_H #include <QObject> #include <QList> #include "ScoreInfo.h" class DataSource : public QObject { Q_OBJECT public: explicit DataSource(QObject *parent = 0); /* * @bref: 设置数据源 * @param: 数据源路径(此处为待处理文本文件路径) * @return: 成功返回true,反之false */ bool setDataPath(QString path); /* * @bref: 导出数据 * @param 无 * @return: QList<ScoreInfo>,数据对象列表 */ QList<ScoreInfo> fetchData(); /* * @bref: 返回数据条数 * @param: 无 * @return: 数据条数 */ int count(); private: /* * @bref: 解析从数据源读取的数据 * @param 数据在文本中的行数,info为存取该条数据的对象,这里这里info一个引用,是输出 * @return: 成功返回true,反之false */ bool parse(QString line/*in*/, ScoreInfo& info/*out*/); private: QList<ScoreInfo> m_data;//存放解析来的数据 }; #endif // DATASOURCE_H
DataSource.cpp
#include <QDir> #include <QTextStream> #include "DataSource.h" 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)) //调用parse解析 { m_data.append(info); } } file.close(); } else { ret = false; } return ret; } QList<ScoreInfo> DataSource::fetchData() { QList<ScoreInfo> ret = m_data; m_data.clear(); //这一步主要是考虑到取出数据之后,这个容器的数据就不再使用,故清除。节省空间(如果数据来自网络) return ret; } int DataSource::count() { return m_data.count(); } bool DataSource::parse(QString line, ScoreInfo &info) { bool ret = true; QStringList list = line.split(",", QString::SkipEmptyParts);//以","分割数据,去除空格 if(list.count() == 3) { QString name = list[0].trimmed(); QString course = list[1].trimmed(); QString score = list[2].trimmed(); int value = score.toInt(&ret); if(ret && (0 <= value && value <= 150)) { info = ScoreInfo(name, course, value);//因为有这一步,故ScoreInfo需要重载=操作符 } else { ret = false; } } else { ret = false; } return ret; }
ScoreInfo类的设计:
——封装数据源中的一组完整数据
——暴露返回具体数值的接口函数
ScoreInfo.h
#ifndef SCOREINFO_H #define SCOREINFO_H #include <QObject> #include <QString> class ScoreInfo : public QObject { Q_OBJECT public: explicit ScoreInfo(QObject *parent = 0); explicit ScoreInfo(QString name, QString course, int score, QObject *parent = 0); ScoreInfo(const ScoreInfo& obj); ScoreInfo& operator =(const ScoreInfo& obj); inline QString getName() const {return m_name;} inline QString getCourse() const {return m_course;} inline int getScore() const {return m_score;} private: QString m_name; //姓名 QString m_course; //科目 int m_score; //分数 }; #endif // SCOREINFO_H
ScoreInfo.cpp
#include "ScoreInfo.h" ScoreInfo::ScoreInfo(QObject *parent) : QObject(parent) { } ScoreInfo::ScoreInfo(QString name, QString course, int score, QObject *parent) { m_name = name; m_course = course; m_score = score; } ScoreInfo::ScoreInfo(const ScoreInfo &obj) { m_name = obj.m_name; m_course = obj.m_course; m_score = obj.m_score; } ScoreInfo& ScoreInfo :: operator =(const ScoreInfo &obj) { if(this != &obj) { m_name = obj.m_name; m_course = obj.m_course; m_score = obj.m_score; } return *this; }
ScoreInfoModel类的设计:
——使用标准模型类QStandardItemModel作为成员变量
——使用前面的ScoreInfo类对象为最小单位组织数据
ScoreInfoModel.h
#ifndef SCOREINFOMODEL_H #define SCOREINFOMODEL_H #include <QStandardItemModel> #include <QList> #include <QTableView> #include <QStandardItem> #include <QTableView> #include "ScoreInfo.h" class ScoreInfoModel : public QObject { public: ScoreInfoModel(); /* * @brief:设置视图 * @param:view 外部定义的视图的引用 * @return:无 */ void setView(QTableView& view); /* * @brief:添加一条数据 * @param:view info一条数据 * @return:成功返回true */ bool addData(ScoreInfo info); /* * @brief:添加数据链 * @param:view infos数据链 * @return:成功返回true */ bool addData(QList<ScoreInfo> infos); /* * @brief:移除数据 * @param:view i欲移除对象的下标索引 * @return:成功返回true */ bool remove(int i); /* * @brief:获取一条数据 * @param:view i欲获取对象的下标索引 * @return:返回欲获取的对象 */ ScoreInfo getItem(int i); /* * @brief:当前有多少行数据 * @param:view 无 * @return:数据行数 */ int count(); void clear(); private: QStandardItemModel m_model; }; #endif // SCOREINFOMODEL_H
ScoreInfoModel.cpp
#include "ScoreInfoModel.h" ScoreInfoModel::ScoreInfoModel() { } void ScoreInfoModel::setView(QTableView& view) { view.setModel(&m_model); } bool ScoreInfoModel::addData(ScoreInfo info) { bool ret = true; QStandardItem* root = m_model.invisibleRootItem();//获取虚结点 QStandardItem* item0 = new QStandardItem(); QStandardItem* item1 = new QStandardItem(); QStandardItem* item2 = new QStandardItem(); if((root != nullptr) && (item0 != nullptr) && (item1 != nullptr) && (item2 != nullptr)) { item0->setData(info.getName(), Qt::DisplayRole); item1->setData(info.getCourse(), Qt::DisplayRole); item2->setData(info.getScore(), Qt::DisplayRole); int rowIndex = count(); root->setChild(rowIndex, 0, item0); root->setChild(rowIndex, 1, item1); root->setChild(rowIndex, 2, item2); } else { ret = false; } return ret; } bool ScoreInfoModel::addData(QList<ScoreInfo> infos) { bool ret = true; for(int i=0; i != infos.count(); i++) { ret = ret && addData(infos[i]); } return ret; } bool ScoreInfoModel::remove(int i) { bool ret = false; if(i >= 0 && i < count()) { m_model.removeRow(i); } return ret; } ScoreInfo ScoreInfoModel::getItem(int i) { ScoreInfo ret; if(i >= 0 && 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::clear() { m_model.clear(); }
最后是数据交互时序图:
1.通过setDataPath()设置数据源
2.通过read()从数据源读取数据
3.通过parse()解析数据
4.通过fetchData()返回数据
5.调用add()添加数据到模型中去
其他文件:
数据源文件(位于源文件同级目录下):
test.txt:
Tom,C++,90 Jim,Java,89 Luna,Python,99 Nancy,Go,97
Qt Creator工程文件
StdScore.pro
#------------------------------------------------- # # Project created by QtCreator 2018-03-15T21:48:11 # #------------------------------------------------- QT += core gui CONFIG += c++11 greaterThan(QT_MAJOR_VERSION, 4): QT += widgets TARGET = StdScore TEMPLATE = app # The following define makes your compiler emit warnings if you use # any feature of Qt which as been marked as deprecated (the exact warnings # depend on your compiler). Please consult the documentation of the # deprecated API in order to know how to port your code away from it. DEFINES += QT_DEPRECATED_WARNINGS # You can also make your code fail to compile if you use deprecated APIs. # In order to do so, uncomment the following line. # You can also select to disable deprecated APIs only up to a certain version of Qt. #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 SOURCES += main.cpp\ ScoreInfoModel.cpp \ ScoreInfo.cpp \ DataSource.cpp \ Widget.cpp HEADERS += \ ScoreInfoModel.h \ ScoreInfo.h \ DataSource.h \ Widget.h
主界面文件:
Widget.h
#ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QWidget> #include <QTableView> #include <QPushButton> #include <QMenu> #include "DataSource.h" #include "ScoreInfo.h" #include "ScoreInfoModel.h" class Widget : public QWidget { Q_OBJECT public: Widget(QWidget *parent = 0); ~Widget(); protected: bool eventFilter(QObject* obj, QEvent* evt); private://界面相关 QTableView m_view; //视图, view ScoreInfoModel m_model; //模型, model QPushButton m_refreshBtn; QPushButton m_clearBtn; QPushButton m_scoreBtn; QMenu m_menu; private slots: void onRefreshBtnClicked(); void onClearBtnClicked(); void onScoreBtnClicked(); void onDeleteActionClicked(); private: DataSource m_source; //数据源 QList<ScoreInfo> m_infos; //存放组装好的数据信息 }; #endif // MAINWINDOW_H
Widget.cpp
#include <QDebug> #include <QMessageBox> #include "Widget.h" Widget::Widget(QWidget *parent) : QWidget(parent) { #if 0 m_view.setParent(this); m_view.resize(width(), height()); if(m_source.setDataPath("../StdScore/test.txt")) { m_infos = m_source.fetchData(); for(int i = 0; i != m_infos.size(); i++) { m_model.addData(m_infos[i]); } m_model.setView(m_view); } #endif m_view.setParent(this); m_view.move(10, 10); m_view.resize(345, 180); m_view.setSelectionBehavior(QAbstractItemView::SelectRows); m_view.installEventFilter(this); m_refreshBtn.setParent(this); m_refreshBtn.move(10, 200); m_refreshBtn.resize(95, 30); m_refreshBtn.setText("Refresh"); m_clearBtn.setParent(this); m_clearBtn.move(135, 200); m_clearBtn.resize(95, 30); m_clearBtn.setText("Clear"); m_scoreBtn.setParent(this); m_scoreBtn.move(260, 200); m_scoreBtn.resize(95, 30); m_scoreBtn.setText("Info"); m_menu.addAction("Delete"); m_model.setView(m_view); connect(&m_refreshBtn, SIGNAL(clicked()), this, SLOT(onRefreshBtnClicked())); connect(&m_clearBtn, SIGNAL(clicked()), this, SLOT(onClearBtnClicked())); connect(&m_scoreBtn, SIGNAL(clicked()), this, SLOT(onScoreBtnClicked())); connect(m_menu.actions()[0], SIGNAL(triggered()), this, SLOT(onDeleteActionClicked())); onRefreshBtnClicked(); } Widget::~Widget() { } 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 source; m_model.clear(); if( source.setDataPath("../StdScore/test.txt") ) { m_model.addData(source.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.getScore() < min ) { min = info.getScore(); } if( info.getScore() > max ) { max = info.getScore(); } average += info.getScore(); } 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); } } void Widget::onDeleteActionClicked() { m_model.remove(m_view.currentIndex().row()); }
mian.cpp
#include "Widget.h" #include <QApplication> int main(int argc, char *argv[]) { QApplication a(argc, argv); Widget w; w.show(); return a.exec(); }
运行结果:
扩展:
——可以再引入委托对不同数据的显示做变换
——可以再添加一个从界面编辑回写到数据源的功能,在实际应用中可以根据当前用户的权限来判断是否能使用该功能