QTableView: updating the model and visible area
-
This is a two part question.
First:
The methodology for updating a view based on changes in the model isn't quite clear to me. In my case, I have several model/view combinations on different tabs. The data in some of the models are dependent on data in other models. So, for example, if a cell is changed in View1, model1 should be changed accordingly. However, model2's data is dependent on model1, so model2 is changed when model1 changes. How do I make sure that view2 updates accordingly?Second:
I can't figure out how to restore the current visible area in a view. So, say view 2 is currently showing a rectangle of cells ( 2, 3, 11, 13 ). Now, I would like to be able to restore this visible area sometime later. At the very least, I would like to know the top-left visible cell and scroll to that item later. I can't figure out a good way to do this.Thanks for the help
-
To question1:
there is an article in the online help regarding "Model-View":http://doc.qt.nokia.com/latest/model-view-programming.html which is quite long. If you change something inside your model, you have to call some methods emit some signals:
- Changing a value MUST lead to emit dataChanged(...)
- adding rows / columns must be sourounded by beginInsertRows(...) and endInsertRows(...) respectively beginInsertColumns(...) and endInsertColumns(...)
- same applies to removing of rows / columns (beginRemove...)
- if you are ching much, you could also call reset() inside the model, so the view must reinitialise itself (--> selections are away after that, the view starts in row 0, opened items are closed here)
This all must be done from within model2 then.
The connected views will update automatically then.In the German wiki, we are working on a MVC tutorial which is (hopefully) easy to understand later on, but it's in starting phase and will take some time to finish it. I think, when it's finished and the others say, it's good, we will also translate it to English and put it to the main wiki.
-
To question2:
you could try columnAt / rowAt if you are using a QTableView.
also "indexAt()":http://doc.qt.nokia.com/latest/qtableview.html#indexAt could helpfor restoring, you could use: "scrollTo()":http://doc.qt.nokia.com/latest/qabstractitemview.html#scrollTo
You could also get the position out of the scrollbars:
@
QAbstractScrollArea::horizontalScrollBar ()->value ()
@but that does not give you the model indexes.
-
No, that's not what dusktreader asked.
If you want to know the model index at a certain point in your view, use QAbstractItemView::indexAt(). Create a QPersistentItemIndex, and use that index to restore the view later on with the scrollTo that Gerolf pointed out earlier.
-
Regarding the first question:
Model1 sends signals, which view1 connects to and redraws the changed parts.
You can connect newly added slots of your model2 to the very same signals of model1 (dataChanged(), rowsInserted(), columnsInsert() etc.) and manipulate model2 accordingly. If you do it "the right" way (i.e. using beginInsertRows(), endInsertRows(), dataChanged(), etc. - see Gerolfs answer) your view2 is signaled too and updates itself.
-
[quote author="Gerolf" date="1294992644"]
- Changing a value MUST lead to emit dataChanged(...)
[/quote]
Do I need to explicitly connect this to a slot in my QTableView?
- Changing a value MUST lead to emit dataChanged(...)
-
[quote author="Gerolf" date="1294993008"]
you could try columnAt / rowAt if you are using a QTableView.
also "indexAt()"
[/quote]That is all fine and good, but how do I find the positional coordinates of the view? Could I just call
@QModelIndex ul = tblView.indexAt( QPoint( 0, 0 ) );
// do some stuff
tblView.scrollTo( ul );@ -
[quote author="dusktreader" date="1295032882"][quote author="Gerolf" date="1294992644"]
- Changing a value MUST lead to emit dataChanged(...)
[/quote]
Do I need to explicitly connect this to a slot in my QTableView?
[/quote]
No, you don't. Your view will connect to the appropriate signals in the model when you set the model on the view using setModel(). You just need to make sure the signal is emitted (if you subclassed QAbstractItemModel or one of the other Qt item models; if you use an existing model, you can trust it will behave correctly.) - Changing a value MUST lead to emit dataChanged(...)
-
My Model implementation wraps a QMap, so each entry of the map is displayed as a row. If I add an entry into the QMap, do I first have to calculate the row on which the new entry will be displayed and call beginInsertRows(), or can I modify the QMap and then call begin/endInsertRows() after the fact?
-
The proper order is: call beginInsertRows, do the actual insert (modify your data), call endInsertRows. See also the first posting by Volker:
[quote]adding rows / columns must be sourounded by beginInsertRows(...) and endInsertRows(...) respectively beginInsertColumns(...) and endInsertColumns(...)[/quote] -
[quote author="Andre" date="1295035987"]The proper order is: call beginInsertRows, do the actual insert (modify your data), call endInsertRows. See also the first posting by Volker:
[quote]adding rows / columns must be sourounded by beginInsertRows(...) and endInsertRows(...) respectively beginInsertColumns(...) and endInsertColumns(...)[/quote][/quote]This is such an awkward interface. Also, it introduces a great deal of risk. What happens if one of the calls is made and not the other.
-
Then the view does not update :-)
You could also call beginXXX endXXX after changing the values, theoretically... but it could lead to unexpected behavior... If you only have one thread, it could perhaps work....
but calling beginXXX endXXX tells the view NOT to call other methods of the model in between.
It's a bit tricky but it's well described and there are examples that also describe it.
[quote author="dusktreader" date="1295033080"][quote author="Gerolf" date="1294993008"]
you could try columnAt / rowAt if you are using a QTableView.
also "indexAt()"
[/quote]That is all fine and good, but how do I find the positional coordinates of the view? Could I just call
@QModelIndex ul = tblView.indexAt( QPoint( 0, 0 ) );
// do some stuff
tblView.scrollTo( ul );@[/quote]you want the visible indizes, so try the visible rect of the scrollarea (look at QAbstractScrollArea, which is a base class of all views)
-
[quote author="dusktreader" date="1295037273"]This is such an awkward interface. Also, it introduces a great deal of risk. What happens if one of the calls is made and not the other.[/quote]
It is well described in the docs. Programming is always a great deal of risk. If you forget the second call your program will probably fail. It's not different to, say, allocating new objects on the heap with new; you eventually will have to clean up the mess with delete some time. If you forget this you will run into problems too. That's awkward too - oddly enough nobody complains about this.
Some background:
It would work as you described, if the model is only used for at most one view and it is manipulated only from the GUI thread. Otherwise it is crucial to inform the view that some change in the model's data will happen, so that the view(s) can stop manipulating the data itself to prevent them from changing the same data from different places. See it as a kind of mutex or semaphore. -
[quote author="Volker" date="1295045477"]That's awkward too - oddly enough nobody complains about this.[/quote]
Surely you aren't serious? Allocation problems are almost the entire motivation for the development of garbage-collectors in the newer dynamic languages. Mis-allocation presents massive risks and results in unimaginable quantities of wasted time in debugging and testing. Every programmer I have ever met complains about this.
My main concern with this interface is that it requires the Model to know where new data is going to be inserted. If the model is wrapping a complex data structure, it may be very costly to calculate the index location of the new data.
I realize that any Model/View framework is going to plagued with synchronization problems. I'm just frustrated by having to know precisely where the new data is going to be inserted.
-
[quote author="dusktreader" date="1295051767"][quote author="Volker" date="1295045477"]That's awkward too - oddly enough nobody complains about this.[/quote]
My main concern with this interface is that it requires the Model to know where new data is going to be inserted. If the model is wrapping a complex data structure, it may be very costly to calculate the index location of the new data.
I realize that any Model/View framework is going to plagued with synchronization problems. I'm just frustrated by having to know precisely where the new data is going to be inserted.[/quote]
If you don'T know where new data is going to, how do you know where to present it to the view? The view is not always asking for all data, it just queries the data of one cell... So you have to calculate the index and from the index to your structure anyways. I Implemented many models and I never had the problem of finding the position where some data will go to.
The other posibility would be to do the MFC way: The document just tells: changed. and it's the views part to calculate, what was changed, its different and worse....
-
[quote author="dusktreader" date="1295037273"]What happens if one of the calls is made and not the other.[/quote]
What happens, I can not tell you, but I know what happend: you made a programming mistake :-)
I am not saying it is ideal, but the problem is that you put the model in an inconsistent state if you change the data in the backend before telling that to the model (and connected views). That could lead to big problems. Perhaps it would be more convenient if it would (optionally?) work the other way around: you tell the model that you're going to insert rows, and when done, tell it where you did that. However, don't count on changes like that happening anymore.If you worry about forgetting the endInsertRows call, then I would suggest you create a RAII class to handle it for you. That way, you can be sure the corresponding method is always called. I do for other cases sometimes too (like for SQL transactions, and for painter save/restores). They are simple enough to write
About the multi-threaded argument made by Volker above: I don't think that this makes it any safer to do multi-threaded models at all. I think that is one of it's shortcommings: it is hard to make a good multi-threaded model. Could you (Volker) perhaps explain how this API helps in getting a nice multi-threaded model implementation?
-
It does not help in doing this, but it prevents the views from aquiring data from the model if the model accesses data that is stored in some backend class that is changed from another thread.
Think of some threads in the background working on the data, and from time to time changing the data. Thats the point where you have to tell the view: don't access or change the data in the range of a to b. If everything is inside a single thread and you emit no signals, it's nearly save to just call beginXXX() and endXXX() directly at the end, as no view can access the data at any time (it's only one thread).