PDA

View Full Version : QML ListModel for interaction with C++



shock
9th March 2012, 12:26
Hey Guys,

I am already struggling with this problem for about 3 days... seems I am kinda stuck now =/.
What I want: I want a QList<T> to interact with my QML view - dynamically - so if I remove an item via a C++ interface call to the model the item should disappear in the QML view

What I know so far: I recognized (logical anyways) that the QList i had before:

//Q_PROPERTY(QDeclarativeListProperty<TaskData> taskData READ taskData CONSTANT)
//QDeclarativeListProperty<TaskData> taskData();
wasn't working because it provides no notification for the view -> after some research I decided to implement my own ListModel then...

The Model seems to work but the View does not display anything...

QMLPtrNotificationModel.h looks like shown below:

class QMLPtrAbstractItem : public QDeclarativeItem
{
public:
QMLPtrAbstractItem(QDeclarativeItem *parent = 0);
virtual ~QMLPtrAbstractItem() { }

virtual QString getName() const = 0;
virtual QString setName() const = 0;
};

class QMLPtrNotificationModel : public QAbstractListModel
{
Q_OBJECT

public:
explicit QMLPtrNotificationModel(QObject *parent = 0);
QMLPtrNotificationModel(const QList<QMLPtrAbstractItem*> &list, QObject *parent = 0);
~QMLPtrNotificationModel();

int rowCount(const QModelIndex &parent = QModelIndex()) const;
int count() const { return rowCount(QModelIndex()); }

QVariant data(const QModelIndex &index, int role) const;
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole);

Qt::ItemFlags flags(const QModelIndex &index) const;

bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex());
bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex());

void sort(int column, Qt::SortOrder order = Qt::AscendingOrder);

QList<QMLPtrAbstractItem*> operator()();
const QList<QMLPtrAbstractItem*> operator()();

QList<QMLPtrAbstractItem*> getList() const;
void setList(const QList<QMLPtrAbstractItem*> &list);

QMLPtrAbstractItem* at(int index);

bool addElement(QMLPtrAbstractItem *element);
bool removeElement(QMLPtrAbstractItem *element);
bool clear();

Qt::DropActions supportedDropActions() const;

private Q_SLOTS:
void refresh();

private:
Q_DISABLE_COPY(QMLPtrNotificationModel)
QList<QMLPtrAbstractItem*> lst;
};

QML_DECLARE_TYPE(QMLPtrNotificationModel)


The QMLPtrNotificationModel.cpp looks like shown below (not all functions provided - I think that not all are necessary to document the problem)

QMLPtrAbstractItem::QMLPtrAbstractItem(QDeclarativ eItem *parent)
: QDeclarativeItem(parent)
{
}

QMLPtrNotificationModel::QMLPtrNotificationModel(Q Object *parent)
: QAbstractListModel(parent)
{
}

QMLPtrNotificationModel::QMLPtrNotificationModel(c onst QList<QMLPtrAbstractItem*> &list, QObject *parent)
: QAbstractListModel(parent), lst(list)
{
}

QMLPtrNotificationModel::~QMLPtrNotificationModel( )
{
clear();
}

int QMLPtrNotificationModel::rowCount(const QModelIndex &parent) const
{
if (parent.isValid())
return 0;

return lst.count();
}

QVariant QMLPtrNotificationModel::data(const QModelIndex &index, int role) const
{
if (index.row() < 0 || index.row() >= lst.size())
return QVariant();

if (role == Qt::DisplayRole || role == Qt::EditRole)
return lst.at(index.row())->getName();

return QVariant();
}

Qt::ItemFlags QMLPtrNotificationModel::flags(const QModelIndex &index) const
{
if (!index.isValid())
return QAbstractItemModel::flags(index) | Qt::ItemIsDropEnabled;

return QAbstractItemModel::flags(index) | Qt::ItemIsEditable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled;
}

bool QMLPtrNotificationModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (index.row() >= 0 && index.row() < lst.size()
&& (role == Qt::EditRole || role == Qt::DisplayRole)) {

lst.at(index.row())->setName(value.toString());
emit dataChanged(index, index);
return true;
}
return false;
}

bool QMLPtrNotificationModel::insertRows(int row, int count, const QModelIndex &parent)
{
if (count < 1 || row < 0 || row > rowCount(parent))
return false;

beginInsertRows(QModelIndex(), row, row + count - 1);

for (int r = 0; r < count; ++r){
lst.insert(row, NULL);
}

endInsertRows();

return true;
}

