• QT中Model-View-Delegate委托代理机制用法介绍



    之前的一篇文章中介绍过QT的委托代理机制,那时候由于理解的比较浅就简单的给了一个例子。最近又做了一部分相关的工作,发现之前的理解有点问题。这里就详细的介绍一下QT的委托代理机制的用法,希望对大家有帮助。

    Model-View-Delegate机制可以简单的理解为将本地的一些数据以特定的UI形式呈现出来。常见的数据结构包括列表数据(list)、表格数据(table)、树状数据(tree),分别对应着QT中的QListView、QTableView、QTreeView控件。本地数据和视图代理之间的关系如下图所示:
    在这里插入图片描述

    数据模型中的数据来源可以是本地的XML文件、JSON文件、二进制数据,也可以数据库中的数据表。这些数据源中的数据按照一定的结构加载到对应的数据模型中,我们可以通过操作数据模型中的数据来间接的操作数据源中的数据。

    有时候,我们需要对数据模型中的数据进行二次处理,包括数据筛选、数据排序、数据处理等等,这时候我们就得需要引入模型代理,负责对数据模型进行处理。当然模型代理不是必须的。QT中的模型代理有两种都是QAbstractProxyModel的子类。分别是QIdentityProxyModel和QSortFilterProxyModel。
    QIdentityProxyModel代理不会修改原有的数据模型,只是重写了data()函数,对返回视图的数据进行了重新组合和修改。
    QSortFilterProxyModel代理会对模型的数据进行筛选和排序。
    有了这两个代理类,我们就可以对模型中的数据进行处理了。

    数据模型加载完毕数据之后,View层就会对数据模型中的数据进行呈现了。由于数据模型中的数据都是以一个个数据单元存在的,我们可以为每个数据单元指定对应的UI。这就用到了委托代理Delegate,委托控件可以给数据模型中的每一个元素指定固定的UI。通过委托代理的机制,我们就可以以个性的图形界面呈现本地数据了。

    下面以一个详细的例子,来说明一下委托代理机制的用法。例子主要功能是以缩略图的形式对本地的图片文件进行管理,类似于一个图片管理器。

    本地数据加载(Data)

    例子中的图片数据主要包含两个字段,一个字段是图片的ID,另一个字段是图片的URL。对应的数据结构如下所示:

    //Picture
    class Picture
    {
    public:
        Picture(const QString & filePath = "")
        {
             mPictureUrl = QUrl::fromLocalFile(filePath);
        }
        Picture(const QUrl& fileUrl)
        {
            mPictureUrl = fileUrl;
        }
    
        int pictureId() const
        {
            return mPictureId;
        }
        void setPictureId(int pictureId)
        {
            mPictureId = pictureId;
        }
    
        QUrl pictureUrl() const
        {
            return mPictureUrl;
        }
        void setPictureUrl(const QUrl &pictureUrl)
        {
            mPictureUrl = pictureUrl;
        }
    
    private:
        int mPictureId;   // 图片ID
        QUrl mPictureUrl;  //图片的地址
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    由于本地的图片数据可能会很多,为了方便对大量的图片数据进行管理,这里我们采用SQLITE数据库对图片信息进行本地持久化。首先,我们新建数据库管理类,管理数据库连接。

    //DatabaseManager.h
    #ifndef DATABASEMANAGER_H
    #define DATABASEMANAGER_H
    
    #include <memory>
    #include <QString>
    #include "PictureDao.h"
    
    class QSqlQuery;
    class QSqlDatabase;
    
    const QString DATABASE_FILENAME = "picture.db";
    
    class DatabaseManager
    {
    public:
        static void debugQuery(const QSqlQuery& query);
        //数据库管理类是单例模式
        static DatabaseManager& instance();
        ~DatabaseManager();
    
    protected:
        //用来构建固定名称的数据库
        DatabaseManager(const QString& path = DATABASE_FILENAME);
        DatabaseManager& operator=(const DatabaseManager& rhs);
    
    private:
        std::unique_ptr<QSqlDatabase> mDatabase;
    
    public:
        //图片数据操作类
        const PictureDao mpictureDao;
    };
    
    #endif // DATABASEMANAGER_H
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    //DatabaseManager.cpp
    #include "DatabaseManager.h"
    
    #include <QSqlDatabase>
    #include <QDebug>
    #include <QSqlError>
    #include <QSqlQuery>
    
    void DatabaseManager::debugQuery(const QSqlQuery& query)
    {
        if (query.lastError().type() == QSqlError::ErrorType::NoError) {
            qDebug() << "Query OK:"  << query.lastQuery();
        } else {
           qWarning() << "Query KO:" << query.lastError().text();
           qWarning() << "Query text:" << query.lastQuery();
        }
    }
    
    DatabaseManager&DatabaseManager::instance()
    {
        static DatabaseManager singleton;
        return singleton;
    }
    
    DatabaseManager::DatabaseManager(const QString& path) :
        mDatabase(new QSqlDatabase(QSqlDatabase::addDatabase("QSQLITE"))),
        mpictureDao(*mDatabase)
    {
        mDatabase->setDatabaseName(path);
        bool openStatus = mDatabase->open();
        qDebug() << "Database connection: " << (openStatus ? "OK" : "Error");
        mpictureDao.init();
    }
    
    DatabaseManager::~DatabaseManager()
    {
        mDatabase->close();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    完成数据库管理类的创建之后,我们需要添加图片数据表的数据库访问对象,访问对象负责完成对图片数据表的增删改查等基本操作,对应的实现如下所示:

    //PictureDao.h
    #ifndef PICTUREDAO_H
    #define PICTUREDAO_H
    
    #include <memory>
    #include <vector>
    
    class QSqlDatabase;
    class Picture;
    class PictureDao
    {
    public:
        explicit PictureDao(QSqlDatabase& database);
        void init() const;
        //添加图片
        void addPicture(Picture& picture) const;
        //删除图片
        void removePicture(int id) const;
        //加载图片
        std::unique_ptr<std::vector<std::unique_ptr<Picture>>> loadPictures() const;
        //删除所有的数据
        void removeAllPictures() const;
    private:
        QSqlDatabase& mDatabase;
    };
    
    #endif // PICTUREDAO_H
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    //PictureDao.cpp
    #include "PictureDao.h"
    #include <QSqlDatabase>
    #include <QSqlQuery>
    #include <QVariant>
    #include "DatabaseManager.h"
    #include "picturemodel.h"
    
    using namespace std;
    
    PictureDao::PictureDao(QSqlDatabase& database) :
        mDatabase(database)
    {
    }
    
    void PictureDao::init() const
    {
        if (!mDatabase.tables().contains("pictures")) {
            QSqlQuery query(mDatabase);
            query.exec(QString("CREATE TABLE pictures")
            + " (id INTEGER PRIMARY KEY AUTOINCREMENT, "
            + "url TEXT)");
            DatabaseManager::debugQuery(query);
        }
    }
    
    void PictureDao::addPicture(Picture& picture) const
    {
        QSqlQuery query(mDatabase);
        query.prepare(QString("INSERT INTO pictures")
            + " (url)"
            + " VALUES ("       
            + ":url"
            + ")");
        query.bindValue(":url", picture.pictureUrl());
        query.exec();
        DatabaseManager::debugQuery(query);
        picture.setPictureId(query.lastInsertId().toInt());
    }
    
    void PictureDao::removePicture(int id) const
    {
        QSqlQuery query(mDatabase);
        query.prepare("DELETE FROM pictures WHERE id = (:id)");
        query.bindValue(":id", id);
        query.exec();
        DatabaseManager::debugQuery(query);
    }
    
    unique_ptr<vector<unique_ptr<Picture>>> PictureDao::loadPictures() const
    {
        QSqlQuery query(mDatabase);
        query.prepare("SELECT * FROM pictures");
        query.exec();
        DatabaseManager::debugQuery(query);
        unique_ptr<vector<unique_ptr<Picture>>> list(new vector<unique_ptr<Picture>>());
        while(query.next()) {
            unique_ptr<Picture> picture(new Picture());
            picture->setPictureId(query.value("id").toInt());
            picture->setPictureUrl(query.value("url").toString());
            list->push_back(move(picture));
        }
        return list;
    }
    
    void PictureDao::removeAllPictures() const
    {
        QSqlQuery query(mDatabase);
        query.prepare("DELETE FROM pictures WHERE 1=1");
        query.exec();
        DatabaseManager::debugQuery(query);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72

    完成数据访问层的构建之后,我们的应用就具备了对底层原始数据操作的能力。这个是应用的基础能力。

    添加数据模型(Model)

    完成了数据操作类之后,接下来我们就需要构建对应的数据模型了。由于图片信息之间是没有关联关系的所以这里采用的是基于QAbstractListModel的列表数据模型,对应的实现如下所示:

    //picturemodel.h
    #ifndef PICTUREMODEL_H
    #define PICTUREMODEL_H
    
    #include <memory>
    #include <vector>
    #include <QAbstractListModel>
    #include <QUrl>
    #include "DatabaseManager.h"
    class PictureModel : public QAbstractListModel
    {
        Q_OBJECT
    public:
        //自定义每个元素的数据类型
        enum Roles {
            UrlRole = Qt::UserRole + 1,
            FilePathRole
        };
    
        PictureModel(QObject* parent = 0);
        //向数据模型中添加单个数据
        QModelIndex addPicture(const Picture& picture);
        Q_INVOKABLE void addPictureFromUrl(const QUrl& fileUrl);
    
        //模型的行数
        int rowCount(const QModelIndex& parent = QModelIndex()) const override;
    
        //获取某个元素的数据
        QVariant data(const QModelIndex& index, int role) const override;
    
        //删除某几行数据
        Q_INVOKABLE bool removeRows(int row, int count, const QModelIndex& parent = QModelIndex()) override;
    
        //每个元素类别的名称
        QHash<int, QByteArray> roleNames() const override;
    
        //加载用户图片
        Q_INVOKABLE void loadPictures();
    
        //清空模型的中的数据,但不移除本地文件数据
        void clearPictures();
    
    public slots:
        //清空模型,删除本地文件中的数据
        void deleteAllPictures();
    
    private:
        void resetPictures();
        bool isIndexValid(const QModelIndex& index) const;
    
    private:
        DatabaseManager& mDatabase;
        std::unique_ptr<std::vector<std::unique_ptr<Picture>>> mPictures;
    };
    
    #endif // PICTUREMODEL_H
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    //picturemodel.cpp
    #include "picturemodel.h"
    #include <QUrl>
    
    using namespace std;
    PictureModel::PictureModel(QObject* parent) :
        QAbstractListModel(parent),
        mPictures(new vector<unique_ptr<Picture>>()),
        mDatabase(DatabaseManager::instance())
    {
    }
    
    QModelIndex PictureModel::addPicture(const Picture& picture)
    {
        int rows = rowCount();
        beginInsertRows(QModelIndex(), rows, rows);
        unique_ptr<Picture>newPicture(new Picture(picture));
        mDatabase.mpictureDao.addPicture(*newPicture);
        mPictures->push_back(move(newPicture));
        endInsertRows();
        return index(rows, 0);
    }
    
    void PictureModel::addPictureFromUrl(const QUrl& fileUrl)
    {
        addPicture(Picture(fileUrl));
    }
    
    int PictureModel::rowCount(const QModelIndex& /*parent*/) const
    {
        return mPictures->size();
    }
    
    QVariant PictureModel::data(const QModelIndex& index, int role) const
    {
        if (!isIndexValid(index))
        {
            return QVariant();
        }
    
        const Picture& picture = *mPictures->at(index.row());
        switch (role) {
            //展示数据为图片的名称
            case Qt::DisplayRole:
                return picture.pictureUrl().fileName();
                break;
            //图片的URL
            case Roles::UrlRole:
                return picture.pictureUrl();
                break;
            //图片地址
            case Roles::FilePathRole:
                return picture.pictureUrl().toLocalFile();
                break;
    
            default:
                return QVariant();
        }
    }
    
    bool PictureModel::removeRows(int row, int count, const QModelIndex& parent)
    {
        if (row < 0
                || row >= rowCount()
                || count < 0
                || (row + count) > rowCount()) {
            return false;
        }
    
        beginRemoveRows(parent, row, row + count - 1);
        int countLeft = count;
        while(countLeft--) {
            const Picture& picture = *mPictures->at(row + countLeft);
            mDatabase.mpictureDao.removePicture(picture.pictureId());
        }
        mPictures->erase(mPictures->begin() + row,
                        mPictures->begin() + row + count);
        endRemoveRows();
        return true;
    }
    
    QHash<int, QByteArray> PictureModel::roleNames() const
    {
        QHash<int, QByteArray> roles;
        roles[Qt::DisplayRole] = "name";
        roles[Roles::FilePathRole] = "filepath";
        roles[Roles::UrlRole] = "url";
        return roles;
    }
    
    void PictureModel::loadPictures()
    {
        beginResetModel();
        mPictures = mDatabase.mpictureDao.loadPictures();
        endResetModel();
    }
    
    void PictureModel::clearPictures()
    {
        resetPictures();
    }
    
    void PictureModel::resetPictures()
    {   
        beginResetModel();
        mPictures.reset(new vector<unique_ptr<Picture>>());
        endResetModel();
        return;
    }
    
    void PictureModel::deleteAllPictures()
    {
        mDatabase.mpictureDao.removeAllPictures();
        resetPictures();
    }
    
    bool PictureModel::isIndexValid(const QModelIndex& index) const
    {
        if (index.row() < 0
                || index.row() >= rowCount()
                || !index.isValid()) {
            return false;
        }
        return true;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125

    QT允许开发者针对数据模型中的每个数据单元ModelIndex定义不同的数据角色。简单来说,就是每个数据单元可以提供各种类型的供外部使用的数据。这里我们定义了UrlRole和FilePathRole分别代表着图片的URL和图片的地址。

    添加代理模型(Proxy)

    模型代理就是对原始模型中的数据进行二次处理,包括排序筛选等等操作。模型代理不能直接修改模型中的数据,只是负责对数据模型中的数据进行二次处理操作。同时模型代理也不是必须的,我们也可以直接用原始的数据模型和视图进行交互。模型代理对应的实现如下所示:

    //picproxymodel.h
    #ifndef PICTURE_PROXY_MODEL_H
    #define PICTURE_PROXY_MODEL_H
    
    #include <QIdentityProxyModel>
    #include <QHash>
    #include <QPixmap>
    
    class PictureModel;
    class PictureProxyModel : public QIdentityProxyModel
    {
    public:
        PictureProxyModel(QObject* parent = 0);
        //通过重写data接口对数据进行二次处理
        QVariant data(const QModelIndex& index, int role) const override;
        //设置获取源数据模型
        void setSourceModel(QAbstractItemModel* sourceModel) override;
        PictureModel* pictureModel() const;
    
    private:
        //重新加载缩略图
        void reloadPictures();
        //生成缩略图
        void generatePictures(const QModelIndex& startIndex, int count);
    
    private:
       QHash<QString, QPixmap*>mPictureHashMaps;
    
    };
    
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    //picproxymodel.cpp
    #include "picproxymodel.h"
    #include "PictureModel.h"
    
    const unsigned int PICTURE_SIZE = 350;
    
    PictureProxyModel::PictureProxyModel(QObject* parent) :
        QIdentityProxyModel(parent),
        mPictureHashMaps()
    {
    }
    
    QVariant PictureProxyModel::data(const QModelIndex& index, int role) const
    {
        //对原始数据模型中的数据进行二次加工处理
        //供前端调用
        if (role != Qt::DecorationRole) {
            return QIdentityProxyModel::data(index, role);
        }
    
        QString filepath = sourceModel()->data(index, PictureModel::Roles::FilePathRole).toString();
        return *mPictureHashMaps[filepath];
    }
    
    void PictureProxyModel::setSourceModel(QAbstractItemModel* sourceModel)
    {
        QIdentityProxyModel::setSourceModel(sourceModel);
        if (!sourceModel) {
            return;
        }
        connect(sourceModel, &QAbstractItemModel::modelReset, [this] {reloadPictures();});
        connect(sourceModel, &QAbstractItemModel::rowsInserted, [this](const QModelIndex& /*parent*/, int first, int last) {
            generatePictures(index(first, 0), last - first + 1);
        });
    }
    
    PictureModel* PictureProxyModel::pictureModel() const
    {
        return static_cast<PictureModel*>(sourceModel());
    }
    
    void PictureProxyModel::reloadPictures()
    {
        qDeleteAll(mPictureHashMaps);
        mPictureHashMaps.clear();
        generatePictures(index(0, 0), rowCount());
    }
    
    void PictureProxyModel::generatePictures(const QModelIndex& startIndex, int count)
    {
        if (!startIndex.isValid()) {
            return;
        }
        const QAbstractItemModel* model = startIndex.model();
        int lastIndex = startIndex.row() + count;
        for(int row = startIndex.row(); row < lastIndex; row++) {
            QString filepath = model->data(model->index(row, 0), PictureModel::Roles::FilePathRole).toString();
            QPixmap pixmap(filepath);
            auto thumbnail = new QPixmap(pixmap.scaled(PICTURE_SIZE, PICTURE_SIZE,Qt::KeepAspectRatio,
                                                 Qt::SmoothTransformation));
            mPictureHashMaps.insert(filepath, thumbnail);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63

    添加元素的代理(Delegate)

    元素代理就是数据表中每个元素对应的UI,我们通过自定义的控件来呈现对应的数据。这里我们采用的是QStyledItemDelegate而不是QItemDelegate,是因为QStyledItemDelegate支持样式表的操作,而QItemDelegate不支持,对应的实现如下所示:

    //picturedelegate.h
    #ifndef PICTUREDELEGATE_H
    #define PICTUREDELEGATE_H
    
    #include <QStyledItemDelegate>
    #include <QMouseEvent>
    
    class PictureDelegate : public QStyledItemDelegate
    {
        Q_OBJECT
    public:
        PictureDelegate(QObject* parent = 0);
        //代理的绘制事件
        void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override;
        //代理的尺寸
        QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const override;
    
    protected:
    
    };
    
    #endif // PICTUREDELEGATE_H
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    //picturedelegate.cpp
    #include "picturedelegate.h"
    #include <QPainter>
    
    //标题栏的尺寸样式
    const unsigned int LABEL_HEIGHT = 20;
    const unsigned int LABEL_COLOR = 0x303030;
    const unsigned int LABEL_ALPHA = 200;
    const unsigned int LABEL_TEXT_COLOR = 0xffffff;
    const unsigned int HIGHLIGHT_ALPHA = 100;
    
    //图片的尺寸样式
    const unsigned int PIXMAP_WIDTH = 200;
    const unsigned int PIXMAP_HEIGHT = 200;
    
    PictureDelegate::PictureDelegate(QObject* parent) :
        QStyledItemDelegate(parent)
    {
    }
    
    void PictureDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
    {
        painter->save();
    
        //绘制对应的图片
        QPixmap pixmap = index.model()->data(index, Qt::DecorationRole).value<QPixmap>();
        painter->drawPixmap(option.rect.x(), option.rect.y(),PIXMAP_WIDTH,PIXMAP_HEIGHT,pixmap);
    
        //绘制图片的标题栏显示图片名称
        QRect bannerRect = QRect(option.rect.x(), option.rect.y(), PIXMAP_WIDTH, LABEL_HEIGHT);
        QColor bannerColor = QColor(LABEL_COLOR);
        bannerColor.setAlpha(LABEL_ALPHA);
        painter->fillRect(bannerRect, bannerColor);
    
        //绘制标题文字
        QString filename = index.model()->data(index, Qt::DisplayRole).toString();
        painter->setPen(LABEL_TEXT_COLOR);
        painter->drawText(bannerRect, Qt::AlignCenter, filename);
    
        //设置元素被选中之后的颜色
        if (option.state.testFlag(QStyle::State_Selected)) {
            QColor selectedColor = option.palette.highlight().color();
            selectedColor.setAlpha(HIGHLIGHT_ALPHA);
            painter->fillRect(option.rect, selectedColor);
        }
    
        painter->restore();
    }
    
    QSize PictureDelegate::sizeHint(const QStyleOptionViewItem& /*option*/, const QModelIndex& index) const
    {
        const QPixmap& pixmap = index.model()->data(index, Qt::DecorationRole).value<QPixmap>();
        return QSize(PIXMAP_WIDTH,PIXMAP_HEIGHT);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54

    我们也可以通过实现QStyledItemDelegate::createEditor()接口,来对每一个元素代理中的数据进行编辑,这里就不详细介绍了,之前的文章中写过。

    添加视图层(View)

    完善了数据模型和元素代理之后,对应的视图层操作就比较简单了。视图层我们添加了和用户交互的接口,用户可以通过对应的UI操作,对数据模型中的数据进行增删改查。同时视图中我们为元素添加了菜单,我们可以通过右键菜单来删除某个特定的元素。

    //mylistview.h
    #ifndef MYLISTVIEW_H
    #define MYLISTVIEW_H
    
    #include <QWidget>
    #include <QItemSelectionModel>
    #include <QMouseEvent>
    #include <QMenu>
    
    namespace Ui {
    class MyListView;
    }
    class PictureProxyModel;
    class MyListView : public QWidget
    {
        Q_OBJECT
    
    public:
        explicit MyListView(QWidget *parent = 0);
        ~MyListView();
    
        //设置数据模型
        void setPictureModel(PictureProxyModel *pictureModel);
        //设置选中的数据模型
        void setPictureSelectionModel(QItemSelectionModel *selectionModel);
    
    private slots:
        void addPictures();
        void delPictures();
        void clearPictures();
        void delAllPicture();
        void delCurrentPicture();
        void showCustomMenu(const QPoint& pos);
    
    private:
        Ui::MyListView *ui;
    
        //图片数据模型
        PictureProxyModel* mPictureModel;
        //选中元素的数据模型
        QItemSelectionModel* mPictureSelectionModel;
        QModelIndex mCurrentIndex;
        QMenu* m_func_menu = nullptr;
        QAction* m_del_current_pic = nullptr;
    };
    
    #endif // MYLISTVIEW_H
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    //mylistview.cpp
    #pragma execution_character_set("utf-8")
    #include "mylistview.h"
    #include "picturedelegate.h"
    #include "picproxymodel.h"
    #include "ui_mylistview.h"
    #include "picturemodel.h"
    #include <QFileDialog>
    #include <QInputDialog>
    #include <QStandardPaths>
    
    
    MyListView::MyListView(QWidget *parent) :
        QWidget(parent),
        ui(new Ui::MyListView)
    {
        ui->setupUi(this);
    
        //设置元素之间的间隔
        ui->pic_list_view->setSpacing(5);
        //设置尺寸变化策略
        ui->pic_list_view->setResizeMode(QListView::Adjust);
        //设置元素增减的时候的变化模式
        ui->pic_list_view->setFlow(QListView::LeftToRight);
        //设置伸缩的时候是否自动换行
        ui->pic_list_view->setWrapping(true);
        //设置每个元素的代理
        ui->pic_list_view->setItemDelegate(new PictureDelegate(this));
    
        //开启自定义的菜单
        ui->pic_list_view->setContextMenuPolicy(Qt::CustomContextMenu);
    
        //初始化功能菜单
        m_func_menu = new QMenu(this);
        m_del_current_pic = new QAction("删除当前图片",this);
        m_func_menu->addAction(m_del_current_pic);
        connect(m_del_current_pic,&QAction::triggered,this,&MyListView::delCurrentPicture);
    
        //对图片数据进行增删改查
        connect(ui->add_pic_btn, &QPushButton::clicked, this, &MyListView::addPictures);
        connect(ui->clear_btn, &QPushButton::clicked,this, &MyListView::clearPictures);
        connect(ui->del_pic_btn, &QPushButton::clicked, this, &MyListView::delPictures);
        connect(ui->del_all_pic_btn,&QPushButton::clicked,this,&MyListView::delAllPicture);
        connect(ui->pic_list_view,&QListView::customContextMenuRequested,this,&MyListView::showCustomMenu);
    }
    
    MyListView::~MyListView()
    {
        delete ui;
    }
    
    
    void MyListView::setPictureModel(PictureProxyModel* pictureModel)
    {
        mPictureModel = pictureModel;
        ui->pic_list_view->setModel(pictureModel);
    }
    
    void MyListView::setPictureSelectionModel(QItemSelectionModel* selectionModel)
    {
        mPictureSelectionModel = selectionModel;
        ui->pic_list_view->setSelectionModel(selectionModel);
    }
    
    void MyListView::addPictures()
    {
        QStringList filenames = QFileDialog::getOpenFileNames(this,
                                                              "添加图片",
                                                              QStandardPaths::writableLocation(QStandardPaths::DesktopLocation),
                                                              "Picture files (*.jpg *.png)");
        if (!filenames.isEmpty()) {
            QModelIndex lastModelIndex;
            for (auto filename : filenames) {
                Picture picture(filename);
                lastModelIndex = mPictureModel->pictureModel()->addPicture(picture);
                lastModelIndex = mPictureModel->index(lastModelIndex.row(),lastModelIndex.column());
            }
            if(lastModelIndex.isValid())
            {
                ui->pic_list_view->setCurrentIndex(lastModelIndex);
            }
        }
    }
    
    void MyListView::delPictures()
    {
        if (mPictureSelectionModel->selectedIndexes().isEmpty()) {
            return;
        }
        int row = mPictureSelectionModel->currentIndex().row();
        mPictureModel->sourceModel()->removeRow(row);
    
        //选中前一个图片
        QModelIndex previousModelIndex = mPictureModel->sourceModel()->index(row - 1, 0);
        if(previousModelIndex.isValid()) {
            previousModelIndex = mPictureModel->index(previousModelIndex.row(),previousModelIndex.column());
            mPictureSelectionModel->setCurrentIndex(previousModelIndex, QItemSelectionModel::SelectCurrent);
            return;
        }
    
        //选中后一个图片
        QModelIndex nextModelIndex = mPictureModel->sourceModel()->index(row, 0);
        if(nextModelIndex.isValid()) {
            nextModelIndex = mPictureModel->index(nextModelIndex.row(),nextModelIndex.column());
            mPictureSelectionModel->setCurrentIndex(nextModelIndex, QItemSelectionModel::SelectCurrent);
            return;
        }
    }
    
    void MyListView::clearPictures()
    {
        PictureModel* pic_model = (PictureModel*)mPictureModel->sourceModel();
        pic_model->clearPictures();
    }
    
    void MyListView::delAllPicture()
    {
        PictureModel* pic_model = (PictureModel*)mPictureModel->sourceModel();
        pic_model->deleteAllPictures();
    }
    
    void MyListView::delCurrentPicture()
    {
        if(mCurrentIndex.isValid())
        {
            PictureModel* pic_model = (PictureModel*)mPictureModel->sourceModel();
            pic_model->removeRow(mCurrentIndex.row());
        }
    }
    
    void MyListView::showCustomMenu(const QPoint &pos)
    {
        QPoint point = pos;
        mCurrentIndex = ui->pic_list_view->indexAt(pos);
        if(mCurrentIndex.isValid() && mCurrentIndex.row() >= 0)
        {
            m_func_menu->exec(ui->pic_list_view->mapToGlobal(point));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139

    完善了列表视图之后,我们就可以在主界面中,添加视图控件了,这也是UI层的最后一步操作了,对应的实现如下:

    //mainwwindow.h
    #ifndef MAINWWINDOW_H
    #define MAINWWINDOW_H
    
    #include <QWidget>
    #include "mylistview.h"
    
    namespace Ui {
    class MainwWindow;
    }
    
    class MainwWindow : public QWidget
    {
        Q_OBJECT
    
    public:
        explicit MainwWindow(QWidget *parent = 0);
        ~MainwWindow();
    private:
        MyListView* mListView=nullptr;
    };
    
    #endif // MAINWWINDOW_H
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    //mainwwindow.cpp
    #include "mainwwindow.h"
    #include "ui_mainwwindow.h"
    #include "picturemodel.h"
    #include "picproxymodel.h"
    
    #include <QHBoxLayout>
    
    MainwWindow::MainwWindow(QWidget *parent) :
        QWidget(parent)
    {
        mListView = new MyListView(this);
    
        PictureModel* pic_model = new PictureModel(this);
        PictureProxyModel* pic_proxy_model = new PictureProxyModel(this);
        pic_proxy_model->setSourceModel(pic_model);
    
        QItemSelectionModel* pictureSelectionModel = new QItemSelectionModel(pic_proxy_model, this);
    
        mListView->setPictureModel(pic_proxy_model);
        mListView->setPictureSelectionModel(pictureSelectionModel);
    
        pic_model->loadPictures();
    
        QHBoxLayout* main_layout = new QHBoxLayout(this);
        main_layout->addWidget(mListView);
        this->setLayout(main_layout);
        this->setFixedSize(910,600);
    }
    
    MainwWindow::~MainwWindow()
    {
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    使用效果

    在这里插入图片描述

  • 相关阅读:
    解析几何@平面上点到直线的距离@点到平面的距离@空间中点到直线的距离
    Design patterns--观察者模式
    CSS选择器
    C++Qt开发——音视频播放
    手把手教你使用PLSQL远程连接Oracle数据库【内网穿透】
    java-php-net-python-个人信息管理系统计算机毕业设计程序
    基于图搜索的规划算法之A*家族(一):A*算法
    Memory Analyzer分析内存溢出
    Web(一)Web前端开发概述
    Android基础第九天 | 字节跳动第四届青训营笔记
  • 原文地址:https://blog.csdn.net/yang1fei2/article/details/125625567