Using QAbstractItemModel as a Q_PROPERTY inside c++ with READ WRITE - qt

I'm trying to achieve a filter model that has a source model inside QML. Both of my models are c++ based and registered as modules.
Assuming I have:
ListView {
id: autocomplete
anchors.top: field.bottom
model: MyTreeModelCompleter {
separator: "."
model: MyTreeModel{}
}
}
The c++ of MyTreeModelCompleter:
class TreeModelCompleter : public QCompleter
{
Q_OBJECT
Q_PROPERTY(QString separator READ separator WRITE setSeparator)
Q_PROPERTY(QAbstractItemModel* model READ model WRITE setModel)
public:
explicit TreeModelCompleter(QObject *parent = Q_NULLPTR);
explicit TreeModelCompleter(QAbstractItemModel *model, QObject *parent = Q_NULLPTR);
QString separator() const;
QAbstractItemModel* model();
public slots:
void setSeparator(const QString&);
void setModel(QAbstractItemModel*);
protected:
QStringList splitPath(const QString &path) const override;
QString pathFromIndex(const QModelIndex &index) const override;
private:
QString m_sep;
QAbstractItemModel *m_model;
};
MyTreeModel c++:
class MyTreeModel : public QAbstractItemModel
{
Q_OBJECT
...
};
MyTreeModel QML:
MyTreeElement {
property bool check
property string name: "name1"
property string description: "desc of name1"
MyTreeElement {
property bool check
property string name: "name2"
property string description: "desc of name2"
}
MyTreeElement {
property bool check
property string name: "name3"
property string description: "desc of name3"
MyTreeElement {
property bool check
property string name: "name 4"
property string description: "desc of name4"
MyTreeElement {
property bool check
property string name: "name 5"
property string description: "desc of name5"
}
}
}
}
MyTreeelement:
class MyTreeNode : public QObject
{
Q_OBJECT
public:
Q_PROPERTY(QQmlListProperty<MyTreeNode> nodes READ nodes)
Q_CLASSINFO("DefaultProperty", "nodes")
MyTreeNode(QObject *parent = Q_NULLPTR);
void setParentNode(MyTreeNode *parent);
Q_INVOKABLE MyTreeNode *parentNode() const;
bool insertNode(MyTreeNode *node, int pos = (-1));
QQmlListProperty<MyTreeNode> nodes();
MyTreeNode *childNode(int index) const;
void clear();
Q_INVOKABLE int pos() const;
Q_INVOKABLE int count() const;
private:
QList<MyTreeNode *> m_nodes;
MyTreeNode *m_parentNode;
};
My main issue is including MyTreeModel inside the completer model MyTreeModelCompleter. The issue is generated when I try to bind them, the compiler complains that the values don't match in types as 1 is QAbstractItemModel* and the other is QAbstractItemModel.
Is there a way to get this model binding to work?
This is by no means a working code, because I think the issue is in the embedding of the Models in one another and not in the actual code.
The error:
Cannot assign object of type "MyTreeModel" to property of type
"QAbstractItemModel*" as the former is neither the same as the latter
nor a sub-class of it.

A way to handle the problem is to use QObject * instead of QAbstractItemModel * for the model property. Here is an example :
class TreeModelCompleter : public QCompleter
{
Q_OBJECT
Q_PROPERTY(QString separator READ separator WRITE setSeparator)
Q_PROPERTY(QObject* model READ model WRITE setModel)
public:
explicit TreeModelCompleter(QObject *parent = Q_NULLPTR);
explicit TreeModelCompleter(QAbstractItemModel *model, QObject *parent = Q_NULLPTR);
QString separator() const;
QObject* model();
public slots:
void setSeparator(const QString&);
void setModel(QObject*);
protected:
QStringList splitPath(const QString &path) const override;
QString pathFromIndex(const QModelIndex &index) const override;
private:
QString m_sep;
QAbstractItemModel *m_model;
};

Related

How to properly link C++ model to QML front end

