PDA

View Full Version : Complex data structure upgrade to QAbstractListModel



MarkoSan
19th October 2015, 16:07
Dear Sirs and Madams!

I am working on Qt/QML with mysql db application I have following situation:
At startup the user must login into system and this part works ok (QML<==>Qt/C++ interaction). Now, I fetch data from several tables using their models and from this data I would like to make subclassed QAbstractListModel class, which will work with following data structure:


typedef QMap<QPair<QString, QString>, QList<UeOrderRecord*>> UeTypeOrders;

Let me clarify: I have logged user (Id, Name, Image, Password), I have some products (Id, Name, Image, Price, ...) and some positions (Id, Name, ...). Now, for evey logged user, new (empty) model with UeTypeOrders must be created. How do I, for example, handle data in UeTypeOrders in virtual data() method?

Sincerely yours,
Marko

anda_skoa
24th October 2015, 17:00
The data() method of a list model needs to do two things:

1) Find the data structure instance for the row as given with index.row()
2) Return the data field as given by role

So if you have a list or vector of UeTypeOrders, a simple index access should take care of (1).
For (2) you specify roles for each of the data fields you'd like to have access to and let a switch() on the role argument handle the access to the respective field.

Cheers,
_

MarkoSan
25th October 2015, 11:47
The data() method of a list model needs to do two things:

1) Find the data structure instance for the row as given with index.row()
2) Return the data field as given by role

So if you have a list or vector of UeTypeOrders, a simple index access should take care of (1).
For (2) you specify roles for each of the data fields you'd like to have access to and let a switch() on the role argument handle the access to the respective field.

Cheers,
_
Here is my data[ method now:


QVariant UeOrdersModel::data(const QModelIndex &index,
int role) const
{
switch(role)
{
case ueRoleUserId:
{
return this->ueOrders()->value(QPair<QString, QString>(this->ueUserName(),this->uePlaceName())).at(index.row())->ueUserId();
} break;

case ueRolePlaceId:
{
return this->ueOrders()->value(QPair<QString, QString>(this->ueUserName(),this->uePlaceName())).at(index.row())->uePlaceId();
} break;

case ueRoleProductId:
{
return this->ueOrders()->value(QPair<QString, QString>(this->ueUserName(),this->uePlaceName())).at(index.row())->ueProductId();
} break;

case ueRoleProductName:
{
return this->ueOrders()->value(QPair<QString, QString>(this->ueUserName(),this->uePlaceName())).at(index.row())->ueProductName();
} break;

case ueRoleProductPriceSell:
{
return this->ueOrders()->value(QPair<QString, QString>(this->ueUserName(),this->uePlaceName())).at(index.row())->ueProductPriceSell();
} break;

case ueRoleProductQuantity:
{
return this->ueOrders()->value(QPair<QString, QString>(this->ueUserName(),this->uePlaceName())).at(index.row())->ueProductQuantity();
} break;

case ueRoleOrderAmount:
{
return this->ueOrders()->value(QPair<QString, QString>(this->ueUserName(),this->uePlaceName())).at(index.row())->ueOrderAmount();
} break;

default:
{
return QVariant();
} break; // default
} // switch

return QVariant();
} // data

Am I getting close and my next question is if the data structure in the model is updated at runtime (i.e, internal data structure record record is added, changed or deleted), does this mean the model should be implemented as editable or still as read only? And if so (editable), I do not know what to put in, for exameple, method insertRow(s)?

anda_skoa
25th October 2015, 14:58
Here is my data[ method now:

A lot of code duplication, this could be written much more compact.

1) no need for "this->"
2) everything up to the last "->" is equal to all cases. can be done outside the switch
3) call cases have return. no need to break
4) no case has local variables. no need to block braces.



Am I getting close and my next question is if the data structure in the model is updated at runtime (i.e, internal data structure record record is added, changed or deleted), does this mean the model should be implemented as editable or still as read only?

Can still be read only, the model only needs to signal its changes to the view(s).

Cheers,
_

MarkoSan
27th October 2015, 13:59
A lot of code duplication, this could be written much more compact.

1) no need for "this->"
2) everything up to the last "->" is equal to all cases. can be done outside the switch
3) call cases have return. no need to break
4) no case has local variables. no need to block braces.


Can still be read only, the model only needs to signal its changes to the view(s).

Cheers,
_

How do I signal changeds to the view, i.e., I have now following method:


void UeOrdersModel::ueAddOrder(const QString& userName,
const QString& placeName,
const QImage& productImage,
const QString& productId,
const QString& productName,
const QString& productPriceSell,
const quint64& productQuantity,
const QString& productVAT)
{
QPair<QString, QString> key=QPair<QString, QString>(userName, placeName);
if(this->ueOrders()->contains(key))
{
double priceSell=productPriceSell.toDouble();
double priceVAT=productVAT.toDouble();
double amountWithoutVAT=productQuantity*priceSell;
double amountWithVAT=productQuantity*(priceSell+(priceSel l*priceVAT));
this->ueOrders()->value(key)->append(new UeOrderRecord(this,
userName,
placeName,
productImage,
productId,
productName,
productPriceSell,
productQuantity,
QString::number(amountWithoutVAT),
productVAT,
QString::number(amountWithVAT)));
} // if
} // ueAddOrder

