Results 1 to 9 of 9

Thread: Tree model implementation for C++ data to QML

  1. #1
    Join Date
    Jan 2021
    Posts
    5
    Thanks
    3

    Default Tree model implementation for C++ data to QML

    I have been going through this example and was looking for another shove in the right direction if possible The example is o-so-close to being exactly what I need but the different derived types instead of all being an Item* is messing me up a bit.

    https://doc.qt.io/qt-5/qtwidgets-ite...l-example.html

    I have a datastructure on the C++ side that I will be modifying and updating (from the C++ side as well mostly). I plan on using this as the model for my views on the QML side. I don't necessarily need to use a listview or anything that will specifically need the data formatted in a certain way, but I do need access to all of this data on the QML side.

    Qt Code:
    1. class Person{
    2. QString name;
    3. QString age;
    4. }
    5.  
    6. class Group{
    7. QString nickname;
    8. QString location;
    9. QList<Person> people;
    10. }
    11.  
    12. class Classroom{
    13. QList<Group> groups;
    14. }
    To copy to clipboard, switch view to plain text mode 

    In use in the backend:

    Qt Code:
    1. QScopedPointer<Classroom> model(new Classroom);
    2. qmlRegisterSingletonInstance("Classroom", 1, 0, "Classroom", model.get());
    To copy to clipboard, switch view to plain text mode 

    1. So far, I have started down the route of making the Classroom derive from QAbstractItemModel (can this be a list model since there is only 1 column?)
    2. I have made group and person, QObjects, and then store pointers to them inside the relevant parent classes. Should the middle item, "Group" derive from a QAbstractItemModel as well or does the top level "Classroom" as a model cover this layer/level and the layer below..
    3. Should I use QVariantList instead and write my own notify signals, this doesn't seem right but I can wrap my head around it a bit better, so I am trying to go the right performant/model route
    4. I do not plan on writing to this model from the qml side, I might fire off a few signals that "impact" the model from the c++ side, which will then in turn cause a change in the view
    5. Should I skip the model, and just make them all qobjects with properties of all of the child items (including qlist<Qobject*>) to get what I want from the top level item, an instance of Classroom?
    6. Roles are really just to make navigation easier from the qml side, right?


    Sorry for all of the questions, I have just been running in circles a bit here and am looking for a bit of guidance.

    The below threads (along with many others) are getting me there but I wanted to take a stab at getting a push in the right direction for my own use case

    https://www.qtcentre.org/threads/706...tractListModel
    https://www.qtcentre.org/threads/397...el-for-dummies
    Last edited by xconverge; 23rd January 2021 at 22:43.

  2. #2
    Join Date
    Jan 2008
    Location
    Alameda, CA, USA
    Posts
    4,971
    Thanks
    277
    Thanked 808 Times in 797 Posts
    Qt products
    Qt5
    Platforms
    Windows Android

    Default Re: Tree model implementation for C++ data to QML

    I have found that it is best to -not- try to convert a hierarchical data structure directly into a hierarchical QAbstractItemModel (if I interpret your statement "making the Classroom derive from QAbstractItemModel" correctly).

    Instead derive a "ClassroomModel" (say) from QAbstractItemModel and store a pointer to your Classroom singleton in it as a data member. Then, you can devise a way to store something in each QModelIndex that uniquely identifies where that index is in the hierarchy. It could be an integer that represents where that node is in a traversal through the hierarchy (0 is the Classroom node, 1 is the first Group node, 2 is the first Person node in the first Group, 3 is the second Person node in that Group, etc.) It could be a void pointer to the Group or Person node. Anything that lets you map from a QModelIndex into your actual data structure.

    In any case, I think you will find the need for a back-pointer from Person to Group or "groupId" members in Person and Group so you can tell which Group a Person belongs to. You will need this in order to implement the model's parent() method.

    The point of roles is so that a view can interrogate the model about how to customize the display of each member of the model. The only one you truly need to implement is the DisplayRole, since that provides the string that is used to display that item in the view. But there are many other roles which can be used to customize the appearance and which the view will ask for as it draws each item. You could have icons that are different for Classroom, Group, and Person nodes, different text or background colors, something other than the default left alignments, etc.

    Identity proxies are an interesting way to display the same base model in different ways in different views by using a different identity model for each view. It is in the identity model where the "decorations" get added (for icon, color, alignment, etc. roles) and the base model simply satisfies the DisplayRole.
    <=== The Great Pumpkin says ===>
    Please use CODE tags when posting source code so it is more readable. Click "Go Advanced" and then the "#" icon to insert the tags. Paste your code between them.

  3. The following user says thank you to d_stranz for this useful post:

    xconverge (24th January 2021)

  4. #3
    Join Date
    Jan 2021
    Posts
    5
    Thanks
    3

    Default Re: Tree model implementation for C++ data to QML

    Interesting, that makes sense to me along with some of the advice you gave here! https://www.qtcentre.org/threads/706...tractListModel

    Perhaps me trying to combine them into my existing data structure classes is contributing to my confusion and when I separate it out, things will start to make some more sense. I was putting them into my existing classes just for simplicity during the prototyping phase, but perhaps it is throwing me off of what I actually need to do to implement my model indices.

    Basically you are suggesting just create somewhat of a linked list implementation of my data structure, and then use a unique identifier for every single node, that then each map to a unique modelindex within ClassroomModel and thus also gets exposed to my QML view.

    I continue to wonder if this is really the best way to accomplish what seems like it should have a simpler solution (just properties for each member at each level since they are all qobjects?), but I see that sentiment in quite a few of the other similar threads to mine Your suggestion is starting to circle in on another idea I was starting to have which was to just have a whole pile of pointers that go into a hash table with some way of accessing them, and the way you are suggesting seems similar (using modelindex and the predefined model interface to solve the access problem)

  5. #4
    Join Date
    Jan 2008
    Location
    Alameda, CA, USA
    Posts
    4,971
    Thanks
    277
    Thanked 808 Times in 797 Posts
    Qt products
    Qt5
    Platforms
    Windows Android

    Default Re: Tree model implementation for C++ data to QML

    I take the name literally: QAbstractItemModel: it is an abstraction of some underlying data structure that acts as a well-defined interface between that data structure and a visualization of that data structure in a user interface. The view interacts with the model, the model interacts with the data structure. There is no need to rewrite the view when the data structure changes because the model-view interface doesn't change.

    I've used hash tables / maps, linked lists, vectors, a Tree class where the actual data for each node is stored in a void pointer data member and all the tree structure does is provide the parent-child links for navigation. The Tree class might work well for your problem, since all you have is a simple hierarchy.

    Each Tree node could contain a "node type" enum (ClassroomType, GroupType, PersonType) which would tell you how to cast the void pointer to an actual class instance pointer. When your underlying data changes, you simply destroy the whole tree and rebuild it (bracketed by calls to beginResetModel() and endResetModel()). The pointer to the Tree node is stored in the QModelIndex's internal data pointer.

    Qt Code:
    1. struct Tree
    2. {
    3. Tree * parent = nullptr;
    4. QList< Tree * > children;
    5. int nodeType = -1;
    6. void * nodeData = nullptr;
    7.  
    8. ~Tree()
    9. {
    10. for ( child : children)
    11. delete child;
    12. }
    13. };
    14.  
    15. QModelIndex MyModel::index( int row, int column, const QModelIndex & parent ) const
    16. {
    17. QModelIndex newIndex;
    18. if ( parent.isValid() )
    19. {
    20. if ( row >= 0 && row < rowCount( parent ) && column >= 0 && column < columnCount( parent ) )
    21. {
    22. Tree * parentNode = static_cast< Tree * >( parent.internalPointer() );
    23. Tree * childNode = parentNode->children[ row ];
    24. newIndex = createIndex( row, column, (void *)childNode );
    25. }
    26. }
    27. return newIndex;
    28. }
    29.  
    30. QVariant MyModel::data( const QModelIndex & index, int role ) const
    31. {
    32. QVariant returnVal;
    33. if ( index.isValid() && role == Qt::DisplayRole )
    34. {
    35. Tree * node = static_cast< Tree * >( index.internalPointer() );
    36. switch( node->nodeType )
    37. {
    38. case ClassroomType:
    39. returnVal = "Classroom";
    40. break;
    41.  
    42. case GroupType:
    43. {
    44. Group * group = static_cast< Group * >(node->nodeData);
    45. returnVal = group->name;
    46. }
    47. break;
    48.  
    49. // etc.
    50. }
    51. }
    52. return returnVal;
    53. }
    To copy to clipboard, switch view to plain text mode 

    If the underlying data structure has multiple "columns", then the data() method will have to map from a column number to a field in the data structure; for example column 0 of a Group node would be the group name, column 1 the location. You just need to expand each clause of the switch statement to retrieve the column data.
    Last edited by d_stranz; 24th January 2021 at 00:55. Reason: updated contents
    <=== The Great Pumpkin says ===>
    Please use CODE tags when posting source code so it is more readable. Click "Go Advanced" and then the "#" icon to insert the tags. Paste your code between them.

  6. The following user says thank you to d_stranz for this useful post:

    xconverge (24th January 2021)

  7. #5
    Join Date
    Jan 2021
    Posts
    5
    Thanks
    3

    Default Re: Tree model implementation for C++ data to QML

    I hope that I don't end up hitting a wall with the need to beginResetModel/endResetModel to update the entire model and thus probably the entire view if just a single element in a child of a child is changed, I don't think this will be an issue for my application (5-10 persons per 1-5 groups, per 1 classroom), but it is worth thinking about. Just because I am new to QML (not new to Qt necessarily), you allude to the shortcut/less maintainable version that piqued my interest, that does not adhere/implement the abstract interface. Would that be a QObject with properties and invokable functions? If I were to go that route, I would get a change notification per property potentially, and be more performant than the original suggestion, wouldn't I? The downside as you mentioned is maintainability of the interface between the model and the view.

    The view is basically going to be a classroom view, with a tabview of groups, and in each tab a rowlayout of persons

    Thanks for your replies, I really appreciate it!

  8. #6
    Join Date
    Jan 2008
    Location
    Alameda, CA, USA
    Posts
    4,971
    Thanks
    277
    Thanked 808 Times in 797 Posts
    Qt products
    Qt5
    Platforms
    Windows Android

    Default Re: Tree model implementation for C++ data to QML

    I don't think this will be an issue for my application (5-10 persons per 1-5 groups, per 1 classroom)
    No. I use a similar structure for a tree with one root element, 5 top level elements under that, and 10 - 15 children under each of those. Rebuilding the tree and re-displaying it when something changes is instantaneous.

    I don't see any advantage in using QObject as a base class for anything in the data structure; if you did, you'd have to change it from QList< whatever > to QList< whatever * > since QObject doesn't allow copy or assignment semantics.

    I am a big believer in clean data structures that are not encumbered by a particular framework (like Qt), so I tend to lean towards straight C++ / STL to represent them. If I need to have an editable data structure, then I will devise an editor class that -is- Qt-based so that I can have that class manage the changes to the data structure and send signals to the rest of the UI to make updates.

    I help maintain a large library of data and algorithm classes that is entirely written in vanilla C++ / STL. It is used in both Qt and MFC applications on Windows, as well as in Qt applications on linux, OS/X, and android platforms. It's probably close to a half-million lines of code. So keeping things clean and portable is a big part of what I've learned to do.
    <=== The Great Pumpkin says ===>
    Please use CODE tags when posting source code so it is more readable. Click "Go Advanced" and then the "#" icon to insert the tags. Paste your code between them.

  9. The following user says thank you to d_stranz for this useful post:

    xconverge (24th January 2021)

  10. #7
    Join Date
    Jan 2021
    Posts
    5
    Thanks
    3

    Default Re: Tree model implementation for C++ data to QML

    Quote Originally Posted by d_stranz View Post
    No. I use a similar structure for a tree with one root element, 5 top level elements under that, and 10 - 15 children under each of those. Rebuilding the tree and re-displaying it when something changes is instantaneous.

    I don't see any advantage in using QObject as a base class for anything in the data structure; if you did, you'd have to change it from QList< whatever > to QList< whatever * > since QObject doesn't allow copy or assignment semantics.

    I am a big believer in clean data structures that are not encumbered by a particular framework (like Qt), so I tend to lean towards straight C++ / STL to represent them. If I need to have an editable data structure, then I will devise an editor class that -is- Qt-based so that I can have that class manage the changes to the data structure and send signals to the rest of the UI to make updates.

    I help maintain a large library of data and algorithm classes that is entirely written in vanilla C++ / STL. It is used in both Qt and MFC applications on Windows, as well as in Qt applications on linux, OS/X, and android platforms. It's probably close to a half-million lines of code. So keeping things clean and portable is a big part of what I've learned to do.
    That is great empirical info on the performance front, thanks

    I planned on using vanilla/stl for the data structures on the backend elsewhere to avoid pulling in the qt dependencies and was going to write a translation layer into the qobject versions, so removing that extra level of abstraction for no reason is going to be helpful and makes a lot of sense!

    If set up this way, what would the pseudocode of the qml look like to access the first person in the first group of the classroom look like from my ClassroomModel singleton?

    I will take a crack at it with this new mindset/plan and report back, thanks a bunch!

  11. #8
    Join Date
    Jan 2008
    Location
    Alameda, CA, USA
    Posts
    4,971
    Thanks
    277
    Thanked 808 Times in 797 Posts
    Qt products
    Qt5
    Platforms
    Windows Android

    Default Re: Tree model implementation for C++ data to QML

    what would the pseudocode of the qml look like
    Well, there's where you've reached the limit of my knowledge. I've never written any serious code using the QML / QtQuick framework. Everything I do is QWidget-based desktop apps.

    Maybe someone else with more experience can jump in.
    <=== The Great Pumpkin says ===>
    Please use CODE tags when posting source code so it is more readable. Click "Go Advanced" and then the "#" icon to insert the tags. Paste your code between them.

  12. #9
    Join Date
    Jan 2021
    Posts
    5
    Thanks
    3

    Default Re: Tree model implementation for C++ data to QML

    So I was able to get a prototype working that looks something like this (warning: pretty far from the implementation idea discussed so far). This prototype captures the simplicity of how I "thought" things should be able to work, so at least now I don't feel crazy. I by no means think this is the right or best way though, but that is mentioned at the bottom of this post

    Qt Code:
    1. class Person : public QObject {
    2. Q_OBJECT
    3. Q_PROPERTY(QString name MEMBER m_name)
    4. Q_PROPERTY(int age MEMBER m_age)
    5. public:
    6. explicit Person(Group* parent = nullptr);
    7. explicit Person(const QString& name, int age, Group* parent = nullptr);
    8.  
    9. private:
    10. QString m_name = "";
    11. int m_age;
    12. };
    13.  
    14. class Group : public QObject {
    15. Q_OBJECT
    16. Q_PROPERTY(QString nickname MEMBER m_nickname)
    17. public:
    18. explicit Group(Classroom* parent = nullptr);
    19. explicit Group(QString nickname, Classroom* parent = nullptr);
    20. ~Group();
    21.  
    22. void appendPerson(Person* person);
    23. Q_INVOKABLE QObject* person(int index);
    24. int personCount() const;
    25. signals:
    26. void personCountChanged();
    27.  
    28. private:
    29. QString m_nickname;
    30. QList<Person*> m_people;
    31. };
    32.  
    33. class Classroom: public QObject {
    34. Q_OBJECT
    35. Q_PROPERTY(int groupCount READ groupCount NOTIFY groupCountChanged)
    36. public:
    37. Classroom(QObject* parent = nullptr);
    38. ~Classroom();
    39.  
    40. void appendGroup(Group* group);
    41.  
    42. Q_INVOKABLE QObject* getGroup(int index) const;
    43. int groupCount() const;
    44. signals:
    45. void groupCountChanged();
    46.  
    47. private:
    48. // Temp debug code to populate the model
    49. void populateModel();
    50.  
    51. QList<Group*> m_groups;
    52. };
    To copy to clipboard, switch view to plain text mode 

    and then in QML I am able to access it with stuff like this:

    Qt Code:
    1. console.log(ClassroomModel.getGroup(groupNum).person(1).name);
    To copy to clipboard, switch view to plain text mode 

    or with a property

    Qt Code:
    1. property int groupCount: ClassroomModel.groupCount
    To copy to clipboard, switch view to plain text mode 

    I think I understand the tradeoffs between using this vs subclassing a qabstractmodel, but wanted to put my thoughts on these explicitly incase there is something I am missing

    1) My datastructures are QObjects, not raw clean c++/stl, so a translation layer will be needed if I want to use raw stl structures on the backend to populate the model
    2) I wrote a custom interface, and my view code will need to be updated more often than if I were to have used the interface from qabstractmodel
    3) I can put in notify signals wherever I want really at any layer
    4) Performance as you said doesn't seem to be bad to do a complete destroy/recreate of my view with the number of items I am talking, but there is room here to optimize using some smarter notify/change signals
    5) Not compatible with tableview/etc as is, but can be used for Repeater counts and anything else where an exposed property would suffice
    6) I can even use this https://doc.qt.io/qt-5/qtqml-cppinte...ect-list-types to simplify the API even more

    This looks like it is going to work for my application, but I would love to hear about any counterpoints (either the same as what I identified above, or different/new)

    I was really struggling with modelindex and how it could work for me, but that could always be a refactor/change I tackle in the future.
    Last edited by xconverge; 24th January 2021 at 23:29.

Similar Threads

  1. Replies: 3
    Last Post: 17th January 2016, 18:06
  2. Replies: 3
    Last Post: 16th November 2015, 21:22
  3. How to map tree model data to list view
    By msopanen in forum Qt Programming
    Replies: 0
    Last Post: 10th November 2009, 19:56
  4. Replies: 1
    Last Post: 7th July 2009, 07:13
  5. data, model and tree view
    By larry104 in forum Qt Programming
    Replies: 17
    Last Post: 3rd July 2006, 14:43

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  
Digia, Qt and their respective logos are trademarks of Digia Plc in Finland and/or other countries worldwide.