I am trying to pass a c++ user model to qml and get an error that I don't understand.
I use a manager class that should reads in the users and fills the listmodel.
The list model itself should be pass to qml via Q_PROPERTY.
The manager class is known in the qml context.
In the public method of the manager class, the compiler tells me that private m_listModel is a deleted function.
UserManager.h
#pragma once
#include <QObject>
#include <QMap>
#include "UserListModel.h"
class UserManager : public QObject
{
Q_OBJECT
public:
explicit UserManager(QObject* parent = nullptr);
Q_PROPERTY(UserListModel model READ getModel)
UserListModel getModel() { return m_listModel; }
private:
UserListModel m_listModel;
};
UserListModel getModel() { return m_listModel; } gives the error on m_listModel
Error is this
error C2280: "UserListModel::UserListModel(const UserListModel &)" : Es wurde versucht, auf eine gelöschte Funktion zu verweisen
UserManager.cpp
#include "UserManager.h"
UserManager::UserManager(QObject* parent)
{
}
void UserManager::initialize(QMap<QString, QString> args)
{
m_listModel.addModel(UserModel("user1", "0000"));
m_listModel.addModel(UserModel("user2", "9999"));
}
UserListModel.h
#pragma once
#include <QObject>
#include <QAbstractListModel>
struct UserModel {
UserModel() {}
UserModel(const QString name, const QString password) : name(name), password(password) {}
QString name;
QString password;
};
class UserListModel : public QAbstractListModel
{
Q_OBJECT
public:
explicit UserListModel(QObject* parent = 0);
enum Roles { NameRole = Qt::UserRole, PasswordRole };
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
QHash<int, QByteArray> roleNames() const override;
void addModel(const UserModel model);
private:
QList<UserModel> m_models;
};
UserListModel.cpp
#include "UserListModel.h"
UserListModel::UserListModel(QObject* parent)
{
}
int UserListModel::rowCount(const QModelIndex& parent) const
{
return m_models.count();
}
QVariant UserListModel::data(const QModelIndex& index, int role) const
{
if (!index.isValid()) return QVariant();
const UserModel user = m_models.at(index.row());
if (role == NameRole) return user.name;
else if (role == PasswordRole) return user.password;
else return QVariant();
}
QHash<int, QByteArray> UserListModel::roleNames() const
{
static QHash<int, QByteArray> mapping{ {NameRole, "name"}, {PasswordRole, "password"} };
return mapping;
}
void UserListModel::addModel(const UserModel model)
{
beginInsertRows(QModelIndex(), rowCount(), rowCount());
m_models << model;
endInsertRows();
}
Sorry if it is a beginner's mistake, I'm just starting to learn qt and c++.
Related
I have got a custom structure defined in C++, which contains several variables among them is a QList variable. I can now add a new structure dynamically in Qml listview, but my question is I also want to add a new item into QList inside the structure, I can do this in the background but fail to update the listview.
I think the error appears in the connect() function in C++. As I understand in order to add a new row need to call beginInsertRows() and endInsertRows(), but should be sender and receiver be the same in these two scenarios?
So I have got the following structure in C++ along with some signals and functions used to insert new rows.
"todolist.h":
struct ToDoItem
{
bool done;
QString description;
QList<int> list;
ToDoItem(){
done = false;
description = "text";
QList<int> mylist;
mylist.append(1);
list = mylist;
}
};
signals:
void preItemAppended();
void postItemAppended();
void preListAppended();
void postListAppended();
public slots:
void appendItem();
void appendList();
void ToDoList::appendItem()
{
emit preItemAppended();
mItems.append(ToDoItem());
emit postItemAppended();
}
void ToDoList::appendList()
{
emit preListAppended();
mItems[0].list.append(1);
emit postListAppended();
}
"todomodel.h"
void ToDoModel::setList(ToDoList *list)
{
beginResetModel();
if (mList)
mList->disconnect(this);
mList = list;
if (mList) {
connect(mList, &ToDoList::preItemAppended, this, [=]() {
const int index = mList->items().size();
beginInsertRows(QModelIndex(), index, index);
});
connect(mList, &ToDoList::postItemAppended, this, [=]() {
endInsertRows();
});
connect(mList, &ToDoList::preListAppended, this, [=]() {
const int index = mList->lists().size();
beginInsertRows(QModelIndex(), index, index);
});
connect(mList, &ToDoList::postListAppended, this, [=]() {
endInsertRows();
});
}
endResetModel();
}
The first two connect function works fine which are used to insert a new item but the second two connect functions fail.
It's not a good idea to connect signals to beginInsertRows and endInsertRows.
You should abstract that and rather connect to add or remove methods.
Assume you have your model class which implements QAbstractListModel:
class MyListModel : public QAbstractListModel {
Q_OBJECT
Q_PROPERTY(int size READ size NOTIFY sizeChanged)
public:
explicit myListModel(QObject *parent = nullptr);
~myListModel() override;
int rowCount(const QModelIndex &p) const override;
QVariant data(const QModelIndex &index, int role) const override;
QHash<int, QByteArray> roleNames() const override;
int size() const;
public slots:
void add(QObject *o);
QObject *remove(int i);
signals:
void sizeChanged();
private:
QList<QObject *> m_data;
};
The implementation of add and remove wouldbe as follows:
void MyListModel::add(QObject* o) {
int i = m_data.size();
beginInsertRows(QModelIndex(), i, i);
m_data.append(o);
o->setParent(this);
sizeChanged();
endInsertRows();
}
QObject * MyListModel::take(int i) {
if ((i > -1) && (i < m_data.size())) {
beginRemoveRows(QModelIndex(), i, i);
QObject * o = m_data.takeAt(i);
o->setParent(nullptr);
sizeChanged();
endRemoveRows();
return o;
}
return nullptr;
}
In this case, you can call add on newly added items, or if you're repopulating your list, you can clear it and loop over your list and add them 1 by 1.
ADDING CUSTOM OBJECT EXAMPLE:
class ToDoItem : public QObject {
Q_OBJECT
Q_PROPERTY(bool done READ done WRITE setDone NOTIFY doneChanged)
public:
explicit ToDoItem(QObject* parent = nullptr);
~ToDoItem() override;
bool done() const { return m_done_; }
void setDone(bool done) { m_done_ = done; emit doneChanged(done); }
signals:
void doneChanged(bool);
private:
bool m_done_;
};
I have a very simple class with 2 properties; key and value:
KeyValue.h:
class KeyValue : public QObject
{
Q_OBJECT
Q_PROPERTY(QString key READ getKey WRITE setKey NOTIFY keyChanged)
Q_PROPERTY(QString value READ getValue WRITE setValue NOTIFY valueChanged)
public:
KeyValue(const QString& key, const QString& value, QObject* parent = 0);
signals:
void keyChanged();
void valueChanged();
private:
QString _key;
QString _value;
QString getKey() const;
QString getValue() const;
void setKey(const QString& key);
void setValue(const QString& value);
};
Q_DECLARE_METATYPE(KeyValue)
In another class I would like a property containing a list of KeyValue objects, so I can use this list as a model in QML.
Controller.h
class Controller : public QObject
{
Q_OBJECT
Q_PROPERTY(QList<KeyValue*> items READ getItems NOTIFY itemsChanged)
public:
explicit Controller(QObject* parent = 0);
signals:
void itemsChanged();
private:
QList<KeyValue*> getItems() const;
};
I want to be able to use this in QML the following way:
import QtQuick 2.7
import customqml 1.0
Item{
Controller{
id: controller
}
Repeater{
model: controller.items
Text{
text: modelData.key + ": " + modelData.value
}
}
}
Both classes are registered in my main.cpp file:
qmlRegisterType<KeyValue>("customqml", 1, 0, "KeyValue");
qmlRegisterType<Controller>("customqml", 1, 0, "Controller");
The above code does not work, bacause I apparently can't expose a QList to QML directly. I have tried using QAbstractItemModel and QQmlListProperty, but I was unable to get it to work. Can anyone point me in the right direction?
My primary issues are the type of the items property in the Controller class and the return value of the getItems method.
I'm using Qt 5.9 if that makes any difference.
Note:
The getters and setters are generally public except for exceptions so move it to the public part
The classes that inherit from QObject do not need QMetaType because when you want to transfer data of that class the pointers are used.
Not all data types are supported by QML through Q_PROPERTY, so a possible solution is to export through known classes such as
QList<QObject *>:
class Controller : public QObject
{
Q_OBJECT
Q_PROPERTY(QList<QObject *> items READ getItems NOTIFY itemsChanged)
public:
explicit Controller(QObject *parent = nullptr);
QList<QObject *> getItems() const;
signals:
void itemsChanged();
private:
QList<KeyValue *>key_values_list;
};
...
QList<QObject *> Controller::getItems() const
{
QObjectList l;
for(auto e: key_values_list)
l << e;
return l;
}
QVariantList:
class Controller : public QObject
{
Q_OBJECT
Q_PROPERTY(QVariantList items READ getItems NOTIFY itemsChanged)
public:
explicit Controller(QObject *parent = nullptr);
QVariantList getItems() const;
signals:
void itemsChanged();
private:
QList<KeyValue *>key_values_list;
};
...
QVariantList Controller::getItems() const
{
QVariantList l;
for(auto e: key_values_list)
l.append(QVariant::fromValue(e));
return l;
}
Other options is to implement a model, the following example shows only a read-only model:
keyvaluemodel.h
#ifndef KEYVALUEMODEL_H
#define KEYVALUEMODEL_H
#include "keyvalue.h"
#include <QAbstractListModel>
class KeyValueModel : public QAbstractListModel
{
Q_OBJECT
public:
explicit KeyValueModel(QObject *parent = nullptr)
: QAbstractListModel(parent)
{
key_values_list = {new KeyValue{"k", "v"}, new KeyValue{"k2", "v2"}};
}
int rowCount(const QModelIndex &parent = QModelIndex()) const override
{
if (parent.isValid())
return 0;
return key_values_list.length();
}
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override
{
if (!index.isValid())
return QVariant();
if(index.row() >= 0 && index.row() < rowCount())
return QVariant::fromValue(key_values_list[index.row()]);
return QVariant();
}
private:
QList<KeyValue* >key_values_list;
};
#endif // KEYVALUEMODEL_H
class Controller : public QObject
{
Q_OBJECT
Q_PROPERTY(KeyValueModel* items READ getItems NOTIFY itemsChanged)
public:
explicit Controller(QObject *parent = nullptr);
KeyValueModel* getItems() const;
signals:
void itemsChanged();
private:
KeyValueModel *model;
};
...
Text{
text: display.key + ": " + display.value
}
...
And in a similar way you can implement a QQmlListProperty, in the docs there are many examples.
If you want a sophisticated model with adding/deleting objects and altering data you should look into subclassing QAbstractListModel.
As a simple but less flexible way you can use a QVariantList and make your Controller class a value type. You need:
The macro Q_DECLARE_METATYPE(Controller) at the end of the Controller header file.
A copy constructor for Controller
A default constructor for Controller
Q_PROPERTY Type and return value are then QVariantList.
There are many good resources on dealing with models and views in the Qt doc, like: http://doc.qt.io/qt-5/model-view-programming.html, but I can't seem to find any that link to dealing with models in QtQuick. There are some basic chapters on extending qml with c++, like in http://doc.qt.io/qt-5/qtqml-tutorials-extending-qml-example.html, and on using models: http://doc-snapshots.qt.io/qt5-5.11/qtquick-modelviewsdata-modelview.html, but I can't quite find a way to use an actual model in extended qml.
Currently I have this model:
class LayoutModel : public QAbstractItemModel {
Q_OBJECT
public:
explicit LayoutModel(const QString &data, QObject *parent = 0);
~LayoutModel();
QVariant data(const QModelIndex &index, int role) const override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
QModelIndex parent(const QModelIndex &index) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
private:
void setupModelData(const QStringList &lines, LayoutItem *parent);
LayoutItem *rootItem;
};
And I'm trying to access it from this view class:
class Layout : public QQuickItem
{
Q_OBJECT
Q_PROPERTY(LayoutModel model READ model WRITE setModel NOTIFY modelChanged)
private:
LayoutModel & m_model;
public:
explicit Layout(QQuickItem * parent = nullptr);
LayoutModel & model() const;
void setModel(const LayoutModel & model);
void itemChange(QQuickItem::ItemChange change, const QQuickItem::ItemChangeData & value) override;
void geometryChanged(const QRectF & newGeometry, const QRectF & oldGeometry) override;
signals:
void modelChanged();
};
But I can't find a way to actual use the model. I can't even properly set up read and write to the model, since QAbstractItemModels (and models in Qt in general) have their copy constructor deleted to enforce entity singularity. Here's my current broken implementation:
Layout::Layout(QQuickItem * parent) : QQuickItem(parent) {}
LayoutList & Layout::model() const
{
return m_model;
}
void Layout::setModel(const LayoutList & model)
{
if (m_model == model)
return;
m_model = model;
emit modelChanged();
}
So, how can I make it so that I can use this extended qml class with my LayoutModel?
QObjects do not have a copy constructor, so you must use a pointer:
*.h
class Layout : public QQuickItem
{
Q_OBJECT
Q_PROPERTY(LayoutModel *model READ model WRITE setModel NOTIFY modelChanged)
private:
LayoutModel *m_model;
public:
explicit Layout(QQuickItem * parent = nullptr);
LayoutModel *model();
void setModel(LayoutModel * model);
...
signals:
void modelChanged();
};
*.cpp
...
LayoutModel *Layout::model()
{
return m_model;
}
void Layout::setModel(LayoutModel *model)
{
if (m_model == model)
return;
m_model = model;
emit modelChanged();
}
I'm currently working with a QML / QtQuick TableView. The data is shown -> great. The only thing I am currently not getting to work, are the headers: As far as I understand the Qt Model concept, the TableView should automatically call the C++ QAbstractTableModel::headerData(int section, Qt::Orientation orientation, int role)-function. However, when I add a qDebug() << "Hello World!" at the beginning of the function (or using the debugger ;)), this function never gets called and the TableView's header doesn't show any text.
Has anyone got this working? Or is this not implemented (yet?) for some reason (Qt 5.3)? What would the alternatives be?
EDIT: Additions
mymodel.cpp
QVariant myModel::headerData(int section, Qt::Orientation orientation, int role) const
{
qDebug() << "Hello World!";
}
[... I left out the implementation of the other functions
here as they work as expected ...]
mymodel.h
#ifndef MYMODEL_H
#define MYMODEL_H
#include <QAbstractTableModel>
#include "inputvectors.h"
class MyModel : public QAbstractTableModel
{
Q_OBJECT
public:
MyModel (QObject *parent = 0);
// InputVectors is just another class I use but it basically
// contains two `QList`s
MyModel (InputVectors *inputData, QObject *parent=0);
int rowCount(const QModelIndex &parent) const;
int columnCount(const QModelIndex &parent) const;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
QVariant headerData(int section, Qt::Orientation orientation, int role) const;
protected:
QHash<int, QByteArray> roleNames() const;
private:
InputVectors *inputData;
};
#endif // MYMODEL_H
main.qml
[...]
TableView {
id: myTableView
anchors.fill: parent
frameVisible: false
model: controller.getInputVectorsModel()
TableViewColumn {
role: "row_line_numbers"
width: 40
delegate: Item {
Text { text: styleData.value; anchors.left:parent.left;anchors.leftMargin: 12}
}
}
}
[...]
I have model inherited from QAbstractItemModel:
class Plugin: public QObject
{
Q_OBJECT
public:
explicit Plugin(QObject *parent=0);
~Plugin();
const QIcon icon();
void setIcon(const QString &ico);
private:
QIcon mIcon;
};
class PluginsModel: public QAbstractItemModel
{
Q_OBJECT
public:
explicit PluginsModel(QObject *parent=0);
~PluginsModel();
protected:
QList<Plugin*> mList;
};
I want to create QIcon instance only once and in data just do:
QVariant PluginsModel::data(const QModelIndex &index, int role) const
{
if (mList.isEmpty())
return QVariant();
Plugin *it = static_cast<Plugin*>(index.internalPointer());
switch (role ) {
case Qt::DecorationRole:
switch (index.column()) {
case 0:
return it->icon();
break;
default:
break;
}
}
return QVariant();
}
So setIcon function looks like:
const QIcon Plugin::icon()
{
return mIcon;
}
void Plugin::setIcon(const QString &ico)
{
mIcon = QIcon(ico);
}
And usage:
Plugin *p = new Plugin(this);
p->setIcon(":/images/16/amarok.png");
mPlugins->add(p);
The problem is that it doesn't work (icon is not drawed). But if I change setIcon to:
void Plugin::setIcon(QIcon &ico)
{
mIcon = ico;
}
... and usage to:
Plugin *p = new Plugin(this);
QIcon i(":/images/16/amarok.png");
p->setIcon(i);
mPlugins->add(p);
... then it is working fine. Anyone can tell me what is a difference between passing QIcon reference from outside and creating reference inside class from image path argument?