bool QMLPtrNotificationModel::removeRows(int row, int count, const QModelIndex &parent)
{
if (count <= 0 || row < 0 || (row + count) > rowCount(parent))
return false;

beginRemoveRows(QModelIndex(), row, row + count - 1);

for (int r = 0; r < count; ++r){
delete lst.at(row);
lst.removeAt(row);
}

endRemoveRows();

return true;
}

bool QMLPtrNotificationModel::clear()
{
return removeRows(0, rowCount(), QModelIndex());
}

bool QMLPtrNotificationModel::addElement(QMLPtrAbstract Item *element)
{
int position = rowCount();
if(insertRows(position, 1, QModelIndex())){
lst << element;
return true;
}
return false;
}

bool QMLPtrNotificationModel::removeElement(QMLPtrAbstr actItem *element)
{
int position = lst.indexOf(element);
if(removeRows(position, 1, QModelIndex())){
return true;
}
return false;
}

QMLPtrAbstractItem* QMLPtrNotificationModel::at(int index)
{
return lst.at(index);
}


It seems QML doesn't recognize anthing - the structure looks as follows:

import QtQuick 1.1
import QMLPtrNotificationModel 1.0

Item {
property variant orientation: Qt.Vertical

ListView {
id: completeTable

QMLPtrNotificationModel {
id: myListmodel
}

model: myListmodel
delegate: TaskData { }
}

And i registered QMLPtrNotificationModel as QML Type via C++ like that:

qmlRegisterType<QMLPtrNotificationModel>("QMLPtrNotificationModel", 1, 0, "QMLPtrNotificationModel");

I know it's a big part of code for a single post - but I think the error is in both parts, QML and the Model itself.. looks like missing notifications again?
Would be really lovely if anybody of you has an idea to fix this issue -> my QML view is empty at the moment (I added elements via addElement())
The Elements to add are correctly derived from QMLPtrAbstractItem and implement the abstract methods.

Any ideas?

shock
12th March 2012, 09:16
Nobody any idea!?

Le_B
12th March 2012, 09:34
is your listview really empty or field with empty items ?
can you give us the delegate ?
did you try to add emit datachanged(QModelIndex(), QModelIndex()); to refresh your model (it s not supposed to be needed but...)

wysota
12th March 2012, 10:24
I think your addElement and removeElement implementations are incorrect. Try an equivalent of:

bool QMLPtrNotificationModel::addElement(QMLPtrAbstract Item *element)
{
int position = rowCount();
beginInsertRows(QModelIndex(), position, position);
lst << element;
endInsertRows();
return true;
}

shock
12th March 2012, 23:34
Before i used an implementation with:

Q_PROPERTY(QDeclarativeListProperty<TaskData> taskData READ taskData CONSTANT)
QDeclarativeListProperty<TaskData> taskData();

That worked so far ... but if i delete an item via C++ QML gets not notified because it's just a QList...
It looked like that (and now should look the same way with the model.. but i think my implementation with the model went wrong in the very beginning .. possibly ListModel is not what i need !?):
7494

The implementation in QML before looked like that (i am afraid - i used a Repeater { } before -- don't know if there is really a difference)

Item {
//width: 900; height: 800
property variant scrollArea
property variant orientation: Qt.Vertical

ListView {
id: completeTable
Rectangle {
id: cornerWidget
width: dataHandler.getEmpWidth()
height: dataHandler.getColumnHeight()
anchors.top: parent.top
anchors.left: parent.left
gradient: Gradient {....}
border.color: "black"
border.width: 2
radius: 1

Text {...}
}
Grid {
anchors.top: cornerWidget.bottom
anchors.left: parent.left
columns: 1

Repeater {
id: empGrid
model: employees
delegate: Employee {}
}
}

Grid {
anchors.left: cornerWidget.right
columns: dataHandler.getColumnCount()

Repeater {
id: dayHeaderGrid
model: dateHeaderData
delegate: DateHeaderData {}
}
}
Grid {
anchors.left: cornerWidget.right
anchors.top: cornerWidget.bottom
columns: dataHandler.getColumnCount()

Repeater {
id: calendarGrid
model: calendarData
delegate: CalendarData { border.width: 2; opacity: 1; visible: true}
}
}
}

Repeater {
id: tasks
model: taskData
delegate: TaskData {}
}
}

In this case taskData was the list .. this worked for the view .. but not for the add/delete
(I can provide the delegate too if it's needed but it seems i have an error somewhere with the model and it's instance in the Item/ListView)

No it looks like that:
7495

seems empty for me (if i compare them ... the list in the model is filled .. i checked that)
The QML Code now looks like that:

Item {
//width: 900; height: 800
property variant scrollArea
property variant orientation: Qt.Vertical

ListView {
id: completeTable

QMLPtrNotificationModel {
id: myListmodel
}

Rectangle {
id: cornerWidget
width: dataHandler.getEmpWidth()
height: dataHandler.getColumnHeight()
anchors.top: parent.top
anchors.left: parent.left
gradient: Gradient {....}
border.color: "black"
border.width: 2
radius: 1

Text {
.....
}
}
Grid {
anchors.top: cornerWidget.bottom
anchors.left: parent.left
columns: 1

Repeater {
id: empGrid
model: employees
delegate: Employee {}
}
}

Grid {
anchors.left: cornerWidget.right
columns: dataHandler.getColumnCount()

Repeater {
id: dayHeaderGrid
model: dateHeaderData
delegate: DateHeaderData {}
}
}
Grid {
anchors.left: cornerWidget.right
anchors.top: cornerWidget.bottom
columns: dataHandler.getColumnCount()

Repeater {
id: calendarGrid
model: calendarData
delegate: CalendarData { border.width: 2; opacity: 1; visible: true}
}
}

model: myListmodel
delegate: TaskData { }
}

//Repeater {
// id: tasks
// model: myListmodel
// delegate: TaskData { }
//}
}

As you can see, i also tried the repeater again - gave the same meaninless empty result =/
I don't really know where to start debugging and where to look for the error in the QML <-> C++ communication -> complicated as it seems ;)

@wysota: Thanks for your tip! I changed the implementation of my derived ListModel to the following:

QMLPtrAbstractItem::QMLPtrAbstractItem(QDeclarativ eItem *parent)
: QDeclarativeItem(parent)
{
}

QMLPtrNotificationModel::QMLPtrNotificationModel(Q Object *parent)
: QAbstractListModel(parent)
{
}

QMLPtrNotificationModel::QMLPtrNotificationModel(c onst QList<QMLPtrAbstractItem*> &list, QObject *parent)
: QAbstractListModel(parent), lst(list)
{
}

QMLPtrNotificationModel::~QMLPtrNotificationModel( )
{
clear();
}

int QMLPtrNotificationModel::rowCount(const QModelIndex &parent) const
{
if (parent.isValid())
return 0;

return lst.count();
}

QVariant QMLPtrNotificationModel::data(const QModelIndex &index, int role) const
{
if (index.row() < 0 || index.row() >= lst.size())
return QVariant();

if (role == Qt::DisplayRole || role == Qt::EditRole)
return lst.at(index.row())->getName();

return QVariant();
}

Qt::ItemFlags QMLPtrNotificationModel::flags(const QModelIndex &index) const
{
if (!index.isValid())
return QAbstractItemModel::flags(index) | Qt::ItemIsDropEnabled;

return QAbstractItemModel::flags(index) | Qt::ItemIsEditable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled;
}

bool QMLPtrNotificationModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (index.row() >= 0 && index.row() < lst.size()
&& (role == Qt::EditRole || role == Qt::DisplayRole)) {

if(role == Qt::EditRole)
qDebug() << "QMLPtrNotificationModel::setData(role == Qt::EditRole)";
else
qDebug() << "QMLPtrNotificationModel::setData(role == Qt::DisplayRole)";

lst.at(index.row())->setName(value.toString());
emit dataChanged(index, index);
return true;
}

return false;
}

QList<QMLPtrAbstractItem*> QMLPtrNotificationModel::getList() const
{
return lst;
}

void QMLPtrNotificationModel::setList(const QList<QMLPtrAbstractItem*> &myitems)
{
emit beginResetModel();
lst = myitems;
emit endResetModel();
emit countChanged();
}

Qt::DropActions QMLPtrNotificationModel::supportedDropActions() const
{
return QAbstractItemModel::supportedDropActions() | Qt::MoveAction;
}

bool QMLPtrNotificationModel::clear()
{
qDebug() << "QMLPtrNotificationModel::clear() == false";
return false;
}


QList<QMLPtrAbstractItem*>* QMLPtrNotificationModel::operator()()
{
return &lst;
}

bool QMLPtrNotificationModel::addElement(QMLPtrAbstract Item *element)
{
int position = rowCount();
beginInsertRows(QModelIndex(), position, position);
lst << element;
qDebug() << "Added Element - new count(): " << lst.count();
endInsertRows();
return true;
}

bool QMLPtrNotificationModel::removeElement(QMLPtrAbstr actItem *element)
{
int position = lst.indexOf(element);
beginRemoveRows(QModelIndex(), position, position);

delete lst.at(position);
lst.removeAt(position);

endInsertRows();
return true;
}

QMLPtrAbstractItem* QMLPtrNotificationModel::at(int index)
{
return lst.at(index);
}

void QMLPtrNotificationModel::refresh()
{
QList<QMLPtrAbstractItem*> myList = getList();
setList(myList);
}

Please - forget the meaningless clear(); function for the moment - i don't think that this might be the problem -> let's talk about clear and delete if i am able to fill the fucking view again ^^

Here is the class description of the QMLPtrAbstractItem again and the definition of the QMLPtrNotificationModel (QMLPtrNotificationModel.h):

class QMLPtrAbstractItem : public QDeclarativeItem
{
public:
QMLPtrAbstractItem(QDeclarativeItem *parent = 0);
virtual ~QMLPtrAbstractItem() { }

virtual QString getName() const = 0;
virtual void setName(QString name) = 0;
};

class QMLPtrNotificationModel : public QAbstractListModel
{
Q_OBJECT
.......
};

QML_DECLARE_TYPE(QMLPtrNotificationModel)

I hope you any further ideas for me - i am stuck ... =(
Thanks, greets

wysota
13th March 2012, 07:52
To test your QML implementation I suggest you attach a QStandardItemModel to it and try to add and remove items in it from within C++ to see if changes are properly reflected in QtQuick view.

shock
14th March 2012, 13:26
Thanks for that example wysota - seems very helpful! The problem is that the classes are derived from QDeclarativeItem and i can't really change stuff to QStandardItem because it's column/row based and the things are working with normal X/Y coordinates .. seems a bit more complicated (inheritance from QStandardItem and QDeclarativeItem seems not really pretty - and not working anyways .. i did it to test stuff -> i get an empty table again like the one i already posted)

I think what i need is something like QStandardDeclarativeItem or QDeclarativeItemModel ? Which is a DeclarativeItem derived from QStandardItem or from QAbstractItem which means i have to reimplement the whole stuff!? =/

Possibly i am wrong!? .. hopefully...

wysota
14th March 2012, 17:21
QDeclarativeItem is something you show in QtQuick. The model has nothing to do with this. It won't make any sense to keep a list of QDeclarativeItem instances in C++ and expect that to be somehow automatically trasfered to QtQuick. Your deleage needs to be a component and for that you can use your declarative item but the model is intended to hold data.

shock
16th March 2012, 11:40
Ahm ... yes .. sure - you are right, it seems i missed that point while writing absurd code ...
After all - with the right code - it does not seem so difficult at all ;) So however here is my solution:

Derive a model from QAbstractListModel and use specific roles for each function related to the Object/Delegate that should be displayed via QML (which is accessing these roles)
ListView QML:

import QtQuick 1.1

Item {
ListView {
...
...
}
Repeater {
id: tasks
model: myMod //the model must be "registered" via the Context:ui->declarativeView->rootContext()->setContextProperty("myMod", model);
delegate: TaskData { }
}
}

Delegate QML:


Rectangle {
Text {
id: taskName
anchors.centerIn: parent
text: name; color: "white";
}
}

C++ Class (Element)

class Element: public QDeclarativeItem
{
Q_OBJECT
public:
Element() { }
virtual ~Element() { }

virtual QString getName() const { return _name; }
virtual void setName(QString name) { _name = name; }

private:
QString _name;
};

C++ Derived Model (header):

class QMLPtrNotificationModel : public QAbstractListModel
{
Q_OBJECT

public:
enum roles {
Name = Qt::UserRole + 1
};

explicit QMLPtrNotificationModel(QObject *parent = 0);
QMLPtrNotificationModel(const QList<Element*> &list, QObject *parent = 0);
~QMLPtrNotificationModel();

int rowCount(const QModelIndex &parent = QModelIndex()) const;
int count() const { return rowCount(QModelIndex()); }

QVariant data(const QModelIndex &index, int role) const;
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole);

Element* at(int index);

bool addElement(Element *element);
bool removeElement(Element *element);
bool updateElement(Element *element);
bool clear();

public Q_SLOTS:
QList<Element*> getList() const;
void setList(const QList<Element*> &list);

private Q_SLOTS:
void refresh();

private:
Q_DISABLE_COPY(QMLPtrNotificationModel)
void init();
QList<Element*> lst;
};