here is my setup:
First is the model class, this stores a pointer to the data of type CompetitionsList. We can see that it implements the necessary basic functions when deriving from QAbstractListModel, and it uses the QML_ELEMENT macro to expose the model to the QML type system:
class CompetitionsListModel : public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(CompetitionsList* list READ list WRITE setList)
QML_ELEMENT
public:
explicit CompetitionsListModel(QObject *parent = nullptr);
enum {
NameRole = Qt::ItemDataRole(),
IdRole
};
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
CompetitionsList *list() const;
void setList(CompetitionsList *list);
virtual QHash<int, QByteArray> roleNames() const;
private:
CompetitionsList* m_list; \\ <--- ptr to data
};
Then we have the data class. This class is also a QML_ELEMENT, and it has a userid property. This is used to populate the QVector<listItem> by requesting data from a RESTful http server.
The two signals preItemAppended() and postItemAppended() are emitted when data is added to the QVector<listItem>. These signals are then connected to the Model as a way of notifying the model it must create a new row in the list. The important function here is populateCompetitions, which I explain below.
struct listItem {
QString competitionName;
QString competitionId;
};
class CompetitionsList : public QObject
{
Q_OBJECT
Q_PROPERTY(QString userId READ getUserId WRITE setUserId)
QML_ELEMENT
public:
explicit CompetitionsList(QObject *parent = nullptr);
void populateCompetitions(const QString& userId);
QVector<listItem> items();
QString getUserId() const;
void setUserId(const QString &value);
signals:
void preItemAppended();
void postItemAppended();
void errorPopulatingData();
private:
QVector<listItem> m_competitions;
QString userId;
};
The populateCompetitions function looks as so:
void CompetitionsList::populateCompetitions(const QString &userId)
{
qDebug() << "initialising data";
HttpClient* client = new HttpClient();
client->getUserCompetitions(userId);
connect(client, &HttpClient::getUserCompetitionsResult, [=](const QByteArray& reply){
QJsonDocument _reply = QJsonDocument::fromJson(reply);
if(_reply["data"].isNull()) {
emit errorPopulatingData();
}
else {
const QJsonArray competitions = _reply["data"]["competitions"].toArray();
for(const QJsonValue& competition : competitions) {
emit preItemAppended();
listItem item{competition["competition-name"].toString(), competition["competition-id"].toString()};
m_competitions.append(std::move(item));
emit postItemAppended();
}
}
});
}
It requests data from the database, and then stores it locally.
Then finally, the QML model which ties it all together:
ListView {
id: view
implicitHeight: 1920
implicitWidth: 1080
clip: true
model: CompetitionsListModel {
list: CompetitionsList {
id: compList
}
Component.onCompleted: { compList.userId = "6033f377257e8630ed13299e" } //<-- calls the populateCompetitions function
}
delegate: RowLayout {
Text {
text: qsTr(model.name + ":::" + model.id)
}
}
}
We can see that it has a ListView element, with a model of type CompetitionsListModel and a list of type CompetitionsList. Once the model component is created, we set the userId of the list, this in turn calls the populateCompetitions function which sets up the data for the model to use as seen above.
Unfortunately this doesnt anything when I run the code. Blank screen. Nada. I was wondering if anyone has an insight as to what might be causing this based on the code provided. Ive been at it for so long and it just inst being nice.
I think you need to call qmlRegisterType or UncreatableType on your CompetitionsListModel :
https://doc.qt.io/qt-5/qqmlengine.html#qmlRegisterType

Split a QAbstarctListModel into sub models

