Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

Question about models with aggregated values (intertwined updates)



  • I'm pretty new to Qt and trying to figure out how should I build my models. Here's the situation (I have complete ownership over everything).

    I have a data class which is something like this:

    class Data
    {
    public:
    	Data();
    
    	void addValue(int newValue);
    	void removeValue(int indexToRemove);
    	// Other access functions
    
    private:
    	void recalculate();
    
    	QList<int> values;
    	int aggregatedValue;
    };
    

    The public functions add and remove values from the QList and call the private function, which recalculates the aggregated value (otherwise unaccessible).

    Now I have to build models to show this data.
    Question 1: is it a good idea to build a big tree model with one branch containing the list and the other containing the aggregated value? I tried a bit, but wasn't able to easily build it so that it would have fixed depth and fixed number of children in a certain branch. Within such a big model, I would be able to implement the entire reculcalation logic inside setData.

    Being unable to make a tree model work, I've built two distinct model, listModel and aggregatedModel, with the latter being a read-only model. The problem is, how do I force the update of widget using aggregatedModel when a new value is inserted into the list?

    I've come up with 2 solutions:

    • create a super model which is responsible for connecting listModel and aggregatedModel. That is, a class that intercepts the dataChanged() signals from the listModel, calls recalculate and then emits the dataChanged() signals from the aggregatedModel in order to force views to update.
    • make the Data class a QObject to be able to send a signal when the aggregated value changes (inside recalculate) and connect aggregatedModel to it.

    Question 2: what is the most common solution? I like the idea of having actual data class as pure c++ class (no overhead, much more portable through projects), but the latter solution seems more flexible and easy to organize, with the added benefit that if I use different models to display the same information, I can do it easily.

    Question 3: let's suppose I have a second data class, PooledData, which pools certain values from many Data objects. It contains pointers to the pooled data (actual data are not simple integer but more complex structures). On top of this I have to build a model which reacts to changes in the data pointed to. How can I do this?


  • Lifetime Qt Champion

    Hi,

    1. a tree model sounds good. Do you mean you want one column per value in your list or one row for each ?
      Did you check the Simple Tree Model example ?

    2. What do you mean by pure C++ ? Qt is written in C++. In any case, you should see the Qt models as wrapper on top of data structures not as a replacement.

    3. See second part of answer 2. Give your model an API to modify directly the underlying data structure and call the begin/endXXX methods corresponding to what you do or the dataChanged signal.



  • Thank you for your reply. Here's some more info:

    1. I already checked the simple tree model example and successfully implemented it somewhere else. I have trouble though to make a tree of a fixed size without using a tree data structure (the TreeItem class in the example). I haven't saved my previous attempts, but wrote something like this:
      int FixedTreeModel::rowCount(const QModelIndex & parent) const
      {
        // if we are at the top, fix the number of column to 2
        if (!parent.isValid())
            return 2;
            // if we are one level deep
            if (!parent->parent.isValid()) {
                // "left" branch: the list
                if (parent.row() == 0)
                    return dataPtr->listCount();
                // "right" branch: the aggregated value
                else if (parent.row() == 1)
                    return 1;
            }
            // no child for everyone else
            return 0;		
        }
    

    Is the reasoning applied in this code correct?

    1. By "pure C++" I mean that's not a QObject, so no signals and slots access. This means that whenever the underlying data is changed, its only contact with the world is through the qt model built on top of it. My models often have a pointer to actual data structures, which are "pure C++" class.

    2. (Also part of question 2) I still don't understand how I can implement it. In the above example - no tree model, instead listModel and aggregatedModel for each part of the data structure - if I add a new value through the listModel interface, the underlying data structure will be updated (one more value added to the QList), then the aggregated value will be recalculated. The problem is that nothing notifies aggregatedModel of this, so no view based upon it will be updated to show the recalculated aggregated value. This happens unless 1) I explicitly connect the dataChanged signal of listModel to aggregatedModel in order to force an update or 2) I make the data structure a QObject, enabling it to notify any attached model if something needs to update (this would have the benefit of having multiple model attached to the same data to be able to stay "synchronized") or 3) something else entirely (?).
      A data structure containing pointers to other data structures makes things even more complicated - data pointed to may change their value (or be added/removed) without the pointer knowing...


  • Lifetime Qt Champion

    1. what fixed size do you have in mind ?
    2. that's the usual way to do it. Your model has an API to update/modify your data structure and through this API you trigger the various signal to propagate the information to the views showing the data.
    3. sounds like your aggregated model is in fact a proxy model, isn't it ? If so it lives on top of your main model and then can benefit directly for the update signaling of its source model.


  • Sorry for the late reply.

    1. By fixed size I mean I want the model to have a tree structure like this:
    • Root Node (empty)
      • List Node (empty)
        • List Item #0
        • List Item #1
        • List Item #2
        • ...
      • Aggregated Node (empty)
        • Aggregated value
        • (possibly expand this if other aggregated values are needed)

    With the data structure being the Data class above. The point is that the data structure is not a tree (node) or something similar, while the model behaves like one. This means that the tree model should have a fixed depth and, depending on the parent node, a fixed amount of children or not (only one aggregated value, only two children for the root node...).
    I have trouble coding this as I'm not 100% sure how I should refer to the root index. Is the example in FixedTreeModel::rowCount() correct?

    1. I don't have much experience with Qt proxy models, but I don't think that would fit. If I understand your suggestion, you may want to remove the PooledData class and build the proxy model directly on top of the other model. However, I need PooledData as it doesn't only contains pointer to Data objects, but also other data which may be a direct input from the user or retrieved from files and other external sources.

  • Lifetime Qt Champion

    Just for the sake of building the idea:

    • List Node would only be a list showing something from your data class or should it already a tree ?
    • Do you have a data structure for these external data to be stored ?

    The basics of your data method looks good.


Log in to reply