one model for several viewings
Could somebody help me?
I have build a model based on QAbstractListModel with such inner structure:
Code:
QList <QPair<QString,QPair<QImage,QPair<QStringList,QStringList> > > > mainMap;
in data method of this model, I describe:
Code:
{
if (!index.
isValid()) return QVariant();
if (!index.
row() >
= mainMap.
size()) return QVariant();
if (role == Qt::DisplayRole) return mainMap.at(index.row());
}
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)?
Re: one model for several viewings
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...
Code:
QList <QPair<QString,QPair<QImage,QPair<QStringList,QStringList> > > > mainMap;
This equals to:
Code:
QList<QPair<QString, QPair<QImage, customType> > > mainMap;
// where
struct customType {
};
If you continue to split the pairs like this, you'll get:
Code:
QList<customType> mainMap;
// where
struct customType {
};
You can notice that all above types are castable to QVariant, so you can also use:
Code:
QList<QList<QVariant>>
but QList<QVariant> is also castable to QVariant, so you can use instead of that complex QPair combination.
Of course it is cleaner to do it using "customType", like so:
Code:
//...
private:
QList<customType> _data;
};
//
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:
Code:
//...
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();
option.initFrom(this);
myCustomRenderer(painter, option, first, second, third, fourth);
//...
Re: one model for several viewings
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:
Code:
//...
private:
struct customType {
};
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.
1 Attachment(s)
Re: one model for several viewings
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).
Re: one model for several viewings
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.
Re: one model for several viewings
Quote:
Originally Posted by
someralex
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.
Quote:
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.
Quote:
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.
Re: one model for several viewings
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:
Code:
//...
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.
Re: one model for several viewings
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:
Code:
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;
}
int row = rowCount(parent); // get number of files
insertRow(row, parent); // insert new file
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:
Code:
MyModel model(rootPath); // rootPath holds the path to the "/docs" folder
QModelIndex fld
= model.
addFolder("doc_1",
"imageInDoc1");
QModelIndex fld2
= model.
addFolder("doc_2",
"imageInDoc2");
// etc.
Re: one model for several viewings
Quote:
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:
Code:
QPair<QStringList,QStringList> listFiles;
const char * path = "/docs";
if (!dir.exists()) qFatal ("Dir not found: %s",dir.dirName());
QFileInfoList fileList;
QFileInfoList dirList
= dir.
entryInfoList(QDir::Dirs);
if (curDir.isDir() & curDir.baseName()!="") {
QDir currentDir
= curDir.
absoluteFilePath();
listDirs.append(currentDir.dirName());
fileList
= currentDir.
entryInfoList(QDir::Files);
listFiles.second.append(file.absoluteFilePath());
listFiles.first.append(file.baseName());
}
}
}
I would be very grateful for the working example.
Re: one model for several viewings
Did you go through the examples that come with Qt?
Re: one model for several viewings
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.
Re: one model for several viewings
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?
Re: one model for several viewings
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.
Re: one model for several viewings
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.
Re: one model for several viewings
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:
Code:
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;
}
int row = rowCount(parent); // get number of files
insertRow(row, parent); // insert new file
setData(ind, file.completeBaseName(), Qt::DisplayRole); // insert filename in
setData(ind, file.absoluteFilePath(), Qt::UserRole); // insert full path in Qt
return ind;
}
setData(parent, image, Qt::UserRole+1);
return parent;
}
Code:
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.
Re: one model for several viewings
Quote:
And how to create a method to get an image from Qt::UserRole+1 I can't understand.
QVariant doesn't have toPixmap method.
Code:
QPixmap pix
= qvariant_cast<QPixmap>
(data
(index, ImageRole
));
Re: one model for several viewings
Quote:
QPixmap pix = qvariant_cast<QPixmap>(data(index, ImageRole));
Code:
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?
Re: one model for several viewings
Code:
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?
Re: one model for several viewings
Code:
setData(parent, image, Qt::DecorationRole);
return parent;
}
Qt::DecorationRole doesn't work also
QPixmap works. The code below displays the image:
Code:
imageLabel
->setPixmap
(QPixmap("/docs/doc_1/1.BMP"));
Re: one model for several viewings
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.