I have a list of custom objects that I need to split across multiple pages of a SwipeView.
Right now, I implement a QAbstractListModel to provide the data to my QML view through custom roles.
But when I have more then a given number of object, I need to split my model in multiple chunks that will be displayed on different pages of a SwipeView.
If my model has 20 object, my SwipeView will have 2 pages with 12 Items in the first one and the 8 remainings in the second page for example but the number of items is, of course, dynamic.
I know that I can use a QSortProxyFilter or a DelegateModel to filter my model on a criteria but I don't know how I can use them to create groups usable as submodels for the content of the pages of the SwipeView. Because of course, I cannot just change the filter when the page change because that wouldn't make the items visible when swiping from one page to the other.
Thanks for any hint or idea on how to achieve this.
Why don't you include a model into another model? You can return the model to QML via QVariant::fromValue. See my example:
models.h
class ChildItem{
public:
ChildItem() {}
};
class ChildModel: public QAbstractListModel{
Q_OBJECT
public:
explicit ChildModel(QObject *parent = nullptr);
void addItem(const ChildItem &item);
int rowCount(const QModelIndex &parent = QModelIndex()) const;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
signals:
void itemAdded();
protected:
QHash<int,QByteArray> roleNames() const;
private:
QVector<ChildItem> m_items;
};
class ModelItem{
public:
ModelItem() {}
ChildModel *childModel() const;
private:
ChildModel *m_childModel;
};
class MainModel: public QAbstractListModel{
Q_OBJECT
public:
explicit MainModel(QObject *parent = nullptr);
void addItem(const ModelItem &item);
int rowCount(const QModelIndex &parent = QModelIndex()) const;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
signals:
void itemAdded();
protected:
QHash<int,QByteArray> roleNames() const;
private:
QVector<ModelItem> m_items;
};
models.cpp
ChildModel *ModelItem::childModel() const{
return m_childModel;
}
QVariant MainModel::data(const QModelIndex & index, int role) const {
if (index.row() < 0 || index.row() >= (int)m_items.size())
return QVariant();
const MainModel &item = m_items[index.row()];
switch (role) {
case MainModelRoles::ChildModel:{
return QVariant::fromValue(item.childModel());
}
default: {
break;
}
}
return QVariant();
}
QHash<int, QByteArray> MainModel::roleNames() const {
QHash<int, QByteArray> roles;
roles[MainModelRoles::ChildModel] = "child_model";
return roles;
}
From within QML you can present model role name as a new model for another element. For example:
StackLayout which is your main element, includes Repeater.
Repeater model is MainModel class instance.
Repeater delegate includes ListView
ListView takes ChildModel as a model for itself (child_model reference)
So you can contain any amount of different models inside main model and expose them to QML as model property. Is this what you're looking for?

add rows in the List

I have class called List to print a list
class List : public QAbstractListModel
{
Q_OBJECT
Q_ENUMS(Roles)
public:
enum Roles {
address = Qt::UserRole + 1,
name
};
DeviceList(QObject *parent = 0);
void addrows(const Manager &client);
int rowCount(const QModelIndex & parent = QModelIndex()) const;
QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const;
protected:
QHash<int, QByteArray> roleNames() const;
private:
QList< Manager > m_client;
};
And another class Manager as
class Manager : public QObject
{
Q_OBJECT
Q_PROPERTY(List* List READ getList CONSTANT)
public:
Manager(const QString &address, const QString &name);
QString address() const;
QString name() const;
virtual List* getList() = 0;
private:
QString m_address;
QString m_name;
};
Now i am trying to addrows in manager.cpp as
void List::addrows(const Manager &client)
{
beginInsertRows(QModelIndex(), rowCount(), rowCount());
m_client << client; // **i am getting error here**
endInsertRows();
}
My intention is to implement getlist in manager.cpp file
List* Manager :: getList()
{
List* list = new List();
list->addrows(Manager("street1","John"));
list->addrows(Manager("street2:","Tim"));
list->addrows(Manager("street3","Roberrt"));
return list;
}
In order to use the operator << you have to override the operator = in the class Manager. Instead of doing that use List and handle list elements as pointer.

Qt/QML How to return QList<T> collection from virtual data metod from QAbstractListModel

