PDA

View Full Version : Questions about models



xtal256
8th August 2011, 01:19
I have a few questions about the models.

Say i have the following classes:

class SubItem {
// some properties...
};

class Item {
// some properties...
QList<SubItem> subitems;
};


Now, i have a UI which displays those items. It is an MDI, where each window has a form layout which displays the properties of the Item.

Most of the properties can just be displayed on a QLabel, but for the list of subitems i need a list-based widget (QListWidget/QListView or QTableWidget/View).

Currently, i am just using the Q*Widget classes. But i would like to use proper model/view programming and use a Q*View.
The problem is that i am not sure how to use a list model in this case. I started to think about the class definitions and realised i have no idea how it will work:

class SubItem...

class Item {
SubItemListModel getSubItemListModel();
}

class SubItemListModel {
Item* item; // A reference to the object which contains the list.

QList<SubItem> getItems() { item->getSubItems(); }
}


Should the SubItemListModel work with the data in the Item's list, or should the Item have it's list data in a SubItemListModel?


Also another minor question - i have my UI classes defined in .cpp files in one folder, and my "model" (or data) classes (Item, SubItem, etc) defined in another folder. This is separation of data and ui. But if i were to start using Qt's model classes (derived from QAbstractItemModel), where would they be defined? With my data classes, or the UI classes? Because as i see it, Q*Models are more about providing data to the UI, rather than actually storing data themselves.

xtal256
10th August 2011, 02:39
Any response?

One of the things i don't understand about Qt's model classes is that they are models of lists of items. What do i do when i want to model a single item? Obviously, such a model wouldn't be used with any list views (tree, table), but it seems that Qt's model/view is build only around this concept of lists.

Santosh Reddy
10th August 2011, 05:30
Should the SubItemListModel work with the data in the Item's list, or should the Item have it's list data in a SubItemListModel?
The better way is to have a Model (Say MyModel)


class MyModel : public QAbstractItemModel
{
public:
class SubItem {
// some properties...
};

class Item {
// some properties...
QList<SubItem> subitems;
};

SubItem mainItem;
};

now MyModel has all the knowledge of Item and SubItem class, it can act as model to any type view, just implement the virtual calls properly.


Also another minor question - i have my UI classes defined in .cpp files in one folder, and my "model" (or data) classes (Item, SubItem, etc) defined in another folder. This is separation of data and ui. But if i were to start using Qt's model classes (derived from QAbstractItemModel), where would they be defined? With my data classes, or the UI classes? Because as i see it, Q*Models are more about providing data to the UI, rather than actually storing data themselves.
I will say the model classes will go with the data.


One of the things i don't understand about Qt's model classes is that they are models of lists of items. What do i do when i want to model a single item? Obviously, such a model wouldn't be used with any list views (tree, table), but it seems that Qt's model/view is build only around this concept of lists.
If you closely observe the interface calls, they are not restricting you to have list as a model, you can have any variety of data structure as your data container, or even get the data from internet or dynamically create data based on state and return to the view.

xtal256
10th August 2011, 07:46
Ok, take the following example:

An application needs to store and display information about people and their job history. Each person has details such as name, address, D.O.B, etc, as well as a list of previous employment. Items in this list contains details such the job name/title, date started, date finished, etc.

Here we have a case where information is nested. There will be a list of people, and each person item will have a list of jobs.

Now, this application will display information about a person in a property page (e.g. MDI window). This window will have name, address, etc as QLabels in a QFormLayout, and the employment history will be in a table view below the QFormLayout.

C++ being an object-oriented language, a Person and Job class is created:

class Job {
QString name;
QDate startDate;
//...
};

class Person {
QString name;
QString address;
QList<Job> employmentHistory;
//...
};
The application maintains a list of Person objects. The person and job data is not stored in a database, let's just say that it is obtained some other way.


So, how would i do this using Qt's model/view classes? Do i need to scrap the Job and Person classes in favour of QAbstractItemModel subclasses, which return the properties (name, address, etc) as columns? And how do i handle the nesting?

Santosh Reddy
10th August 2011, 08:18
So good you already have container for data list of persons, and list of jobs for each person, you just need to give this data to the view using QAbstractItemModel. Here I will give a heads up


class Job {
QString name;
QDate startDate;
};

class Person {
QString name;
QString address;
QList<Job> employmentHistory;
};

class MyModel : public QAbstractItemModel
{
public:
int rowCount(QModelIndex index)
{
if(!index.isValid())
return p.count();
return 0;
}

int columnCount(QModelIndex index)
{
return 2; // say 2 columns, name & address (or name & start date)
}

QVariant data(QModelIndex index, int role)
{
if(role == Qt::DataRole)
{
if(index.column() == 0)
return p.at(index.row()).name;
if(index.column() == 1)
return p.at(index.row()).address;
}
return QVariant();
}

// implement other required interface calls also.
private:
QList<Person> p; // have your list of persons in here, or a pointer to point to the actual data
};