How do I now send dataChanged() signal from this method to update QML ListView?

anda_skoa
27th October 2015, 15:03
1) Determine the row to which this entry belongs
2) get the QModelIndex for that row
3) emit dataChanged with that index for begin/end and either keep the third parameter empty or add all roles that have changed to the list of roles

But ueAddOrder sounds like this would add something, are you sure this only changes existing data?

Cheers,
_

MarkoSan
27th October 2015, 15:07
With this method I add record to model internal data structure. Do you have some example of your previous post?

anda_skoa
27th October 2015, 17:39
With this method I add record to model internal data structure

ok, but does that mean new rows in the model or just data in existing rows changing?



Do you have some example of your previous post?


const int affectedRow = ....;

const QModelIndex changedIndex = index(affectedRow, 0);

emit dataChanged(changedIndex, changedIndex); // if all roles changed or if you don't know which roles


Cheers,
_

MarkoSan
27th October 2015, 17:52
ok, but does that mean new rows in the model or just data in existing rows changing?




const int affectedRow = ....;

const QModelIndex changedIndex = index(affectedRow, 0);

emit dataChanged(changedIndex, changedIndex); // if all roles changed or if you don't know which roles


Cheers,
_

I think this:
1) If I add order to already logged user, that means adding record to model's internal data's QList - QPair and QMap exist.
2) If already logged user changes target "place", then model's internal data's QPair with empty QList is added to EXISTING QMap
3) However, if new user logs into system, then new object is created and added to QMap

So, in number 3 scenario I think new row must be added, or I am wrong?

anda_skoa
27th October 2015, 19:21
This is your data, only you know which criteria you used for rowCount().

Well, we can guess.
We can guess that the row count is the size/length/count of the QList returned by ueOrders()->value().

And you append to that.
So your row count changes.
So the model has now one row more than it had
Which means you added a row.
So you should emit signals for row insertion.
Which you would do with beginInsertRows() and endInsertRows().

All just guessing of course, if you are sure you are not adding rows, then just emit the data change signal for the affected row.

Cheers,
_

MarkoSan
28th October 2015, 08:11
This is your data, only you know which criteria you used for rowCount().

Well, we can guess.
We can guess that the row count is the size/length/count of the QList returned by ueOrders()->value().

And you append to that.
So your row count changes.
So the model has now one row more than it had
Which means you added a row.
So you should emit signals for row insertion.
Which you would do with beginInsertRows() and endInsertRows().

All just guessing of course, if you are sure you are not adding rows, then just emit the data change signal for the affected row.

Cheers,
_

Ok, you have a point, but I am verry sorry, I am total confused at the moment. Let's start from the begining. Let say, you and I work in a restaurant with this piece of software of mine. I login into system and at that point the mentioned data structure


/*
* QPair QString1 user name
* QPair QString2 place name
* QMap QList list of orders
*/
typedef QMap<QPair<QString, QString>, QList<UeOrderRecord*>*> UeTypeOrders;

is appended with QMap<QPair<username, placename> empty QList>. That means, I've logged into system using my credentials and I have empty orders QList. Since I need to encapsulate this data structure into model to be used by QML ListView, I "packed" this data structure into subclassed QAbstractListModel. Now, let's say, you come to work and you also login into system, which means another object is appended to UeTypeOrder, with your credentials and empty orders list. Does at this point model changes or not? Or does it only change when I add order to third component, QList? How do I setup my QAbastractListModel, does this means model is readonly or not - should I implement read-only model it editable model? And should I return from model (using data() method, for instance) only QList or whole QMap?

Sincerely,
Marko

anda_skoa
28th October 2015, 09:01
Now, let's say, you come to work and you also login into system, which means another object is appended to UeTypeOrder, with your credentials and empty orders list. Does at this point model changes or not?

This depends on what your model does.
Does it show the logged in people? Does it show the orders of all people? Does it show the combined orders of all logged in people? Does it only show the orders of one logged in person?

This is your model, you know what aspect of your data your model exposes.

1) Determine what you want to show in the view
2) Find out how to get that data from your data structure
3) Create a model that does that

At this point it will be obvious which changes to the data structure will change the amount of data exposed by the model and which changes will only change values.

Cheers,
_

seyed_m
6th January 2017, 21:11
I don't understand why you use this way for presenting data.
In a recent project, I created model as following:

class Number {
public:
QString number;
bool isChecked;
};

class NumberModel: public QAbstractListModel {
//...
private:
QList<Number *> m_list;
};

class Contact {
public:
QString name;
NumberModel numbers;
};

class ContactModel : public QAbstractListModel {
//...
private:
QList<Contact *> m_list;
};

class Phonebook {
public:
QString title;
QDate creationDate;
ContactModel contacts;
};

class PhonebookModel: public QAbstractListModel {
//...
private:
QList<Phonebook *> m_list;
};

in the data() method you can return your sub-model as a QVariant which is created using QObject *.
In QML you can use your model as follow:


ListView {
model: phonebookModel
delegate: Component{
Text {
model.title
}
MouseArea {
onClicked: { myDialog.model = model.contacts; myDialog.open(); }
}
}
}

Nornimices
17th February 2017, 11:52
I am not sure about it.