I want to summarize What to do. I have an DataObject class which have members:
QString first;QString last;QList<SubObject*> m_sublist;
I am using QAbstractListModel to this. I can refer first and last to listview but I can't refer to like m_sublist[0].lesson. It gives me error like:
Cannot read property 'lesson' of undefined.
My code:
dataobject.h
class SubObject :public QObject
{
Q_OBJECT
public:
SubObject(const QString &lesson,QObject *parent = 0);
const QString lesson;
private:
// bool operator==(const SubObject* &other) const {
// return other->lesson == lesson;
// }
};
class DataObject :public QObject{
Q_OBJECT
public:
DataObject(const QString &firstName,
const QString &lastName,
const QList<SubObject*> &sublist);
QString first;
QString last;
QList<SubObject*> m_sublist;
};
simplelistmodel.h
class SimpleListModel : public QAbstractListModel {
Q_OBJECT
public:
SimpleListModel(QObject *parent=0);
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
int rowCount(const QModelIndex &parent = QModelIndex()) const;
QHash<int,QByteArray> roleNames() const { return m_roleNames; }
private:
// Q_DISABLE_COPY(SimpleListModel);
QList<DataObject*> m_items;
static const int FirstNameRole;
static const int LastNameRole;
static const int SubListRole;
QHash<int, QByteArray> m_roleNames;
};
simplelistmodel.cpp
const int SimpleListModel::FirstNameRole = Qt::UserRole + 1;
const int SimpleListModel::LastNameRole = Qt::UserRole + 2;
const int SimpleListModel::SubListRole = Qt::UserRole + 3;
SimpleListModel::SimpleListModel(QObject *parent) :
QAbstractListModel(parent) {
// Create dummy data for the list
QList<SubObject*> mysublist;
mysublist.append(new SubObject("MAT"));
mysublist.append(new SubObject("FEN"));
DataObject *first = new DataObject(QString("Arthur"), QString("Dent"),mysublist);
DataObject *second = new DataObject(QString("Ford"), QString("Prefect"),mysublist);
DataObject *third = new DataObject(QString("Zaphod"), QString("Beeblebrox"),mysublist);
m_items.append(first);
m_items.append(second);
m_items.append(third);
// m_roleNames = SimpleListModel::roleNames();
m_roleNames.insert(FirstNameRole, QByteArray("firstName"));
m_roleNames.insert(LastNameRole, QByteArray("lastName"));
m_roleNames.insert(SubListRole, QByteArray("subList"));
}
int SimpleListModel::rowCount(const QModelIndex &) const {
return m_items.size();
}
QVariant SimpleListModel::data(const QModelIndex &index,
int role) const {
if (!index.isValid())
return QVariant(); // Return Null variant if index is invalid
if (index.row() > (m_items.size()-1) )
return QVariant();
DataObject *dobj = m_items.at(index.row());
switch (role) {
case Qt::DisplayRole: // The default display role now displays the first name as well
case FirstNameRole:
return QVariant::fromValue(dobj->first);
case LastNameRole:
return QVariant::fromValue(dobj->last);
case SubListRole:
return QVariant::fromValue(dobj->m_sublist);
default:
return QVariant();
}
}
main.cpp
int main(int argc, char *argv[]) {
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
SimpleListModel model;
QQmlContext *classContext = engine.rootContext();
classContext->setContextProperty("absmodel",&model);
engine.load(QUrl(QStringLiteral("qrc:/myuiscript.qml")));
return app.exec(); }
myuiscript.qml
import QtQuick 2.0
import QtQuick.Window 2.0
Window {
id: bgRect
width: 200
height: 200
color: "black"
visible: true
ListView {
id: myListView
anchors.fill: parent
delegate: myDelegate
model: absmodel
}
Component {
id: myDelegate
Item {
width: 200
height: 40
Rectangle {
anchors.fill: parent
anchors.margins: 2
radius: 5
color: "lightsteelblue"
Row {
anchors.verticalCenter: parent.verticalCenter
Text {
text: firstName
color: "black"
font.bold: true
}
Text {
text: subList[0].lesson
color: "black"
}
}
}
}
}
}
I can't find any solution. Virtual data model returns single type of objects. FirsName is a string. I cant refer listview delegate like firstName(rolename). Also LastName is refered like lastName(rolename). But I can't refer subList(roleNames) like sublist[0].lesson.
My target is very simple. I want to refer single type (int,QString ....) to text in delegate by using rolename. I can't refer collection type(QList<SubObject*>) to text in delegate using rolename(subList[0].lesson). How to achive them?
Let's fix it step by step. This line text: subList[0].lesson in QML produce the error message
TypeError: Cannot read property 'lesson' of undefined
That means subList[0] is an undefined object and QML engine cannot read any property, including lesson, from this object. In fact, subList returned from model is a well-defined QList<SubObject*> object, but not subList[0] since QList<SubObject*> is not a QML list. To correctly pass a list from C++ to QML, return a QVariantList instead of QList.
//class DataObject
DataObject(const QString &firstName,
const QString &lastName,
const QVariantList &sublist);
QVariantList m_sublist; //use QVariantList instead of QList<SubObject*>
//---
//SimpleListModel::SimpleListModel
QVariantList mysublist; //use QVariantList instead of QList<SubObject*>
mysublist.append(QVariant::fromValue(new SubObject("MAT", this))); //remember parent
mysublist.append(QVariant::fromValue(new SubObject("FEN", this)));
//...
Now, subList[0] can be accessed in QML, but not subList[0].lesson. To access properties in a C++ class, explicitly define the property with Q_PROPERTY macro.
class SubObject :public QObject
{
Q_OBJECT
Q_PROPERTY(QString lesson READ getLesson NOTIFY lessonChanged)
public:
SubObject(const QString &lesson,QObject *parent = 0):
QObject(parent), m_lesson(lesson){;}
QString getLesson() const {return m_lesson;}
signals:
void lessonChanged();
private:
QString m_lesson;
};
And the QML code works now.

