【QML与C++混合编程】用QVariantList传递数组类型成员

2017.5.8 更新:Record类要用指针,QObject 不能有拷贝函数。
我有一个C++中自定义的ReaderModel,继承自QAbstractListModel类,传递给了QML。
它的me成员是一个Reader指针,Reader有个成员是RecordModel。
通过reader获取的recordModel,在qml中类型是QVariant(RecordModel),我没法把它作为一个ListView的model。
要怎么让它绑定给view呢?
我尝试者把数据拷贝到一个直接传给qml的recordModel,但是当数据之后发生了变化时,视图就不会更新,除非再次拷贝,这样效率不可观。

通过艰难地google查找相关问题,我最后的解决方案是:
取消这个RecordModel成员,用QVariantList来储存所有record。

简单地说就是传递自定义类中的自定义结构体数组

作为解决方案的代码(如果不需要,完全可以不用ReaderModel,但是要用setContextProperty把reader变量传给qml):

record.h

#ifndef RECORD_H
#define RECORD_H
#include <QObject>

class Record: public QObject
{
    Q_OBJECT
    Q_PROPERTY(QString bookId READ bookId WRITE setBookId NOTIFY bookIdChanged)
    Q_PROPERTY(int state READ state WRITE setState NOTIFY stateChanged)
public:
    Record(const QString &bookId="",int state=0):
    bookId_(bookId),state_(state){}

    Record(const Record &r){
        bookId_ = r.bookId_;
        state_ = r.state_;
    }

    QString bookId() const;
    int state() const;
public slots:
    void setBookId(const QString &);
    void setState(int);
private:
    QString bookId_;
    int state_;
signals:
    void bookIdChanged();
    void stateChanged();
};
Q_DECLARE_METATYPE(Record*)//元类型注册
#endif // RECORD_H

reader.h

#ifndef READER_H
#define READER_H
#include <QObject>
#include <QVariantList>
class Reader: public QObject
{
    Q_OBJECT
    Q_PROPERTY(QString id READ id WRITE setId NOTIFY idChanged)
    Q_PROPERTY(QString password READ password WRITE setPassword NOTIFY passwordChanged)
    Q_PROPERTY(QVariantList record READ record WRITE setRecord NOTIFY recordChanged)
public:
    Reader(const QString &id = "",
           const QString &password = "")
    : id_(id),password_(password){}

    QString id() const;       //id
    QString password() const; //密码
    QVariantList record() const; //记录

    Q_INVOKABLE void doSomething()const;

public slots:
    void setId(const QString &);
    void setPassword(const QString &);
    void setRecord(const QVariantList &);

private:
    QString id_;
    QString password_;
    QVariantList record_;//借书记录

signals:
    void idChanged();
    void passwordChanged();
    void recordChanged();
};
#endif // READER_H

readerModel.h

#ifndef READERMODEL_H
#define READERMODEL_H

#include <QAbstractListModel>
#include <QJsonValueRef>
#include <QVariant>
#include "reader.h"
#include "record.h"

class ReaderModel :  public QAbstractListModel
{
    Q_OBJECT
    Q_PROPERTY(Reader* me READ me WRITE setMe NOTIFY meChanged)
public:
    enum ReaderRole {
        IdRole = Qt::DisplayRole, //0
        PasswordRole = Qt::UserRole,
        RecordRole
    };
    Q_ENUM(ReaderRole)

    ReaderModel(QObject *parent = nullptr){Q_UNUSED(parent);}

    int rowCount(const QModelIndex & = QModelIndex()) const;
    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
    QHash<int, QByteArray> roleNames() const;

    Q_INVOKABLE QVariantMap get(int row) const;
    Q_INVOKABLE void append(const QString &id, 
                            const QString &password);
    Q_INVOKABLE void set(int row, const QString &id,
                         const QString &password);
    Q_INVOKABLE void remove(int row);

    Q_INVOKABLE Reader *me() const;//当前登录的用户对象指针
    Q_INVOKABLE void setMe(Reader *r);

private:
    QList<Reader*> m_readers;
    Reader *me_;
signals:
    void meChanged();
};

#endif // READERMODEL_H

reader.cpp

