PDA

View Full Version : one model for several viewings



someralex
9th December 2006, 18:31
Could somebody help me?
I have build a model based on QAbstractListModel with such inner structure:




QList <QPair<QString,QPair<QImage,QPair<QStringList,QStringList> > > > mainMap;


in data method of this model, I describe:




QVariant MainModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid()) return QVariant();
if (!index.row() >= mainMap.size()) return QVariant();
if (role == Qt::DisplayRole) return mainMap.at(index.row());
else return QVariant();
}


In which method view (based on QAbstractItemView or QAbstractListView) I need to state what data from mainMap I need to use in order to show in view (for example, I need to show mainMap.at(index.row()).first in the first list, and mainMap.at(index.row()).second.second.first) in the second one)?

wysota
9th December 2006, 19:02
The view displays items according to their indices - it'll display items [0 - columnCount()] x [0 - rowCount()] fetching the data using QAbstractItemModel::data. If you wish to implement your own view instead of using a default one, you have to fetch and display the data yourself using whatever means you find usefull. In general it means reimplementing all pure abstract methods and painting on the viewport (reimplementing the paintEvent). The concept of delegates is at your disposal if your view can easily differenciate between items - you can then use the standard item view infrastructure and implement the delegate (or use a default one) - in such situation data fetching will take place in the delegate (you'll be given proper indexes by the view).

By the way, I think your "data" method is invalid.You are returning a QPair, but you need to make it a QVariant and QPair is not castable to QVariant (and it doesn't make much sense to return QPair as a single role anyway, it's better to use mutliple roles and split the pair).

Are you sure you need such a complex data type? Maybe QVariantMap would be enough? Or maybe a custom structure? Let's see...


QList <QPair<QString,QPair<QImage,QPair<QStringList,QStringList> > > > mainMap;
This equals to:

QList<QPair<QString, QPair<QImage, customType> > > mainMap;
// where
struct customType {
QStringList first;
QStringList second;
};
If you continue to split the pairs like this, you'll get:

QList<customType> mainMap;
// where
struct customType {
QString first;
QImage second;
QStringList third;
QStringList fourth;
};

You can notice that all above types are castable to QVariant, so you can also use:

QList<QList<QVariant>> but QList<QVariant> is also castable to QVariant, so you can use
QList<QVariant> instead of that complex QPair combination.

Of course it is cleaner to do it using "customType", like so:


class MyModel::QAbstractTableModel{
//...

private:
QList<customType> _data;
};

QVariant MyModel::data(const QModelIndex & index, int role = Qt::DisplayRole ) const {
if(!index.isValid()) return QVariant();
//
int row = index.row();
if(role==Qt::DisplayRole){
switch(index.column()){
case 0: return _data.at(row).first;
case 1: return _data.at(row).second;
case 2: return _data.at(row).third;
case 3: return _data.at(row).fourth;
}
}
// ...
}

This way you get a four column model consisting of a string, an image and two lists of strings in a much cleaner way than returning the whole bundle at once.

You can then in the view fetch the data and render it:


//...
QModelIndex givenindex; // this is the item you have to render
int row = givenindex.row();
QString first = givenindex.sibling(row, 0).data(Qt::DisplayRole).toString();
QImage second = givenindex.sibling(row, 1).data(Qt::DisplayRole).toImage();
QStringList third = givenindex.sibling(row, 2).data(Qt::DisplayRole).toStringList();
QStringList fourth = givenindex.sibling(row, 3).data(Qt::DisplayRole).toStringList();
QStyleOptionViewItem option;
option.initFrom(this);
myCustomRenderer(painter, option, first, second, third, fourth);
//...

someralex
9th December 2006, 21:14
Thanks for such a detailed answer.

And could you please show me on example how can I determine the view classes if we have, for example, the QList<customType> list in MyModel model:


class MyModel::QAbstractTableModel{
//...

private:
struct customType {
QString first;
QImage second;
QStringList third;
QStringList fourth;
};

QList<customType> _data;
public:
void init_data();
};


After completing init_data we have the following list:

row=0

_data[row].first="item_1"
_data[row].second = "image_1"
_data[row].third[0] = "str_1_1"
_data[row].third[1] = "str_1_2"
_data[row].third[2] = "str_1_3"
_data[row].fourth[0] = "str_1_1"
_data[row].fourth[1] = "str_1_2"
_data[row].fourth[2] = "str_1_3"

row=1

_data[row].first="item_2"
_data[row].second = "image_2"
_data[row].third[0] = "str_2_1"
_data[row].third[1] = "str_2_2"
_data[row].third[2] = "str_2_3"
_data[row].fourth[0] = "str_2_1"
_data[row].fourth[1] = "str_2_2"
_data[row].fourth[2] = "str_2_3"