Invoke copy paste for QgraphicsTextItem

How can the cut, copy paste slots be invoked for QgraphicsTextItem. Though the the text editor has default slots for cut, copy and paste, but I don't know how to invoke them.
I have class that is inherited from QgraphicsTextItem. The use of this class is to add a text in GraphicsView. Now I want the cut, paste and copy functality for it. How can I go for that?
drawText.h
#include <QGraphicsTextItem>
#include <QPen>
QT_BEGIN_NAMESPACE
class QFocusEvent;
class QGraphicsItem;
class QGraphicsScene;
class QGraphicsSceneMouseEvent;
QT_END_NAMESPACE
class mText : public QGraphicsTextItem
{
Q_OBJECT
public:
mText( int, QGraphicsItem *parent=0 );
enum { Type = UserType + 5 };
int type() const;
int id;
signals:
void lostFocus(mText *item);
void selectedChange(QGraphicsItem *item);
protected:
QVariant itemChange(GraphicsItemChange change, const QVariant &value);
void focusOutEvent(QFocusEvent *event);
void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event);
};
.cpp
#include "mtext.h"
mText::mText( int i, QGraphicsItem *parent)
: QGraphicsTextItem(parent )
{
//assigns id
id = i;
}
int mText::type() const
{
// Enable the use of qgraphicsitem_cast with text item.
return Type;
}
QVariant mText::itemChange(GraphicsItemChange change,
const QVariant &value)
{
if (change == QGraphicsItem::ItemSelectedHasChanged)
emit selectedChange(this);
return value;
}
void mText::focusOutEvent(QFocusEvent *event)
{
setTextInteractionFlags(Qt::NoTextInteraction);
emit lostFocus(this);
QGraphicsTextItem::focusOutEvent(event);
}
void mText::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
{
if (textInteractionFlags() == Qt::NoTextInteraction)
setTextInteractionFlags(Qt::TextEditorInteraction);
QGraphicsTextItem::mouseDoubleClickEvent(event);
}

Resources