Qt模型视图(MV)小案例

需求
  • 在本地文件中读取学生分数信息,按照一定规则显示到界面中
  • 支持数据动态加载,即本地文件更改后,可以动态地在界面上显示
  • ......支持分数平均等小功能

本案例采用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();
}
运行结果:


扩展:
——可以再引入委托对不同数据的显示做变换
——可以再添加一个从界面编辑回写到数据源的功能,在实际应用中可以根据当前用户的权限来判断是否能使用该功能


猜你喜欢

转载自blog.csdn.net/naughfy/article/details/79834849