xtal256
11th August 2011, 01:07
Right, that much i got. Basically, the model returns the data's members as columns.
But that still doesn't help me, you have not mentioned how to model the Job list within each Person object.

xtal256
13th August 2011, 12:17
Anyone?

Surely others must have this problem, or at least understand the problem i have.

boudie
13th August 2011, 16:39
You could create a second Model, like Santosh showed you, serving Job info (the Jobs Model).
Now there are two ways you can go:
1. Make some kind of selection possible in the Jobs Model to select only jobs for a selected person, or
2. Give every person an instance of the Jobs model class.

If you do 1), you only have to connect one Jobs Model to the View. It will be updated with the correct values when scrolling through Persons, because Persons updates the Jobs selection.
If you go for 2), you have to connect (setModel) the right Jobs Model to the View when a new Person is selected.

I don't know what's best. And maybe there are better ways...

xtal256
14th August 2011, 12:47
You could create a second Model, like Santosh showed you, serving Job info (the Jobs Model).
Santosh never mentioned a second Model. He only ever showed me how to create a single model (which i already know how to do).

As for your ideas, it seems like Qt's models just aren't built for this kind of thing. For now i guess i will stick to using QTableWidgets for this.

boudie
14th August 2011, 23:16
What I meant was: you create a second model in the same way you created the first one.
I think Qt can handle this very well; I described two options to implement it. It's up to you to decide which option you think is best.
Once you've tried, feel free to ask for detailed help.

marcvanriet
14th August 2011, 23:48
As for your ideas, it seems like Qt's models just aren't built for this kind of thing. For now i guess i will stick to using QTableWidgets for this.

Models are build for this kind of things, but a model can be only for 1 kind of data : it cannot be a model both for persons, and also for jobs.

Compare it with a simple relational database : you have a table with persons, and a table with jobs, and they are linked together using some sort of index field. In this case, each record in the 'jobs' table will have a field to say whose job this is or was. If you make a GUI around this, you would make an instance of a 'PersonsModel' class, and an instance of a 'JobsModel' class. Then when a different person is selected in the PersonsModel, you could set a filter in the JobsModel to show only the job history of this person.


One of the things i don't understand about Qt's model classes is that they are models of lists of items. What do i do when i want to model a single item?

It is also my understanding that models are only for lists of items (persons, jobs, ...). In your example you want to keep the information about more than 1 person, don't you ? If you want to keep information about just 1 item (although I can't really think of a practical example of such a case), then you can can create a model which has just 1 entry in it - like a database table with just 1 record in it.

I'm really not very familiar with model/view programming, so I don't think I can be of any futher help. Maybe you can check out the Master-Detail example (http://doc.qt.nokia.com/latest/sql-masterdetail.html).

Regards,
Marc

xtal256
15th August 2011, 01:05
Models are build for this kind of things, but a model can be only for 1 kind of data : it cannot be a model both for persons, and also for jobs.

Compare it with a simple relational database : you have a table with persons, and a table with jobs, and they are linked together using some sort of index field. In this case, each record in the 'jobs' table will have a field to say whose job this is or was. If you make a GUI around this, you would make an instance of a 'PersonsModel' class, and an instance of a 'JobsModel' class. Then when a different person is selected in the PersonsModel, you could set a filter in the JobsModel to show only the job history of this person.
Yeah, i guess. The difference with me is that i am not using a database, i am simply storing data in C++ objects. So i am not storing Person and Job data in separate lists/tables, but rather each Person contains a list of Jobs.



It is also my understanding that models are only for lists of items (persons, jobs, ...). In your example you want to keep the information about more than 1 person, don't you ? If you want to keep information about just 1 item (although I can't really think of a practical example of such a case), then you can can create a model which has just 1 entry in it - like a database table with just 1 record in it.
Yes, I am storing information about more than one person*, but i am only displaying one at a time in the view.

I just wanted a simple way of modelling this, but i already have it working with QTableWidget and that way at least makes a lot more sense to me. I guess it would be possible in some way to use Qt's models here, but it seems like more trouble than it's worth.


* The person/job classes i posted is just an example, but they demonstrate what i have in my actual application.

Santosh Reddy
15th August 2011, 07:28
Ok, I somehow came to a conclusion that I may not able to explain you further without help of a working example.

So here we look at an example, which should be very similar to what you explained. Please make a BIG NOTE that this is just an example, and shows one of the ways to implement a model.

The key feature here is to relate the Person to the Job, and at run-time able to refer them from one another. In the example below i use a simple row & and type encoding which make it easy to relate job to a person. One could use any type of technique to relate Job & Person, simple pointers / run time type determination / holding pointer to each others etc.



//PersonJobModel.h
#ifndef PERSONJOBMODEL_H
#define PERSONJOBMODEL_H

#include <QList>
#include <QDate>
#include <QAbstractItemModel>

struct Job {
QString name;
QDate startDate;
};

struct Person {
QString name;
QString address;
QList<Job*> employmentHistory;
};

