PDA

View Full Version : Sharing Selections between Model and ProxyModel



mentat
27th June 2006, 20:29
Hi All-

I am new to Qt 4 (I have used Qt 3 for sometime however), and I am trying to get a handle on the use of the model/view architecture. Specifically, the data that will ultimately populate the model is hierarchal in nature and I plan on having two separate views: one a QTreeView and the other a QTableView that will just show the leaves of the tree. I am assuming that I will need to implement a proxymodel in order to mask a tabular model on top of the hierarchical model and translate indices between the two types of views.

Working under that assumption, I have been trying to learn the ins and outs of how a proxymodel and a model are supposed to interact with one another. Using Qt's sortingmodel example - found in QTDIR/examples/itemviews/sortingmodel/main.cpp - as a starting point, I have been trying to figure out how one is supposed to share a selection between the two views. Therefore, I have made the following change to that aforementioned main.cpp file:

----------------------------------------------------------------------------------------------
sortedView.header()->setClickable(true);

sortedView.setSelectionModel(unsortedView.selectio nModel()); // line added

sortedView.show();
----------------------------------------------------------------------------------------------

As may come to no surprise to some of you, this didn't work. I know that I should be using mapSelectionToSource and/or mapSelectionFromSource somewhere, but I'm not sure where. Should I re-implement QTreeView in order to tell the object that its referenced model is a proxymodel? That doesn't seem right. I would assume that since the QSortFilterProxyModel was developed by Trolltech, that it already has the important code where its needed, so that's doesn't seem right either. Should I make a change to the TreeModel referenced in the example? Any thoughts on how to get going here?

Thank you for your time!

wysota
27th June 2006, 22:03
As far as I understand models and selections, you can't share a selection between different models, becuase their indexes don't match. If you want to have a simmilar selection model for both models, you have to implement a controller which will be connected to selectionChanged() signals of the models and will map selections from one model to the other (for example using the same scheme the proxy model uses).

mentat
28th June 2006, 18:08
As far as I understand models and selections, you can't share a selection between different models, becuase their indexes don't match. If you want to have a simmilar selection model for both models, you have to implement a controller which will be connected to selectionChanged() signals of the models and will map selections from one model to the other (for example using the same scheme the proxy model uses).

Thanks. I'll give that a try. Since a wrapper is by definition an interface to some underlying structure, I would have thought this would be something encapsulated in some way since I can't imagine that I would be the only one that would need to share selections between a model and a WRAPPER of that model.

wysota
28th June 2006, 21:42
The point is that you need to translate selections between different models. It's not a wrapper -- "controller" or "synchroniser" are better words for that. You are really implementing an additional listener layer -- you need to monitor for changes in one selection model and react by changing the other one (just make sure you don't fall into an infinite loop).

mentat
28th June 2006, 22:04
The point is that you need to translate selections between different models. It's not a wrapper -- "controller" or "synchroniser" are better words for that. You are really implementing an additional listener layer -- you need to monitor for changes in one selection model and react by changing the other one (just make sure you don't fall into an infinite loop).

I actually meant that the proxy is considered a wrapper..at least that's how it is documented, but I'll give your suggestion a try.

In general, I've had a lot of trouble with proxymodels, and understanding how they are related with the underlying model. For instance, for the life of me, I can not get a tablular view of the data by re-implementing mapFromSource and mapToSource to translate or compress the underlying hierarchical model into a tabular model. I guess my question becomes: when are these map*Source functions called when the view is being built since obviously the QTableView itself doesn't call them? Is there any better documentation or real examples of M/V then those on the Qt site? In general, when it comes from starting from square one as I am, it is extremely difficult to figure out which end is up based on a few sentences here or there.

wysota
28th June 2006, 22:18
I actually meant that the proxy is considered a wrapper..at least that's how it is documented, but I'll give your suggestion a try.

It is a wrapper in that way that it transforms the already existing model to provide a new hierarchy.



In general, I've had a lot of trouble with proxymodels, and understanding how they are related with the underlying model. For instance, for the life of me, I can not get a tablular view of the data by re-implementing mapFromSource and mapToSource to translate or compress the underlying hierarchical model into a tabular model. I guess my question becomes: when are these map*Source functions called when the view is being built since obviously the QTableView itself doesn't call them? Is there any better documentation or real examples of M/V then those on the Qt site? In general, when it comes from starting from square one as I am, it is extremely difficult to figure out which end is up based on a few sentences here or there.