C++ Derived Model:

void QMLPtrNotificationModel::init(){
QHash<int, QByteArray> roles;
roles[Name] = "name";
setRoleNames(roles);
}

int QMLPtrNotificationModel::rowCount(const QModelIndex &parent) const
{
if (parent.isValid())
return 0;

return lst.count();
}

QVariant QMLPtrNotificationModel::data(const QModelIndex &index, int role) const
{
if (index.row() < 0 || index.row() > lst.size())
return QVariant();

const Element* const elem = lst[index.row()];

if (role == Name)
return elem->getName();

return QVariant();
}

bool QMLPtrNotificationModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (index.row() >= 0 && index.row() < lst.size()) {
bool changed = false;

Element* const elem = lst.at(index.row());
if(task){
if (role == Name){
changed = true;
elem->setName(value.toString());
}

if(changed)
emit dataChanged(index, index);
return changed;
}
return false;
}
return false;
}

QList<Element*> QMLPtrNotificationModel::getList() const
{
return lst;
}

void QMLPtrNotificationModel::setList(const QList<Element*> &myitems)
{
emit beginResetModel();
lst = myitems;
emit endResetModel();
}

bool QMLPtrNotificationModel::clear()
{
beginRemoveRows(QModelIndex(), 0, rowCount());

lst.clear();

endRemoveRows();
return true;
}

