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

Choose between model/view or other approach



  • Hi to all,

    I'm working on a custom widget for a project that should became a timeline editor, like the one used in the video/audio editing software.

    I'm relatively new to the Qt framework and facing some issue projecting and implementing the structure I have in mind with this framework.

    My basic idea is that I will have a main component that should work as a list of tracks or track groups (that should contains their relative tracks or other groups).
    Every track have his own data and have as child a list of clips.
    Every clips have his own data.

    I've a general idea of how to implement that structure visually, but I'm struggling projecting the class structure that should keep the data and communicate with the widgets.
    My first implementation was based on some simple class Track, TrackGroup and Clip, that's the simplest/lightest structure come to my mind, but with this structure I can't update the widget dynamically when a data is set.
    I've request here on the forum a way to implement something like the reactivity that some javascript framework like vuejs have and someone point me to the model/view structure in Qt (a sort of Observable Pattern), used for example for the QListView component, that can solve part of my problem: a view that react to the data change.
    So I've studied how to implement the model/view pattern in my structure finding many little doubt that it's stopping me from considering the model/view the right path from my project (at least the Qt implementation):

    • First of all, looking at the implementation like in the Simple Tree Model Example, I don't understand why I have to implement a Model object that doesn't contain the real data, and I should put the real data in an Item object that is referenced in the Model. Cannot I put the data directly in the Model? Doesn't this Model-Item approach consume more memory?
    • Second, In my case I have a sort of tree structure, at least for the Tracks and Track Groups structure, so probably the best idea is to implement the QAbstractItemModel interface. The doubt is because those type of objects are different: they have different data and different type of child, and should be rendered completely differently. Is the tree structure the right way?
    • Third, looking how many thing I have to implement with this pattern I've noticed that I should use many generic call/data type, like the data/setData function, based on column concept, and the QVariant, while I have many specific data and function that should do particular thing when the data is set.

    So my question is if the model/view pattern is the right choice for my project.
    Looking at the pros I have an event based system that update the view when I change the data and I have a system that manage the selection and the drag&drop ready to use (only to implement/adjust), the cons is that I should implement many thing in my widgets to make some of the function present in some other standard widget less generic and like I want (or completely rewrite it from a basic widget if it's less harder that completely change something like a QListView/QTreeView in something other).

    I hope someone could help me clarifying my ideas :)

    PS: If you can, please, argue why you think a solution is better than another.
    Thanks to all
    Cheers
    Mix



  • @mix359
    This is a big question! Certainly model-view may help you. It's just a generic way to approach this.

    For others to maybe comment further, you might like to clarify the following:

    • but with this structure I can't update the widget dynamically when a data is set.

    Why/what?

    • I don't understand why I have to implement a Model object that doesn't contain the real data, and I should put the real data in an Item object that is referenced in the Model. Cannot I put the data directly in the Model?

    I don't get this?

    • Where are you going to get all your track/group/clip data from? For example, are they coming out of a database?


  • @JonB Thanks for the reply

        For others to maybe comment further, you might like to clarify the following:
    
                but with this structure I can't update the widget dynamically when a data is set.
    

    Well, I can do that but not with a simple c++ object. I would probably have to use a simple QObject and implement some signal/event to emit when a change is done (or is going to append).
    As at first that sound better to me, because I will implement only the signal I really need and it will be cleaner, but to reach all the feature I have in mind, I would probably have to make many signals similar to the one used in the QAbstractItemModel (or some descendant implementation) so I wouldn't end reinventing the wheel.
    At the moment I don't have a full idea of how QAbstractItemModel work/is implemented so that's why I was asking here, otherwise I should probably read all the framework code to understand if is worth to use it or it's better to not use it. (The documentation is not enough to understand that)

        Why/what?
    
                I don't understand why I have to implement a Model object that doesn't contain the real data, and I should put the real data in an Item object that is referenced in the Model. Cannot I put the data directly in the Model?
    

    Looking at the Simple Tree Model Example I see that the real data is stored in a QList<QVariant> m_itemData inside the TreeItem object.
    I don't understand why this data and the parent/child relationship aren't stored directly inside the TreeModel class?

        Where are you going to get all your track/group/clip data from? For example, are they coming out of a database?
    

    I would probably load all the data from a file into the heap (as objects).

    Thanks again
    Cheers Mix



  • @mix359

    Looking at the Simple Tree Model Example I see that the real data is stored in a QList<QVariant> m_itemData inside the TreeItem object.
    I don't understand why this data and the parent/child relationship aren't stored directly inside the TreeModel class?

    The advantage of ​​this separatation is related with class cohesion. In this example, the TreeItem resposability is child, data and parent management. While the model is responsible for how to insert, edit or remove the data.
    However, the point of this modeling idea is that TreeItem has parent that is another TreeItem that can be list of datas and another parent...

    Third, looking how many thing I have to implement with this pattern I've noticed that I should use many generic call/data type, like the data/setData function, based on column concept, and the QVariant, while I have many specific data and function that should do particular thing when the data is set.

    In model design, you should consider how the date will be retrieved and manipulated. This partitioning makes it easier to locate the problem.



  • @KillerSmath thanks

    Can you please make me an example of why it's easier work in this way?
    What's the disavvantage of having the data and the method that use/manipule that inside the same object?

    Meanwhile I'm doing some test with the QAbstractItemModel and I've found another "disadvantage" of this method: the data is required using a row or a row + column system, but let say I want to have the color of a track, if I understand well I have to define that for example the column 2 is the color, the column 1 is the name, etc, so to have the color of a track I need to create an index that point to the row + column 2?
    Or I should ignore the column and use the Role to have the right information? In that case, how could I set that?
    I'm finding this way of working with the model more confusing than having a model with some getter/setter for his properties, that emit signal when a setter is called.

    Thanks
    Cheers Mix ;)



  • @mix359

    I'm finding this way of working with the model more confusing than having a model with some getter/setter for his properties, that emit signal when a setter is called.

    But you do have that? QAbstractItemModel::data(), QAbstractItemModel::setData(), and the QAbstractItemModel::dataChanged() signal, do each of these respectively. Meanwhile, you write your class(es) to inherit from that and expose stuff as dedicated getter/setters as you see fit.



  • @JonB thanks :)
    So you're suggesting to implement the setters and calling the QAbstractItemModel::dataChanged() signal? Is that enough?
    And for the QAbstractItemModel::data() and QAbstractItemModel::setData()? I have to implement that like a sort of noop? Or I have to implement that for compatibility, for example returning the track/clip name when data is called with DisplayRole?

    Thanks again
    Cheers Mix



  • @mix359
    I don't really understand your question (this is often the case for me!). Given that you have whatever data structures you want for your tracks etc., your derived class will implement QAbstractItemModel::data() to return whatever is appropriate out of your data model. And if you need setData() (only read-write models do) that will set whatever in your model, and raise dataChanged. In this derived class you can also write whatever methods you feel like to "wrap" certain data/setData() methods with dedicated get/setters, for your convenience.



  • @JonB Sorry, probably the question wasn't clear...

    Looking at the QAbstractItemModel system I see that al the data is requested and passed pointing at with an index and using a role. I would have many types of data and is not represented by the role system, so I would benefit from having a normal getter/setter (for example name/setName) system on the model instead of the data/setData methods.
    If I understand well, I simply need to emit the dataChanged signal when one of my setter is called, right?
    Or this way of using the model upset the idea of how the models work here in Qt?

    Thanks
    Cheers Mix


  • Lifetime Qt Champion

    @mix359
    Hi If you don't follow the Model View convention using roles and ModelIndex, none
    of the normal view would be able to use your model.
    The virtual function for the QAbstractItemModel defines the interfaces to the view so
    you must return data via QAbstractItemModel::data()
    However, its up to you how the actual data is stored behind the scene as long as
    you follow the interface.
    If you look at this sample
    https://doc.qt.io/qt-5/qtwidgets-itemviews-editabletreemodel-example.html
    it stores the data in
    QVector<QVariant> itemData;
    However, that could have been anything as the Views dont care, they just ask for data/setData as always.



  • @mix359
    Briefly: You are attempting to by-pass/replace the data/setData() methodology. No, you cannot emit dataChanged in such a case.

    What @mrjj is saying is 100% correct. If you wish to use model/view you must respect the data() way of working. If not, then either there is no point using model/view or things will actually go wrong. You may supply dedicated get/setters in your derived class which present the data in that way, but underneath still call data/setData() to do the work; you must not have them be "standalone" and not use data() at all.



  • Thanks for the answers, I'm taking the piece of the puzzle together :)
    So back to the original question: I'll probably doesn't need to use my model with other widget, I will create custom widgets that use my data.
    Does it worth to use the model/view system if I have to adapt it (creating many custom roles, etc) to my necessities?

    Cheers Mix



  • @mix359,
    I am curious. What data do you think would not be able to be represented efficiently by the roles approach of data?



  • @fcarney It's not a matter of efficiency, because many of the data are simple data that can be represented with a derivation of QVariant, so I can probably put all of this data in a QList o QMap.

    It's a matter of control of data and operation that I can do to it, for example parse an input that is given to me from the widget and save it in some other way i need (thing that can be normally done in a getter or setter function).
    With the model/view interface I will have to do the operation for all of the properties in the same data/setData function.

    The problem with the roles system is that I have many particular data like for an audio track if it's enabled (bool), if it's soloed (bool), his volume (float), a send volume (float), his height (int), his output channel, and so on... So I would have to create many custom roles to represent they.
    Also in some case that custom roles couldn't be enough: let's say I have to save the volume to a send channel, and the number of send channel can change globally. How could I use a role in that case that should also take some data with him (in this case the send channel number)?

    Generally speaking I find more cleaner call the name of a properties on an object instead of save the value with a numeric index on a QList and access it defining many custom roles, but maybe I'm missing something so that's why I'm asking here ;)

    Finally, talking of efficiency, correct me if I'm wrong, but using something like a QList or QMap for all the properties consume more memory then a simple object?

    Thanks again ;)
    Cheers Mix



  • @mix359 said in Choose between model/view or other approach:

    It's not a matter of efficiency, because many of the data are simple data that can be represented with a derivation of QVariant, so I can probably put all of this data in a QList o QMap.

    You can use any storage/class/structure you want. It could be a database backend or anything. Also, you are not limited to using only the data() function. You could use that for a standard roles for listing things, but also define more getters/setters for the model object or another object if wanted. Use the index returned from the standard interface in getter/setter calls on that object to do more complex stuff. Just make sure you couch changes in the model that could affect what is shown in the views inside the correct begin/end data changed calls.

    We use qml for a lot or our work and it is common for there to be stuff we do outside the model if it makes sense. Like you might want to return a custom object to the qml interface we may have more methods defined on the model itself. I don't know if your using qml, but it is nice for doing complex interactions. I am sure you could do the same if your using widgets.

    The model/view code in Qt is really good at displaying lists and trees. No reason to reinvent that. Then add complex functionality if needed, where needed.


Log in to reply