#include "reader.h"
#include "record.h"
#include <QVariant>
QString Reader::id() const
{
    return id_;
}
QString Reader::password() const
{
    return password_;
}
QVariantList Reader::record() const
{
    return record_;
}
void Reader::setId(const QString &value)
{
    if (id_ == value)
        return;
    id_ = value;
    emit idChanged();
}
void Reader::setPassword(const QString &value)
{
    if (password_ == value)
        return;
    password_ = value;
    emit passwordChanged();
}
void Reader::setRecord(const QVariantList &value)
{
    if (record_ == value)
        return;
    record_ = value;
    emit recordChanged();
}

record.cpp

#include "record.h"
QString Record::bookId() const
{
    return bookId_;
}
int Record::state() const
{
    return state_;
}
void Record::setBookId(const QString &value)
{
    if(bookId_ == value)
        return;
    bookId_ = value;
    emit bookIdChanged();
}
void Record::setState(int value)
{
    if(state_ == value)
        return;
    state_ = value;
    emit stateChanged();
}

readerModel.cpp

#include "readermodel.h"
#include "reader.h"

int ReaderModel::rowCount(const QModelIndex & /*parent*/) const
{
   return m_readers.count();
}

QVariant ReaderModel::data(const QModelIndex &index, int role) const
{
    if(index.row() < rowCount())
        switch(role){
        case IdRole: return m_readers.at(index.row())->id();
        case PasswordRole: return m_readers.at(index.row())->password();
        case RecordRole: return m_readers.at(index.row())->record();
        default: return QVariant();
        }
    return QVariant();
}

QHash<int, QByteArray> ReaderModel::roleNames() const
{
    static const QHash<int, QByteArray> roles{
        {IdRole, "id"},
        {PasswordRole, "password"},
        {RecordRole, "record"}
    };
    return roles;
}

QVariantMap ReaderModel::get(int row) const
{
    Reader *reader = m_readers.value(row);
    return { {"id", reader->id()}, 
            {"password", reader->password()},
            {"record", reader->record()}};
}

void ReaderModel::append(const QString &id, const QString &password)
{
    int row = m_readers.count();
    beginInsertRows(QModelIndex(), row, row);
    Reader *r = new Reader(id, password, name, power, school, credit, money, unback);
    m_readers.append(r);
    endInsertRows();
}

void ReaderModel::set(int row, const QString &id, const QString &password)
{
    if (row < 0 || row >= m_readers.count())
        return;
    Reader *r = new Reader(id, password == ""? m_readers[row]->password() : password);
    m_readers.replace(row, r);
    dataChanged(index(row, 0), index(row, 0), { IdRole,PasswordRole});
}

void ReaderModel::remove(int row)
{
    if (row < 0 || row >= m_readers.count())
        return;

    beginRemoveRows(QModelIndex(), row, row);
    m_readers.removeAt(row);
    endRemoveRows();
}

Reader *ReaderModel::me() const
{
    return me_;
}

void ReaderModel::setMe(Reader *r)
{
    me_ = r;
    emit meChanged();
}

main.cpp

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QtQml>
#include <QQmlContext>
#include "readermodel.h"

int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);

    //向qml注册类型
    qmlRegisterType<ReaderModel>("Backend", 1, 0, "ReaderModel");
  
    ReaderModel *readerModel = new ReaderModel();
    ...//写入数据
    QQmlApplicationEngine *engine = new QQmlApplicationEngine();

    //向qml传递变量
    engine->rootContext()->setContextProperty("readerModel", readerModel);
    engine->load(QUrl(QLatin1String("qrc:/main.qml")));

    return app.exec();
}

mail.qml:

ListView {
  visible: true
  id: recordView
  width: parent.width
  height: parent.height
  model: readerModel.me.record
  delegate:Rectangle{
      property var record: readerModel.me.record[index]
      RowLayout{
          spacing: 10
          Label {
              text: record.bookId
          }
          Label {
              text: record.state
          }
      }
      Component.onCompleted: {
        console.log(readerModel.me.record)
        console.log("\n",readerModel.me.record[index])
        console.log("\n",readerModel.me.record[index].state)
      }
  }
}

参考:[SOLVED] Cascaded QVariantList exposed to QML causes Error: Cannot assign [undefined] to QString

posted @ 2017-04-30 05:24  水郁  阅读(16166)  评论(0编辑  收藏  举报
……