bool QMLPtrNotificationModel::addElement(Element *element)
{
int position = rowCount();
beginInsertRows(QModelIndex(), position, position);

lst << element;

endInsertRows();
return true;
}

bool QMLPtrNotificationModel::removeElement(Element *element)
{
int position = lst.indexOf(element);
beginRemoveRows(QModelIndex(), position, position);

lst.removeAt(position);

endRemoveRows();
return true;
}

bool QMLPtrNotificationModel::updateElement(Element *element)
{
int position = lst.indexOf(element);
if(position>-1){
lst[position] = element;

emit dataChanged(index(position), index(position));

return true;
}
return false;
}


Element* QMLPtrNotificationModel::at(int index)
{
return lst.at(index);
}

void QMLPtrNotificationModel::refresh()
{
QList<Element*> myList = getList();
setList(myList);
}

After changing the value pointed to by the model i do model.updateElement(elem);
After deletion/on deletion of the object i do model.removeElement(elem);
I hope this could be helpful for others who are struggling by implementing an own model for QML ;)

cheers

wysota
16th March 2012, 11:49
But why is your Element class derived from QDeclarativeItem?

shock
16th March 2012, 14:01
Because i am using setPos() to place these items in the DeclarativeView

wysota
16th March 2012, 14:28
They are part of your data and not items. The model makes no use of their declarative item nature. If you delete such an object behind the model's back, your application will crash the next time the model tries to use that item.