QTableView doesn't have to call them. It calls data() to read the data from the model. And then the proxy should map the request (for example the index) using methods you mentioned to fetch the data from the underlying model and give that data back to the object which requested it (like the view in this case).

I belive that if you wanted to make a simple transformation of the model where you'd like to transpose the model (exchange columns with rows), you'd have to implement such a proxy:


class TransposeProxyModel : public QAbstractProxyModel{
public:
TransposeProxyModel(QObject *p = 0) : QAbstractProxyModel(p){}
QModelIndex mapFromSource ( const QModelIndex & sourceIndex ) const{
return index(sourceIndex.column(), sourceIndex.row());
}
QModelIndex mapToSource ( const QModelIndex & proxyIndex ) const{
return sourceModel()->index(proxyIndex.column(), proxyIndex.row());
}
QModelIndex index(int r, int c, const QModelIndex &ind) const{
return createIndex(r,c);
}
QModelIndex parent(const QModelIndex&) const {
return QModelIndex();
}
int rowCount(const QModelIndex &) const{
return sourceModel()->columnCount();
}
int columnCount(const QModelIndex &) const{
return sourceModel()->rowCount();
}
QVariant data(const QModelIndex &ind, int role) const {
return sourceModel()->data(mapToSource(ind), role);
}

};

Edit: I forgot to implement the rest of pure virtual methods :)

wysota
28th June 2006, 22:36
And a small example to illustrate.

mentat
28th June 2006, 22:54
QVariant data(const QModelIndex &ind, int role) const {
return sourceModel()->data(mapToSource(ind), role);
}

};



I think that may have been what I was forgetting. The good news, while the app is now crashing (probably due to a still-to-be-bugfixed transformation algo on my part), it is now calling map*Source correctly! Thanks for your help. I'll bug fix the code this evening and see what I can see. Hopefully this will mean that I have a working proxy model. Then I would just need to implement the selection code noted previously, and everything should be working.

mathpup
27th November 2006, 03:24
And a small example to illustrate.

I have a couple of questions. It appears that changes in the table on the left are not reflected in the table on the right until I move the slider or shade-and-unshade the window or do something else that causes a repaint. Is there a way to keep the model and its proxy and view in sync?

Also, I noticed that changing items on the right (the proxy model's view) does not result in changing items on the left. Is there an easy way to make it bi-directional?

wysota
27th November 2006, 11:09
The example is very simple and only illustrates the way of handling things. To implement features you want, you'd probably have to reimplement setData() and connect proper signals and slots so that changes propagate between models.

mathpup
28th November 2006, 04:41
By reimplementing setData() in the proxy model, I managed to get changes in the proxy model to be reflected by in the original model.

However, I am still having a slight problem keeping the proxy in sync with the original model. For example, if I changed row 1, column 1, from "1" (the original value) to "A", the proxy model's view is not updated until I do something like move the scroll bar or something else that generates events or signals.

Which signal should I connect to which slot (and which object)?

reblag
26th January 2010, 14:15
The transpose proxy code (http://wiki.qtcentre.org/index.php?title=Transpose_Proxy_Model) works fine but only with quadratic models where there are the same number of rows and columns. If there are e.g. more columns than rows the data() function is not called for all cells that it needs to be called for. Is there anyone who knows how to implement this transpose with a on-quadratic model?

wysota
26th January 2010, 18:03
The model is broken, it was just an example. But it should work for non-square models too, I don't see what would prevent it from that. The only two things that are not in the model is reacting to changes in the source model and handling hierarchical source models. Apart from that the example is fully functional.

reblag
27th January 2010, 15:05
It does not work for non-square models since the data-functions is not called enough number of times if there is e.g. more columns than rows

wysota
27th January 2010, 18:31
You are wrong, I just checked it. The model works fine for non-square models.


int main(int argc, char **argv){
QApplication app(argc, argv);
QTableView v1;
QTableView v2;
QStandardItemModel model(3,5);
for(int i=0;i<5;i++)
for(int j=0;j<3;j++){
model.setData(model.index(j,i), qrand()%20);
}
v1.setModel(&model);
TransposeProxyModel proxy;
proxy.setSourceModel(&model);
v2.setModel(&proxy);
v1.show();
v2.show();
return app.exec();
}