row[2]

_data[row].first="item_3"
_data[row].second = "image_3"
_data[row].third[0] = "str_3_1"
_data[row].third[1] = "str_3_2"
_data[row].third[2] = "str_3_3"
_data[row].fourth[0] = "str_3_1"
_data[row].fourth[1] = "str_3_2"
_data[row].fourth[2] = "str_3_3"

The question.
How can I bundle this given model with views in such a way, that all the elements of _data.first will be in one list? so let's say that QListView_1 list contains the following :
item_1
item_2
item_3

If now item_1 is highlited (i.e. row=0) and, for example, QListView_2 list displays QStringList from _data[0].third . When transferring to item_2 (row=1) in QListView_1 list, the contents of QListView_2 list correspondingly switches to _data[1].third and so on.

Thanks in advance.

wysota
10th December 2006, 00:30
An example says more than a thousand words ;)

The first widget on the left is a view that displays the whole model so that you can see how it is structured. The second one shows only the first column of the model and the third one shows the second list of strings for the item currently selected in the previous view.

Please note, that I restructured the model a bit to get rid of stringlists in favour of strings.
You could simplify it even more and place the image in the first column using the decoration role (instead of display role).

someralex
10th December 2006, 20:53
Thank you so much for the example. It really helped me to understand "model/view" mechanism. However, I couldn't understand the whole thing.
I have two questions appeared while completing the task. I'd be very grateful if you could help me to understand it better.
1.As you can see from my example, every item_1, item_2, item_3 corresponds to QImage, which has to transfer to another widget like a parameter by a click to the corresponding item in this list (in your example it is lv list). How can I get this QImage from a model by clicking to on of the strings of lv?
2.And how, by clicking to a string in lv_2, can I get another column of this string in a model that corresponds to the string, displayed in lv_2 list?
And one more thing, can you please show me on example how data method should look like, while creating such a model based on QAbstractItemModel?

Thank you very much in advance! You are really helping me out.

wysota
10th December 2006, 21:15
1.As you can see from my example, every item_1, item_2, item_3 corresponds to QImage, which has to transfer to another widget like a parameter by a click to the corresponding item in this list (in your example it is lv list). How can I get this QImage from a model by clicking to on of the strings of lv?

Exactly the same as I "transfered" the stringlist to lv2 - when you click on an item in a view, a signal is emitted that carries a QModelIndex of the item clicked. Now you have two choices:
1. The image is a second column of the model - in this situation you find an index which represents the second column of the same row using QModelIndex::sibling() and you query the model to return the data corresponding to the index using QAbstractItemModel::data()

2. The image is in the same column of the model but under a different role (like Qt::DecorationRole) - in this situation you use QAbstractItemModel::data() directly, just passing Qt::DecorationRole as the role you wish to fetch.


2.And how, by clicking to a string in lv_2, can I get another column of this string in a model that corresponds to the string, displayed in lv_2 list?
I don't understand what exactly you want here, but the general answer is - the same as in my example - using QAbstractItemView::setRootIndex() and probably some custom slot connected to clicked() signal of the view.


And one more thing, can you please show me on example how data method should look like, while creating such a model based on QAbstractItemModel?

You have an example in my first post in this thread. If you want something more complex, please state what you have problems with, so that I can create an example focused on what you need.

someralex
11th December 2006, 21:46
Here is my problem.

There's a directory, for example /docs, that contains a lot of other directories - /docs/doc_1, /docs/doc_2 and so on. Every directory in /docs folder contains files, for example with .tst extension, and one of the files is an image with .jpg extension.
There are two widgets to display the lists and one widget to display the graphical file and my class. The inner parameter to my class's constructor is the full path to the file, i.e. /docs/doc_1/file_1.tst

I thought that it would be easier to create a model with such an inner structure:


class MyModel::QAbstractTableModel{
//...
private:
struct customType {
QString first; //folder name in /doc ( doc_1, doc_2 and so on .. )
QImage second; //graphical file from this folder
QStringList third; // the list of .tst files in this folder (without .tst extension, i.e. file_1, file_2 and so on)
QStringList fourth; // the list of full paths to these files (/doc/doc_1/file_1.tst ,/doc/doc_1/file_1.tst and so on)
};
QList<customType> _data;
public:
void init_data();
};


and to bundle it with the corresponding views.

So, I need to display in one QListView (lv_1) the contents of /docs folder, thus, the field first from _data (doc_1, doc_2, doc_3 and so on). By clicking on item in lv_1 in another QListView (lv_2) I need to display the list of files from corresponding directory, i.e. the list lv_2 is filled with data from _data.third , and widget for graphical file displays the image from _data.second.
By clicking on item in lv_2 list the whole path to the file is transfered to my function, i.e. corresponding _data.fourth (for example the user clicked the second string in lv_2 - the value of _data.firth[1] is transfered to function, clicked the third string, we get _data.firth[2] and so on).