The proper approach would be to have some internal representation of your data that is shared between the model and the item you display somewhere in the scene.

shock
17th March 2012, 11:53
I don't know if i got your point, but that is (as i think) exactly what i do?
I changed the interface design later to:

class QMLPtrAbstractItem : public QDeclarativeItem
{
public:
virtual ~QMLPtrAbstractItem() { }

virtual bool setData(int role, const QVariant &value) = 0;
virtual QVariant getData(int role) const = 0;

virtual int getUID() const = 0;
virtual void setUID(int uid) = 0;
};

So that i have an abstract "interface" to the model then i derive other classes of QMLPtrAbstractItem

class EmpData : public QMLPtrAbstractItem

I could have also done:

class QMLPtrAbstractItem {}
class EmpData : public QMLPtrAbstractItem, public QDeclarativeItem {}

But the Abstractlayer is my communication layer between the DeclarativeItem and the Model - or have i missed something?
I am using only Data in the model - the data i get from the Item itself (i wanted to avoid to implement 3 different model for the same type of use case...

In the Model i call "getData()" in data() to return the data associated with the provided role (different acting for different item)
The model holds a list of pointers to the Items (as usual!?) and with data() I request the data for a given role from the selected item

Isn't that the same behaviour as usual? The same is done here: http://harmattan-dev.nokia.com/docs/platform-api-reference/xml/daily-docs/libqt4/declarative-modelviews-abstractitemmodel-model-cpp.html

This is what i do: (looks the same for me)

QVariant QMLPtrNotificationModel::data(const QModelIndex &index, int role) const
{
if (index.row() < 0 || index.row() > lst.size())
return QVariant();

const QMLPtrAbstractItem * const ptrItem = lst[index.row()];

return ptrItem->getData(role);
}

bool QMLPtrNotificationModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (index.row() >= 0 && index.row() < lst.size()) {

QMLPtrAbstractItem * const ptrItem = lst.at(index.row());
if(ptrItem){
bool changed = ptrItem->setData(role, value);

if(changed)
emit dataChanged(index, index);
return changed;
}
return false;
}else
qDebug() << "QMLPtrNotificationModel::setData( " << role << ")";

return false;
}