class PersonJobModel : public QAbstractItemModel
{
Q_OBJECT
public:
explicit PersonJobModel(QObject *parent = 0);
~PersonJobModel();

QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const;
QModelIndex parent(const QModelIndex &child) const ;
int rowCount(const QModelIndex &parent = QModelIndex()) const;
int columnCount(const QModelIndex &parent = QModelIndex()) const;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;

signals:

private:
enum RowType {
TypePerson = 0x00000000,
TypeJob = 0x01000000,
TypeMask = 0xFF000000
};

QList<Person*> persons;

inline quint32 encodeTypeRow(RowType type, int row) const
{ return type | ( ~TypeMask & row); }

inline int decodeRow(const QModelIndex& index) const
{ return static_cast<quint32>(index.internalId()) & ~TypeMask; }

inline RowType decodeType(const QModelIndex& index) const
{ return static_cast<RowType>(index.internalId() & TypeMask); }

inline bool isPerson(const QModelIndex& index) const
{ return decodeType(index) == TypePerson; }

inline bool isJob(const QModelIndex& index) const
{ return decodeType(index) == TypeJob; }
};


//PersonJobModel.cpp
#include "PersonJobModel.h"

PersonJobModel::PersonJobModel(QObject *parent)
: QAbstractItemModel(parent)
{
//Create some sample data, 4 Persons, 5 Jobs per person
for(int i = 0; i < 4; i++)
{
Person* person = new Person;
person->name = QString("Person %1").arg(i+1);
person->address = QString("Address of Person %1").arg(i+1);
for(int j = 0; j < 5; j++)
{
Job* job = new Job;
job->name = QString("Person %1, Job %2").arg(i+1).arg((i+1)*(j+1));
job->startDate = QDate(i+1, j+1, (i+1)*(j+1));
person->employmentHistory.append(job);
}
persons.append(person);
}
}

PersonJobModel::~PersonJobModel()
{
while(persons.count())
{
while(persons.at(0)->employmentHistory.count())
delete persons.at(0)->employmentHistory.takeFirst();
delete persons.takeFirst();
}
}

QModelIndex PersonJobModel::index(int row, int column, const QModelIndex &parent) const
{
if(!parent.isValid())
return createIndex(row, column, encodeTypeRow(TypePerson, 0));
else if(isPerson(parent))
return createIndex(row, column, encodeTypeRow(TypeJob, parent.row()));
return QModelIndex();
}

QModelIndex PersonJobModel::parent(const QModelIndex& child) const
{
if(child.isValid())
if(isJob(child))
return createIndex(decodeRow(child), 0, encodeTypeRow(TypePerson, 0));
return QModelIndex();
}

int PersonJobModel::rowCount(const QModelIndex& parent) const
{
if(!parent.isValid())
return persons.count();
else if(isPerson(parent))
return persons.at(parent.row())->employmentHistory.count();
return 0;
}

int PersonJobModel::columnCount(const QModelIndex& parent) const
{
return 2;
}

QVariant PersonJobModel::data(const QModelIndex& index, int role) const
{
if(role == Qt::DisplayRole)
{
if(isPerson(index))
{
if(index.column() == 0)
return persons.at(index.row())->name;
else if(index.column() == 1)
return persons.at(index.row())->address;
}
else if(isJob(index))
{
if(index.column() == 0)
return persons.at(decodeRow(index))->employmentHistory.at(index.row())->name;
else if(index.column() == 1)
return persons.at(decodeRow(index))->employmentHistory.at(index.row())->startDate.toString();
}
}
return QVariant();
}

#endif // PERSONJOBMODEL_H

and here is the output view

marcvanriet
15th August 2011, 21:25
The difference with me is that i am not using a database, i am simply storing data in C++ objects. So i am not storing Person and Job data in separate lists/tables, but rather each Person contains a list of Jobs.

Well, a database is also just a way of storing data. You can store the same data in C++ objects. It doesn't matter for the GUI. You must only have different models for accessing the data. The advantage of the model/view approach is that it is more generic and works with a lot of built-in widgets. For your C++ objects, you have to implement everything yourself.

Regards,
Marc

xtal256
16th August 2011, 02:00
Santosh, that code seems awful complicated compared to the few lines it takes to create QTableWidgetItems.
For example:


int row = 0;
jobsTable->setRowCount(person.jobs().size());
jobsTable->setColumnCount(2);
for (i = person.jobs().begin(); i != person.jobs().end(); i++) {
QTableWidgetItem* jobNameItem = new QTableWidgetItem((*i).name());
QTableWidgetItem* jobDateItem = new QTableWidgetItem((*i).startDate());

jobsTable->setItem(row, 0, jobNameItem);
jobsTable->setItem(row, 1, jobDateItem);
row++;
}


Also, your screenshot is a little different to what i want. I know the view can be changed independent of the model, but here is an example of what my "view" (UI) looks like:
6764

Santosh Reddy
16th August 2011, 03:37
As always you can use QTableWidget to keep things simple :)