I'd be very grateful for help in solving this problem. It would help me to understand model/view mechanism better.

wysota
11th December 2006, 22:53
Ok, let's focus on the model first.
As your model is hierarchical, it makes more sense to subclass either QStandardItemModel or QAbstractItemModel than QAbstractTableModel

Let's make it a two-level hierarchy - in the top level you'll have a list of folders in /doc and the image that goes with that folder and in the bottom level you'll have a list of files in each folder.

In the top level you need to store two things - (1) name of the folder itself and (2) name of the image inside the folder (or the image itself, whatever you prefer).

In the bottom level you need two another things - (1) name of the file and (2) complete path to the file.

Note, that if you're operating on real folders and files, you don't need any custom data structures - you can use the model only as a data layer over the filesystem itself. If you're not operating on real files, then you'll need to store the data in the model.

Qt Model/View implementation allows an item to have different roles - like the text to display, an icon, a font, colour of text, colour of background, etc. You can also add new roles - in this situation I'd suggest using Qt::DisplayRole as the name of the folder for the upper level and name of the file for lower level and adding two custom roles - ImageRole that will hold the path to the image (or the image itself, whatever you prefer) of the upper level and PathRole which will store a complete path to a file in the lower level.

This way you'll end up with a single column model, which simplifies things a bit.

In your situation I'd use a QStandardItemModel as there is no need to implement an own model from scratch unless you want the approach I mentioned earlier, where you only provide a wrapper over the filesystem - in such situation you don't have any benefits of using QStandardItemModel.

Using QStandardItemModel is quite simple - you either use the item approach (QStandardItem) or the index approach (I prefer the latter). Only methods you'll need to use are index(), data(), setData(), insertRow(), insertColumn(), removeRow() and removeColumn(). Just use insertRow/Column and setData to fill your model using the roles I mentioned earlier and use data() to fetch data from the model. You have an example of using such approach in the example I provided few posts earlier.

QStandardItemModel is the way to go in most situations if you don't feel experienced enough to implement an own model from scratch. The only thing more you can do is to subclass QStandardItemModel and extend it with some convenience methods which will call insertRow(), insertColumn() and setData() for you, for example:


QModelIndex MyModel::addFolder(const QString &foldername, const QString &imagefile){
int row = rowCount(); // number of folders already in the model
insertRow(row); // add a new row
QModelIndex ind = index(row, 0); // index of the newly created item
insertColumn(0, ind); // add a column for future children of the item
setData(ind, foldername, Qt::DisplayRole);
setData(ind, imageFile, ImageRole); // ImageRole = Qt::UserRole
setData(ind, QPixmap(rootPath+"/"+foldername+"/"+imageFile+".jpg"), ImageDataRole); // ImageDataRole = ImageRole+1; (this keeps the actual image)
return ind;
}
QModelIndex MyModel::addFile(const QModelIndex &parent, const QString &filename){
int row = rowCount(parent); // get number of files
insertRow(row, parent); // insert new file
QModelIndex ind = index(row, 0, parent); // get index
setData(ind, filename, Qt::DisplayRole); // insert filename
QString filePath = rootPath+"/"+data(parent, Qt::DisplayRole).toString()+"/"+filename+".tst"; // assemble file path
setData(ind, filePath, PathRole); // PathRole = ImageDataRole+1
return ind;
}
Then you can use these like so:

MyModel model(rootPath); // rootPath holds the path to the "/docs" folder
QModelIndex fld = model.addFolder("doc_1", "imageInDoc1");
QModelIndex fil1 = model.addFile(fld, "file_1");
QModelIndex fil2 = model.addFile(fld, "file_2");
QModelIndex fld2 = model.addFolder("doc_2", "imageInDoc2");
// etc.

someralex
13th December 2006, 19:08
9th December 2006 17:30
wysota
An example says more than a thousand words ;-)


:-)

If it is not very hard for you, could you please make the working example? I can't understand theoretically how it works, especially the mechanism with role. It would be easier to grasp the idea using the working example.

I've made the following code:


QPair<QStringList,QStringList> listFiles;
QStringList listDirs;

const char * path = "/docs";

QDir dir(path);
if (!dir.exists()) qFatal ("Dir not found: %s",dir.dirName());
QFileInfoList fileList;
QFileInfoList dirList = dir.entryInfoList(QDir::Dirs);

