首页 > 代码库 > 第60课 自定义模型类(下)

第60课 自定义模型类(下)

1. 界面GUI设计

技术分享 

2. 界面的类图设计

技术分享 

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

(1)定义菜单对象(QMenu

(2)连接菜单中QAction对象到槽函数

(3)定义事件过滤器,并处理ContextMenu事件

(4)在当前鼠标的位置打开菜单对象

【编程实验】数据应用界面和右键菜单的实现

//main.cpp

技术分享
#include "Widget.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w;
    w.show();

    return a.exec();
}
View Code

//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;

protected slots:
    void onRefreshBtnClicked();
    void onClearBtnClicked();
    void onScoreBtnClicked();
    void onDeleteActionClicked();
public:
    Widget(QWidget *parent = 0);
    bool eventFilter(QObject* obj, QEvent* evt);
    ~Widget();
};

#endif // WIDGET_H

//Widget.cpp

#include "Widget.h"
#include "ScoreInfo.h"
#include "ScoreInfoModel.h"
#include "DataSource.h"
#include <QMessageBox>
#include <QEvent>
#include <QDebug>

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");

    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("Score");

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

void Widget::onRefreshBtnClicked()
{
    DataSource ds;
    m_model.clear();

    if(ds.setDataPath("C:/Users/SantaClaus/Desktop/test.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);
    }
}

bool Widget::eventFilter(QObject* obj, QEvent* evt)
{
    if((obj == &m_view) && (evt->type() == QEvent::ContextMenu))
    {
        m_menu.exec(QCursor::pos());
    }
    return QWidget::eventFilter(obj, evt);
}

void Widget::onDeleteActionClicked()
{
    m_model.remove(m_view.currentIndex().row());
}

Widget::~Widget()
{

}

//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 = 0);
    explicit ScoreInfo(QString id, QString name, int score, QObject *parent = 0);

    //重写复制构造函数和赋值操作符,以便对三个成员变量进行赋值操作
    ScoreInfo(const ScoreInfo& obj);
    ScoreInfo& operator=(const ScoreInfo& obj);
    QString id();
    QString name();
    int score();
};

#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) : QObject(parent)
{
    m_id = id;
    m_name = name;
    m_score = score;
}

ScoreInfo::ScoreInfo(const ScoreInfo& obj):QObject() //不能QObject(obj.parent())
{
    m_id = obj.m_id;
    m_name = obj.m_name;
    m_score = obj.m_score;
}

ScoreInfo& ScoreInfo::operator=(const ScoreInfo& obj)
{
    if(this != &obj)
    {
        //不能setParent(obj.parent()),因为当:
        //ScoreInfo info;
        //info = ScoreInfo(...);时会把临时对象的parent传给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;
}

//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;

    //这里是个小技巧,通过返回值来判断info里保存的数据是否有效
    bool parse(QString line, ScoreInfo& info);
public:
    explicit DataSource(QObject* parent = 0);

    bool setDataPath(QString path);
    QList<ScoreInfo> fetchData();
    int count();


};

#endif // DATASOURCE_H

//DataSource.cpp

#include "DataSource.h"
#include <QFile>
#include <QTextStream>
#include <QStringList>

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

}

bool DataSource::parse(QString line, ScoreInfo& info)
{
    bool ret = false;

    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 = http://www.mamicode.com/score.toInt(&ret);

        if(ret && (0 <= value ) && (value <= 150))
        {
            info = ScoreInfo(id, name, value);
        }
        else
        {
            ret = false;
        }
    }

    return ret;
}

bool DataSource::setDataPath(QString path)
{
    bool ret = false;
    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);
            }
        }
        file.close();
        ret = true;
    }

    return ret;
}

//为什么是fetchData而不是getData?
QList<ScoreInfo> DataSource::fetchData()
{
    QList<ScoreInfo> ret = m_data;

    m_data.clear(); //清空原来的数据,可以腾出空间以便下次获取数据
                    //如果getData这里就不应被清空,这也将导致m_data里
                    //的数据不断增加
    return ret;
}

int DataSource::count()
{
    return m_data.count();
}

//ScoreInfoModel.h

#ifndef SCOREINFOMODEL_H
#define SCOREINFOMODEL_H

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

class ScoreInfoModel : public QObject
{
    Q_OBJECT

    QStandardItemModel m_model;
    void initHorizonHeaderLabels();
public:
    explicit ScoreInfoModel(QObject *parent = 0);
    bool add(ScoreInfo info);
    bool add(QList<ScoreInfo> list);
    bool remove(int index);
    ScoreInfo getItem(int index);
    int count();
    void clear();
    void setView(QTableView& view);
};

#endif // SCOREINFOMODEL_H

//ScoreInfoModel.cpp

#include "ScoreInfoModel.h"

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

void ScoreInfoModel::initHorizonHeaderLabels()
{
    QStringList list;

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

    m_model.setHorizontalHeaderLabels(list);
}

bool ScoreInfoModel::add(ScoreInfo info)
{
    QStandardItem* root = m_model.invisibleRootItem();
    QStandardItem* item0 = new QStandardItem(); //编号
    QStandardItem* item1 = new QStandardItem(); //姓名
    QStandardItem* item2 = new QStandardItem(); //成绩
    bool ret = false;

    if(m_model.rowCount() > 0)
        initHorizonHeaderLabels();

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

        ret = true;
    }

    return ret;
}

bool ScoreInfoModel::add(QList<ScoreInfo> list)
{
    bool ret = true;

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

    return ret;
}

bool ScoreInfoModel::remove(int index)
{
    bool ret = false;
    if( (0 <= index) && (index <count()))
    {
        m_model.removeRow(index);
        ret = true;
    }

    return ret;
}

ScoreInfo ScoreInfoModel::getItem(int index)
{
    ScoreInfo ret;

    if( (0 <= index) && (index <count()))
    {
        QModelIndex idx0 = m_model.index(index, 0, QModelIndex());
        QModelIndex idx1 = m_model.index(index, 1, QModelIndex());
        QModelIndex idx2 = m_model.index(index, 2, QModelIndex());
        QVariant v0 = idx0.data(Qt::DisplayRole);
        QVariant v1 = idx1.data(Qt::DisplayRole);
        QVariant v2 = idx2.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();
}

void ScoreInfoModel::setView(QTableView& view)
{
    view.setModel(&m_model);
}

4. 小结

(1)数据源类(DataSource)用于抽象表示数据的来源

(2)模型类(Model)用于从数据源获取数据并组织

(3)视图类(View)用于显示模型中的数据

(4)数据应用4层架构设计非常易于扩展和维护

 

第60课 自定义模型类(下)