What's wrong with that architecture? If you say -you must be right because you have more knowhow than me, but if that's "bad design" i could have missed the point - i am always interested in your opinion ;)

wysota
17th March 2012, 12:25
You are still using QDeclarativeItem as the base class for items in your model.


Isn't that the same behaviour as usual? The same is done here: http://harmattan-dev.nokia.com/docs/...model-cpp.html
No, it's not. In the quoted example Animal is not a subclass of QDeclarativeItem.

shock
18th March 2012, 10:00
Ok.. so you mean this would be better?:

class QMLPtrAbstractItem
{
}

class QMLPtrNotificationModel : public QAbstractListModel
{
....
private:
QList<QMLPtrAbstractItem*> lst;
};

class EmpData : public QDeclarativeItem, public QMLPtrAbstractItem
{
....
}

wysota
18th March 2012, 12:36
What is this EmpData class? Are you adding instances of this class directly to the declarative scene? Your initial QML code suggests the only custom item you are using is "TaskData". How is "EmpData" related to "TaskData"?

shock
18th March 2012, 15:19
EmpData and TaskData are items that i really add to the DeclarativeView - EmpData is not really related to TaskData (That are employees - TaskData are the Tasks that are associated to the Employees which are represented via to the DeclatativeView via EmpData)
They are however threated similar to Tasks TaskData is created the same way:


class TaskData : public QDeclarativeItem, public QMLPtrAbstractItem
From TaskData there are however again two specifications: Events (sickness, Vacation) and Orders (real working tasks) Task is the "interface for both"

So OrderData and EventData are then derived from TaskData...

EventData *td = new EventData();
td->setUID(ID.toInt());
td->setName(type);
td->setBgColor1("#ffffff");
td->setBgColor2(color.name());
td->setXPos(x);
td->setYPos(y);
td->setLength(w);
td->setCreationUser(creationUser);
taskModel.addElement(td);


OrderData *td = new OrderData();
td->setUID(uID);
td->setName(text);
td->setBgColor1("#ffffff");
td->setBgColor2(ZEGlobal->getStatusColor(oid2sta[orderID]).name());
td->setXPos(ex_x);
td->setYPos(ex_y);
td->setLength(len);
td->setOrderID(orderID);
td->setLttID(lttID);
td->setGrpID(grpID);
td->setCreationUser(creationUser);
taskModel.addElement(td);

These items are later checked for collisions - but i think i got your point ... i don't need the DeclarativeItem ... cauze the items are placed via
the data from the model in QML..
Was that your point?

wysota
18th March 2012, 16:15
Ok, I think the problem is that you are not using QtQuick correctly. It's a declarative framework, not an imperative one. Your TaskData and EmpData classes are delegates -- components that should be used to display (and otherwise interact with) data from a model. They are not supposed to be part of the model itself so you don't need to add them manually to the scene. You just need to state "ok, here is a model containing my data and here is how each item should look like".

So based on that your EmpData class can have the same base class as your model item class but that's not required in any way, you just need to teach the EmpData class to handle pieces of data from your model. Instead of inheritance you can as well use composition.


class EmpDataItem : public QDeclarativeItem {
Q_OBJECT
Q_PROPERTY(QString name READ name WRITE setName)
public:
void setName(const QString &n) {
m_data.name = n;
update();
}
QString name() const { return m_data.name; }
private:
EmpData m_data;
};

This way you won't require any virtual methods in your data class and you can easily have the model class hold QList<Data> instead of QList<Data*> which makes things a lot easier.

shock
21st March 2012, 18:56
Hmm what you say sounds good so far..
"Your TaskData and EmpData classes are delegates -- components that should be used to display (and otherwise interact with) data from a model" I agree to that and got that (i think)..
Means I have Delegates that don't have any data by heart and the delegates are provided X-times according to the Listmodel which holds the data for each Delegate - this might be set via setItemData() correct? And in the delegate I can access the data via the role.

What for do i need the QDeclarativeItem anyways? If the Delegate interacts with the model that thing is not needed? By now it is working without it..

If i store the data in the model directly i have to use different model instances - correct? At the moment i am using one instance for Tasks and Events thats why i
used virtual functions and the Abstractlayer

What would you do with

private:
EmpData m_data;
};
in the case above ? how does the model interact with that Data?

