QTableView: updating the model and visible area
-
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).
-
I don't think the suggestion that you can safely call beginXXX() and endXXX() directly at the end of your operation is good advice. You never know what happens in other parts of the code. If somebody calls processEvents(), you may already be screwed.
I understand that the mechanism could help, but I think you need quite a bit more to make it work properly. For instance, I think that you would need the beginXXX() to be a synchronization point: don't start to change the data untill you are sure that the GUI thread has actually called beginXXX(), otherwise you may still have data access while changing that data from another thread.
-
ThatÄ's why I said it's nearly safe not it's safe. If there are no asynchronous things inside your program, I mean really no, then it could work, I already saw code where it worked. But then you MUST ensure, that this never changes. I did not say, do it that way. I will never say that it's a good solution to do it in the end, do beginXXX() at the beginning and endXXX() at the end.
-
[quote author="dusktreader" date="1295051767"][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.
[/quote][/quote]Of course I am serious. I know not any C++ programmer who complains about this. And please notice I am talking of C++ programmers. I do not care about Java or any other GC enabled programming language. These do have other drawbacks. If you use C++ you accept its concepts and usage. If you feel too uncomfortable you choose another tool.
[quote author="dusktreader" date="1295051767"]
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]
So you're lost and you'd better choose a programming language or environment or GUI toolkit that is not using model/view for the data display and that does not support multithreading. These environments do not have a need to care about the awkward stuff you're complaining about.
What are you moaning about calculating the location of your data? You must eventually decide where in the view it has to go. The view does not have a crystal ball that tells it that some magic woodaboo in the model has happend and that it has to insert some rows in the table but it's unkown which rows and where to find them... Your model has to tell the view! And you have to in any other toolkit too. If it is really the beginInsertXXX() and endInsertXXX() that makes you feel so uncomfortable then you always could consider using another framework.
-
[quote author="Volker" date="1295218048"]
I am angry and condescending. I can't contribute anything useful to the conversation because I am too busy telling other people that they don't know what they are talking about
[/quote]Volker, thank you for adding some comic relief to my thread.
Gerolf and Andre, thank you for your help and input. I have successfully implemented a solution for my model/view infrastructure. I finished the primary development of my solution today. It comprises a data model bundled with 3 views in single widget that inherits from QFrame. Two of the views provides frozen headers for the columns and rows of arbitrary size. The frozen headers can be toggled on and off. The solution provides an API for easily wrapping a client's custom data structure in a table view format. The client only has to implement a few API functions for data access and view organization. The client doesn't have to know much about Qt's model/view framework to use this solution.
I realized that the cost of calculating locations for row insertions and deletions is not significant as such calculations have to be done repeatedly in the data(), setData(), and flags() functions anyway. Using the beginXXX() and endXXX() paradigm still seems unsatisfactory to me, but I have figured out a solution that works well.
If it is wanted, I may post my solution with a working example in a few days