foreach (QFileInfo curDir, dirList){
if (curDir.isDir() & curDir.baseName()!="") {
QDir currentDir = curDir.absoluteFilePath();
listDirs.append(currentDir.dirName());
fileList = currentDir.entryInfoList(QDir::Files);
foreach (QFileInfo file, fileList){
listFiles.second.append(file.absoluteFilePath());
listFiles.first.append(file.baseName());
}
}
}


I would be very grateful for the working example.

wysota
13th December 2006, 19:51
Did you go through the examples that come with Qt?

someralex
13th December 2006, 20:34
I could modify the example /examples/itemview/sampletreeview to use for my own purposes, but couldn't get the role. But there you use two columns for one item, and I would like to understaned how to do it using the role.

wysota
13th December 2006, 21:18
I didn't say to modify the code, but to understand it. At least one of the examples provided uses custom roles.

I can't write the complete code for you - you won't learn this way. Besides, I have given you an almost complete solution using QStandardItemModel, what more do you need?

someralex
14th December 2006, 10:55
I really can't figure it out. The deadline is coming, and I still don't get it. And it is always easier to understand something using the example. If you can, just provide the approximate code, so I could get it clear. I really need it. I'd be very grateful.
Thank you in advance.

wysota
14th December 2006, 11:17
What's wrong with my example a few posts earlier? Or with the one before? If you assemble them both, you get a complete solution for your problem. The first example shows you how to retrieve data from the standard item model and the last shows how to add data to the model. What more do you need? Just wrap the methods I have written into a complete class and you're ready to go.

someralex
15th December 2006, 19:00
I have figured everything out! Thanks so much.
I just read it through, studied the code, read the assistent and got it all clear.
I have done the following methods:


QModelIndex myModel::addFolder(const QDir &foldername){
int row = rowCount(); // number of folders already in the model
insertRow(row); // add a new row
QModelIndex ind = index(row, 0); // index of the newly created item
insertColumn(0, ind); // add a column for future children of the item
setData(ind, foldername.dirName(), Qt::DisplayRole);
return ind;
}

QModelIndex myModel::addFile(const QModelIndex &parent, const QFileInfo &file){
int row = rowCount(parent); // get number of files
insertRow(row, parent); // insert new file
QModelIndex ind = index(row, 0, parent); // get index
setData(ind, file.completeBaseName(), Qt::DisplayRole); // insert filename in
setData(ind, file.absoluteFilePath(), Qt::UserRole); // insert full path in Qt
return ind;
}
QModelIndex myModel::addImage(const QModelIndex &parent, const QPixmap &image){
setData(parent, image, Qt::UserRole+1);
return parent;
}




void MainWindow::changeItemInLV2(const QModelIndex &myIndex){
imageLabel->setText(myIndex.sibling(myIndex.row(),0).data(Qt:: UserRole).toString());
}

And how to create a method to get an image from Qt::UserRole+1 I can't understand.
QVariant doesn't have toPixmap method.
I'm asking for your help. again.

wysota
15th December 2006, 19:24
And how to create a method to get an image from Qt::UserRole+1 I can't understand.
QVariant doesn't have toPixmap method.


QPixmap pix = qvariant_cast<QPixmap>(data(index, ImageRole));

someralex
15th December 2006, 20:08
QPixmap pix = qvariant_cast<QPixmap>(data(index, ImageRole));





void MainWindow::changLV1Item(const QModelIndex &fld){
lv2->setRootIndex(fld);
imageLabel->setPixmap(qvariant_cast<QPixmap>(fld.sibling(fld.row(),0).data(Qt::UserRole+1)));
}


However, it doesn't return anything - QPixmap doesn't return from the model. I think the problem is in Qt::UserRole+1. In my previous post I have written how I fill the model with images. Am I doing something wrong?

wysota
15th December 2006, 20:19
QModelIndex myModel::addImage(const QModelIndex &parent, const QPixmap &image){
setData(parent, image, Qt::UserRole+1);
return parent;
}

Here you add the image to the "parent". Maybe you're simply using a wrong index? Try using Qt::DecorationRole instead of Qt::UserRole+1 and display the model in QTableView or QTreeView. The pixmap should be there. If it's not, then maybe the pixmap is invalid?

someralex
15th December 2006, 22:35
QModelIndex myModel::addImage(const QModelIndex &parent, const QPixmap &image){
setData(parent, image, Qt::DecorationRole);
return parent;
}

Qt::DecorationRole doesn't work also

QPixmap works. The code below displays the image:


imageLabel = new QLabel;
imageLabel->setPixmap(QPixmap("/docs/doc_1/1.BMP"));

someralex
15th December 2006, 22:52
Everything went well! It works. I had a little mistake in my code. Qt::UseRole+1 works. I'm really grateful for your help. Many thanks.