wysota
21st March 2012, 19:21
and the delegates are provided X-times according to the Listmodel which holds the data for each Delegate
The delegate is provided once per view using the "delegate" property of the view. The model itself doesn't use a delegate.


this might be set via setItemData() correct? And in the delegate I can access the data via the role.
No, that's wrong, that's my point that you are doing it the wrong way. Your model shouldn't be composed of delegates or anything like that. The model holds data, regardless of how the data is stored. You don't need any "items" in the model, the data could be generated on the fly. The "items" are not "delegates", they have no visual representation in the model.


What for do i need the QDeclarativeItem anyways?
The way I see it you are providing a custom delegate (TaskData or EmpData). If you implement it in C++, it will be derived from QDeclarativeItem.


By now it is working without it..
You have QDeclarativeItem instances all over your code, just take a look.


If i store the data in the model directly i have to use different model instances
Different model instances of what?


how does the model interact with that Data?
The model doesn't interact with data. The model represents the data, it provides access to it.

shock
21st March 2012, 20:16
You have QDeclarativeItem instances all over your code, just take a look.

This is the structure that works for me by now:

class QMLPtrAbstractItem : public QObject
{
Q_OBJECT
public:
virtual ~QMLPtrAbstractItem() { }

virtual bool setData(int role, const QVariant &value) = 0;
virtual QVariant getData(int role) const = 0;

virtual int getUID() const = 0;
virtual void setUID(int uid) = 0;
};

class EmpData : public QMLPtrAbstractItem
{
Q_OBJECT
public:
enum EmpRoles {
Name = Qt::UserRole + 1,
UID,
Qualification
};

static QHash<int, QByteArray> getQMLRoles(){
QHash<int, QByteArray> roles;
roles[Name] = "name";
roles[UID] = "uid";
roles[Qualification] = "qualification";
return roles;
}

EmpData() {}

virtual QVariant getData(int role) const {
if (role == Name)
return getName();
else if (role == UID)
return getUID();
else if (role == Qualification)
return getQualification();
return QVariant();
}

virtual bool setData(int role, const QVariant &value) {
bool changed = false;
if (role == Name){
changed = true;
setName(value.toString());
}
else if (role == UID){
changed = true;
setUID(value.toInt());
}
else if (role == Qualification){
changed = true;
setQualification(value.toString());
}
return changed;
}

private:
.....
};

class TaskData : public QMLPtrAbstractItem
{
Q_OBJECT

public:
enum TaskRoles {
UID = Qt::UserRole + 1,
Name,
BgColor1,
BgColor2,
XPos,
YPos,
Length,
CreationUser,
OrderType,
Active,
Index
};

static QHash<int, QByteArray> getQMLRoles(){
QHash<int, QByteArray> roles;
roles[UID] = "uid";
roles[Name] = "name";
roles[BgColor1] = "bgColor1";
roles[BgColor2] = "bgColor2";
roles[XPos] = "xPos";
roles[YPos] = "yPos";
roles[Length] = "length";
roles[CreationUser] = "creationuser";
roles[OrderType] = "orderType";
roles[Active] = "active";
roles[Index] = "index";
return roles;
}

TaskData() {
_bgcolor1 = "#FEFEFE";
_bgcolor2 = "#ededeb";
_active = false;
}
virtual ~TaskData() { }

.....

QVariant getData(int role) const {
if (role == UID)
return getUID();
else if (role == Name)
return getName();
else if (role == BgColor1)
return getBgColor1();
else if (role == BgColor2)
return getBgColor2();
else if (role == XPos)
return getXPos();
else if (role == YPos)
return getYPos();
else if (role == Length)
return getLength();
else if (role == CreationUser)
return getCreationUser();
else if (role == OrderType)
return getOrderType();
else if (role == Active)
return isActive();
else if (role == Index)
return getIndex();
return QVariant();
}

bool setData(int role, const QVariant &value) {
bool changed = false;
if (role == UID){
changed = true;
setUID(value.toInt());
}
else if (role == Name){
changed = true;
setName(value.toString());
}
else if (role == BgColor1){
changed = true;
setBgColor1(value.toString());
}
else if (role == BgColor2){
changed = true;
setBgColor2(value.toString());
}
else if (role == XPos){
changed = true;
setXPos(value.toInt());
}
else if (role == YPos){
changed = true;
setYPos(value.toInt());
}
else if (role == Length){
changed = true;
setLength(value.toInt());
}
else if (role == CreationUser){
changed = true;
setCreationUser(value.toInt());
}
else if (role == OrderType){
changed = true;
setOrderType(value.toInt());
}
else if (role == Active){
changed = true;
setActive(value.toInt());
}
else if (role == Index){
changed = true;
setIndex(value.toInt());
}
return changed;
}

private:
....
};

class OrderData : public TaskData
{
Q_OBJECT
....
}

class EventData : public TaskData
{
Q_OBJECT
...
}

class QMLPtrNotificationModel : public QAbstractListModel
{
Q_OBJECT
....
}

QVariant QMLPtrNotificationModel::data(const QModelIndex &index, int role) const
{
if (index.row() < 0 || index.row() > lst.size())
return QVariant();

const QMLPtrAbstractItem * const ptrItem = lst[index.row()];

return ptrItem->getData(role);
}

bool QMLPtrNotificationModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (index.row() >= 0 && index.row() < lst.size()) {

QMLPtrAbstractItem * const ptrItem = lst.at(index.row());
if(ptrItem){
bool changed = ptrItem->setData(role, value);

if(changed)
emit dataChanged(index, index);
return changed;
}
return false;
}else
qDebug() << "QMLPtrNotificationModel::setData( " << role << ")";

return false;
}

QML looks like that:

Repeater {
id: tasks
model: taskModel // is registered via context() - holds OrderData and EventData via virtual binding
delegate: TaskData { }
}


There is no QDeclarativeItem anymore - and it compiles and works

Different model instances of what?
Different Model instances: one for OrderData, one for EventData and one for EmpData

As i think the code will also work if i rename the classes to *Cpp? They just have the same names as the delegates - The QML delegate gets the
Data from the Model provided in the Repeater via accessing the roles of the model?!

TaskData.qml:

Text {
id: taskName
anchors.centerIn: parent
text: name; // sets the name as text -> accesses the given model and executes data() for the role "name" which returns the name !?
color: "white";
}

Where is my structural error? =(

-------------------

I tried it by renamin the classes to *Cpp - works either

class EmpDataCpp : public QMLPtrAbstractItem { ... }
class TaskDataCpp : public QMLPtrAbstractItem { ... }
class OrderDataCpp : public TaskDataCpp { .. .}
class EventDataCpp : public TaskDataCpp { .. .}

With Delegates:

Repeater {
id: empGrid
model: empModel
delegate: Employee { }
}
Repeater {
id: tasks
model: taskModel
delegate: TaskData { }
}

wysota
21st March 2012, 20:43
You are making it much more complicated than it needs to be. The item class needs not inherit QObject since you are not using any QObject related functionality in it. Next thing is that your model doesn't do anything fancy, it has functionality similar to QStandardItemModel, so you can use that instead of a custom model you need to care about. Third of all if you really insist on using a custom model class, there is no point in having various subclasses of QMLPtrAbstractItem because neither of these subclasses add anything to their base class functionality. Fourth of all if you get rid of QObject legacy, you can use objects instead of pointers to objects in your model which reduces possibilities of making an error somewhere in code.

Currently your code is equivalent to:


class MyModel : public QStandardItemModel {
public:
MyModel(QObject *parent = 0) : QStandardItemModel(parent) { setColumnCount(1); }
void setRoles(const QHash<int, QByteArray> & roleNames) { setRoleNames(roleNames); }
};

enum EmpRoles { Name = Qt::DisplayRole, UID = Qt::UserRole, Qualification };
enum TaskRoles { Name = Qt::DisplayRole, UID = Qt::UserRole, ... }
QHash<int, QByteArray> empNames;
empNames[EmpRoles::Name] = "name";
empNames[EmpRoles::UID] = "uid";
// etc.
MyModel empModel;
empModel.setRoles(empNames);



QHash<int, QByteArray> taskNames;
taskNames[TaskRoles::Name] = "name";
taskNames[TaskRoles::UID] = "uid";
MyModel taskModel;
taskModel.setRoles(taskNames);

My code seems much shorter and less error prone than yours.

shock
21st March 2012, 21:02
Hmm .. that's correct - so you mean (and yes of course that seems much easier) using the standardmodel that way is much better -> and for inserting i use insertRow()/appendRow() and Insert QStandardItems to it (using the setData of the StandardItem for specifying the data for the specific role)

Was that your point for so far? ;) (btw - big thanks for your patience! - i hope i am not too exhausting ^^)

wysota
21st March 2012, 23:42
I had a couple of points. Mainly:

no declarative items in the model,
no custom classes that do not add anything new to the ecosystem,
no need for custom models not doing anything different from what COTS are doing.