Results 1 to 20 of 54

Thread: Integration: Nested Tree Parent Child C++ Classes with Models/Delegates for QML

Hybrid View

Previous Post Previous Post   Next Post Next Post
  1. #1
    Join Date
    Dec 2016
    Posts
    46
    Thanks
    20
    Qt products
    Qt5
    Platforms
    Windows

    Smile Integration: Nested Tree Parent Child C++ Classes with Models/Delegates for QML

    Hi!

    I have been using Qt the last couple of months for a class project and I am really loving it especially the QML part of it. It really is exactly what I would like to work with. However, I have gotten stuck at a certain obsticle the last couple of weeks and I would really appreciate some help with it.

    The application that I am building is basically an interface to manage, sort and filter through Pokémon cards in the users collection. The user creates a new Pokémon cards album and populates it with Pokémon Cards.

    Here is how the UI looks like:


    For example:
    The user creates two albums, one called "Favorites" and another called "Wishlist". The user can then add cards to those Albums. Once that is done, the user will be able to view the available albums in a tab or a page and clicking on an album will open up a view that display the cards in that album.

    I have found different ways to go about this implementation. One of them uses a TreeItem class and TreeModel class to create this nested QList of objects. The problem is that I will need to have different classes with different properties and children depending on where they are in the Tree. Here is a diagram I made to show what I mean:


    Since the Card objects won't exist outside of Albums, I have decided to pass them to the Albums directly rather than using pointers. However, this means that appending a card to an Album will demand a copy constructor if I use the following method:
    Qt Code:
    1. avoid Album::addCard(const Card &scarf)
    2. {
    3. BeginInsertRows();
    4. m_cards << card;
    5. EndInsertRows();
    6. }
    To copy to clipboard, switch view to plain text mode 

    The problem here is that I keep running into problems with the creating the Copy constructor and any methods that I need to add to the class to make the constructor work.
    So here is how the approach that I took looks like:

    main.cpp:
    I included the three header files for the classes App, Album and Card and registered them using qmlregistertype. I also created an instance of the App class and set it as root context property to use the models later on.

    App:
    This class basically fulfills the uppermost parent in the tree. It has no properties such as name and so on. Just the minimal tree root data methods to add and remove Albums to and from it on run time in it's QList<Album> m_album private member. It also has a NameRole to get the name of the Albums it contains.

    Album:
    It is added to an App object instance. It points to it's parent through a private App* m_parentApp variable. It has a QString m_name and it can add cards to it's QList<Card>m_cards private list. It also has several Roles used to retrieve information about cards in it using the data method.

    Card:
    It is added to Albums, has no list of children. Points to it's parent Album. Has many variables such as Name, ID, ImageURL and so on.

    All the classes inherit QAbstractListModel publicly. Not sure if they really should but that's what I have found people recommending on threads.

    The problem with creating the copy constructor (Album(const Album &source) is that when I am adding an Album to an App (m_albums << album) is this:
    I am not really sure how to go about iteratating through the children appended to the object being copied.

    I created a for() loop that ends at m_cards.count(). Then I noticed that I needed a way to access the card data in the Album being copied for each iteration but I have found no way to make it work. Using the source.data(index, role) expects a QmlIndex rather than an int. I tried dynamic_casting, not sure if I should but that didn't work either so I thought that I should create a new method that retrieves the Card from the Album by value or reference so that my copy constructor works but haven't had any success there.
    I tried:
    Qt Code:
    1. Card getCard(int row)
    2. {
    3. Return m_cards.at(row);
    To copy to clipboard, switch view to plain text mode 
    }
    But that didn't work, I tried using Card* and Card& as well as m_cards[index.row()] but that didn't work.
    The only reason I am doing this is so that my copy constructor which is automatically called when appending an album to and App or a Card to an Album can do something like this:
    Qt Code:
    1. For all the children in the objects QList:
    2. This->name = source.m_cards.name();
    To copy to clipboard, switch view to plain text mode 

    I would appreciate some help with this. Feel free to give any feedback about the approach or suggest a different too if you think that something else would be better.

    Thanks.

  2. #2
    Join Date
    Jan 2006
    Location
    Graz, Austria
    Posts
    8,416
    Thanks
    37
    Thanked 1,544 Times in 1,494 Posts
    Qt products
    Qt3 Qt4 Qt5
    Platforms
    Unix/X11 Windows

    Default Re: Integration: Nested Tree Parent Child C++ Classes with Models/Delegates for QML

    There are multiple options for this.

    1) You could have an internal data structure and have a model that presents the album "view" on the data and models that present the card "view" of the data.
    Depending on whether you can show multiple albums at the same time, you might need more than one instance of the latter.

    2) Another option is to make some of the data classes themselves into models themselves, e.g. the "Album" class and the "App" class.

    3) The "App" class could also be just a normal QObject derived class and have the "Album" models as a list property.

    Option (1) requires separate classes for data and models, but also allows the data classes to be copyable.
    I.e. more classes and thus more core, but treating data separately from the QML adapter to the data.

    Options (2) and (3) need at least the "Album" class as a QAbstractListModel subclass, so a QList<Album*> as the container in "App".

    When you write "all classes inherit QAbstractListModel" it looks like your chosen approach is (2), but do you also mean "Card" is a list model?

    Cheers,
    _

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

    Nizars (30th December 2016)

  4. #3
    Join Date
    Dec 2016
    Posts
    46
    Thanks
    20
    Qt products
    Qt5
    Platforms
    Windows

    Default Re: Integration: Nested Tree Parent Child C++ Classes with Models/Delegates for QML

    Hi Anda,

    Thanks for reading the problem I am having and I appreciate the overview of the different options I have.

    My goal is to develop the application further and add the ability to search through the albums, sort by a specific variable and possibly even drag and drop a card to a different Album.

    The more important function however is the ability to save the "session" to a local file and load from it on start up. It could be a parent class to App called AppManager that has the methods ExportLibrary and ImportLibrary to and from a local file. I have not decided on a format for the data yet, I am really open for anything.

    I am mentioning this to help narrow down the more appropriate path to take for the data structure. Once I have gotten the current obsticle fixed and learned how to implement it, I will change the root context property from App to AppManager and have it provide the App instance to the application.

    I assume that this narrows down my options to option 1 for the most suitable data structure which is having the data classes separate from the from the models.

    The Card class shouldn't inherit publicly from QAbstractListModel. That was a mistake from my part. It however should be registered and made available to the QML to help instanciate Card objects for the Albums.

    What do you think, does it sound right?

    Cheers.

  5. #4
    Join Date
    Jan 2006
    Location
    Graz, Austria
    Posts
    8,416
    Thanks
    37
    Thanked 1,544 Times in 1,494 Posts
    Qt products
    Qt3 Qt4 Qt5
    Platforms
    Unix/X11 Windows

    Default Re: Integration: Nested Tree Parent Child C++ Classes with Models/Delegates for QML

    I don't see anything that would narrow your option down to just (1), however, it is often benefitial to not design data structures and data handling with just the UI's needs in mind.

    Lets look at this with a simplied example, an address book:

    1) for that I would have
    * a Contact class that can hold all data for a single address book entry
    * an AddressBook class that has a list or maybe even a hash of Contact objects and which would have methods to add/remove/change/get/list contacts

    2a) a list model that gets a pointer to an AddressBook and provides the list of contacts, contact details as roles

    2b) an Import/Export class or classes that also get an AddressBook pointer or reference and implement data I/O

    3) optionally, a Contact adapter/editor class that serves as an QML adapter for modifying a single contact. It would get a Contact object and provide its data as separate Q_PROPERTY fields, it can provide methods to check for validity of the data, etc.

    (2a) and (2b) are different usages of the data but can still work on the same data. the base classes only need to ensure they can provide all the functionality required for these two use cases.

    For your application I wonder if you really want cards nested inside albums.
    I.e. is an Album not just a "view" of cards, a selection from the whole?
    For example could a user have "All my cards", "All time favorites", "Favorite Water Pokemons", with the same "Vaporeon" card being in all three?

    Cheers,
    _

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

    Nizars (30th December 2016)

  7. #5
    Join Date
    Dec 2016
    Posts
    46
    Thanks
    20
    Qt products
    Qt5
    Platforms
    Windows

    Default Re: Integration: Nested Tree Parent Child C++ Classes with Models/Delegates for QML

    You make a good point!
    I didn't really think of that. The thing is, for now at least and to narrow down my project, I have decided to work only with one set of Pokemon cards. Those cards exist in <cardID>.json files in a resource. Those JSON documents contain all the variable values for the cards such name, hp, attacks and so on. The card images exist in another folder in the resources as <cardID>.jpg. I have done this so that I have something to work with and not risk spending a month or two working with network, ssl, cache, json classes and risk not having anything to present as the deadline aproaches. I do like the project I am working with and I am planning to add those functionalities in the future, maybe even a function that adds cards by scanning them with a phone camera. The JSON documents are retreived from: pokemontcg.io but that is something that I won't touch until the main application fully works.

    My line of thinking has been that when the user wants to add a card to an album. He or she will type the name or the id of the card and the program creates an instance of that card in the album. If he doesn't add it to the album and decided just to see it, it will be deleted once he exits the view. I also thought of having an instance of all the cards pre-loaded in a hidden default album that the App uses to display the cards before the user adds them to his or her Album.

    You make a good point about a Card being available in more than one album. I guess there should be a default "All my cards" Album. I guess cards in that Album determines what cards are made available to the other albums. I assume that this would be a bit problematic if the user wants to create a "My wishlist" or "To buy" Album. There is also the problem of duplicate cards.

    I will try to rethink the relationships between the Albums and between the Cards and Albums. I think that having an Album as an isolated container of cards from other Cards in other Albums might make the implementation a bit easier assuming that the user will manually add Cards to Albums and not expect them to be shared among the Albums. I can however create a "view" that shows all the cards in all the albums or multiple Albums.

  8. #6
    Join Date
    Jan 2006
    Location
    Graz, Austria
    Posts
    8,416
    Thanks
    37
    Thanked 1,544 Times in 1,494 Posts
    Qt products
    Qt3 Qt4 Qt5
    Platforms
    Unix/X11 Windows

    Default Re: Integration: Nested Tree Parent Child C++ Classes with Models/Delegates for QML

    Just sketching some ideas here:

    Qt Code:
    1. class Card
    2. {
    3. QString id;
    4. QString name;
    5. // ....
    6. };
    7.  
    8. class CardManager : public QObject
    9. {
    10. Q_OBJECT
    11.  
    12. public:
    13. // returns an invalid card if ID is not found, e.g. Card object with empty "id"
    14. Card addCardById(const QString &id);
    15. Card addCardByName(const QString &name);
    16.  
    17. Card card(const QString &id) const;
    18. QList<Card> cards() const;
    19.  
    20. int count() const;
    21.  
    22. signals:
    23. void cardAdded(const QString &id);
    24. void cardRemoved(const QString &id);
    25.  
    26. private:
    27. QList<Card> m_cards;
    28. };
    To copy to clipboard, switch view to plain text mode 

    Qt Code:
    1. class Album
    2. {
    3. QString id;
    4. QString name;
    5. QStringList cardIDs;
    6. };
    7.  
    8. class AlbumManager : public QObject
    9. {
    10. Q_OBJECT
    11. public:
    12. Album createAlbum(const QString &name);
    13.  
    14. Album album(const QString &id) const;
    15.  
    16. QList<Album> albums() const;
    17.  
    18. int count() const;
    19.  
    20. void addCard(const QString &albumId, const QString &cardId);
    21. void removeCard(const QString &albumId, const QString &cardId);
    22.  
    23. signals:
    24. void albumAdded(const QString &id);
    25. void albumRemoved(const QString &id);
    26.  
    27. void cardAdded(const QString &albumId, const QStringList &cardIDs);
    28. void cardRemoved(const QString &albumId, const QStringList &cardIDs);
    29. };
    To copy to clipboard, switch view to plain text mode 

    Qt Code:
    1. class AbstractCardModel : public QAbstractListModel
    2. {
    3. Q_OBJECT
    4. public:
    5. enum Roles {
    6. IdRole = Qt::UserRole + 1,
    7. NameRole,
    8. // ....
    9. }
    10.  
    11. explicit AbstractCardModel(CardManager *cardManager, QObject *parent = 0);
    12.  
    13. // this uses cardForIndex to get the card, then uses role to return the actual data
    14. QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
    15.  
    16. protected:
    17. QHash<int, QByteArray> roleNames() const;
    18.  
    19. virtual Card cardForIndex(const QModelIndex &index) const = 0;
    20.  
    21. protected:
    22. CardManager *m_cardManager;
    23. };
    To copy to clipboard, switch view to plain text mode 
    Qt Code:
    1. // for showing the cards of a single album
    2. class AlbumCardsModel : public AbstractCardsModel
    3. {
    4. Q_OBJECT
    5. public:
    6. AlbumCardsModel(AlbumManager *albumManager, CardManager *cardManager, QObject *parent = 0);
    7.  
    8. void showAlbum(const QString &albumId);
    9.  
    10. int rowCount(const QModelIndex &parent) const;
    11.  
    12. protected:
    13. Card cardForIndex(const QModelIndex &index) const;
    14.  
    15. private slots:
    16. // gets connected to m_albumManager's albumRemoved() signal
    17. void onAlbumRemoved(const QString &albumId);
    18.  
    19. void onCardAdded(const QString &albumId, const QString cardId);
    20. void onCardRemoved(const QString &albumId, const QString cardId);
    21.  
    22. private:
    23. AlbumManager *m_albumManager;
    24. Album m_album;
    25. };
    26.  
    27. void AlbumCardsModel::showAlbum(const QString &albumId)
    28. {
    29. beginResetModel();
    30.  
    31. m_album = m_albumManager->album(albumId);
    32.  
    33. endResetModel();
    34. }
    35.  
    36. int AlbumCardsModel::rowCount(const QModelIndex&) const
    37. {
    38. return m_album.cardIDs.count();
    39. }
    40.  
    41. Card AlbumCardsModel::cardForIndex(const QModelIndex &index) const
    42. {
    43. return m_cardManager->card(m_album.cardIDs.at(index.row());
    44. }
    45.  
    46. void AlbumCardsModel::onAlbumRemoved(const QString &albumId)
    47. {
    48. if (albumId != m_album.id) return;
    49.  
    50. beginResetModel();
    51. m_album = Album();
    52. endResetModel();
    53. }
    54.  
    55. void AlbumCardsModel::onCardAdded(const QString &albumId, const QString &cardId)
    56. {
    57. if (albumId != m_album.id) return;
    58.  
    59. const Album updatedAlbum = m_albumManager->album(albumId);
    60. const int row = updatedAlbum.cardIDs.indexOf(cardId);
    61.  
    62. beginInsertRows(QModelIndex(), row, row);
    63.  
    64. m_album = updatedAlbum;
    65.  
    66. endInsertRows();
    67. }
    68.  
    69. void AlbumCardsModel::onCardRemoved(const QString &albumId, const QString &cardId)
    70. {
    71. if (albumId != m_album.id) return;
    72.  
    73. const Album updatedAlbum = m_albumManager->album(albumId);
    74. const int row = m_album.cardIDs.indexOf(cardId);
    75.  
    76. beginRemoveRows(QModelIndex(), row, row);
    77.  
    78. m_album = updatedAlbum;
    79.  
    80. endRemoveRows();
    81. }
    To copy to clipboard, switch view to plain text mode 

    Cheers,
    _

  9. The following 2 users say thank you to anda_skoa for this useful post:

    d_stranz (30th December 2016), Nizars (30th December 2016)

  10. #7
    Join Date
    Dec 2016
    Posts
    46
    Thanks
    20
    Qt products
    Qt5
    Platforms
    Windows

    Default Re: Integration: Nested Tree Parent Child C++ Classes with Models/Delegates for QML

    I really appreciate your time and all the help you are offering me on this!

    I have a quick question for you while I finish making changes to my classes.

    What is the role of a <class>Manager class? I am just trying to understand how it fits in.
    Say I want to to add copy constractors and assignment opperators for the App, Album and Card classes, where would those be implemented? in their respective classes? or in the their accompanying <class>Manager classes?
    What about the Setter and Getter class methods such as "QString getCardName() const;"? Same with the default and overloaded constructors? should I even have a default constructor in the original class?

    I have noticed that you combine each class and it's Manager class in the same document. Is there a reason or a benifit behind this practise? or is this simple down to preference and time saving?
    I have created three different classes for each of the "core" classes. An Album class, an AlbumsManager class and an AlbumModel class. I am totally open to changing it to the template you wrote if it makes a difference.

  11. #8
    Join Date
    Jan 2008
    Location
    Alameda, CA, USA
    Posts
    5,230
    Thanks
    302
    Thanked 864 Times in 851 Posts
    Qt products
    Qt5
    Platforms
    Windows

    Default Re: Integration: Nested Tree Parent Child C++ Classes with Models/Delegates for QML

    Yes, anda_skoa has been very generous with his help on this one and has given you a well-thought-out and flexible design.

    If you use the Card and Album classes as anda_skoa has defined them in his code, you don't actually need an explicit copy constructor (or even an assignment operator) because C++ will implement default ones for you. Usually default constructor / assignment does a bitwise copy of the right-hand side instance (i.e. the instance you are copying from), except when the class being copied contains member variables which themselves have copy / assignment operators, which QString and QStringList do (and which fundamental types like int, float, etc. also have). If you add anything to the definitions of these classes which does not have explicit copy / assignment, you'll either have to make copy and assignment methods for them, or implement them in the Card or Album class. For example, if you add a pointer variable, you'll probably want to either wrap it in a smart pointer (which has copy / assignment), make a deep copy of the contents of the instance pointed to, or add another "manager" which is in charge of the lifetime of these pointers (rather than delete them in the Card or Album destructor).

    Combining the Card and CardManager class in the same file is basically just for convenience. More importantly, only the CardManager should be allowed to create Card instances, because then it will automatically add them to the list of Card instances it is managing. Likewise for Album and AlbumManager. You can't have a standalone, unmanaged Card or Album. But on the other hand, anda_skoa's design allows for cards that belong to no album as well as cards that belong to multiple albums.

    You'll probably only have one instance each of the AlbumManager and CardManager classes, but the design also anticipates that you might want to expand to handle multiple users, each one of whom will have their own card and album collections, so you'd need a card and album manager for each of them.

    The AlbumManager and CardManager classes are the GUI-independent classes that are used by the AlbumCardsModel. This model is the interface to the views; when either the CardManager or AlbumManager make changes to their content, they emit signals that the model listens for so it can in turn update the views. You might have several instances of AlbumCardsModel - one for interfacing to one or more views of different albums.
    Last edited by d_stranz; 31st December 2016 at 01:02.
    <=== 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. The following user says thank you to d_stranz for this useful post:

    Nizars (31st December 2016)

Similar Threads

  1. Nested Models
    By Dan7 in forum Qt Programming
    Replies: 1
    Last Post: 26th August 2015, 20:31
  2. Models and Delegates
    By bgeller in forum Newbie
    Replies: 13
    Last Post: 4th March 2010, 04:46
  3. QStandardItemModel, parent / child to form tree structure
    By Nightfox in forum Qt Programming
    Replies: 2
    Last Post: 8th January 2010, 17:01
  4. Nested delegates in a QListWidget
    By youkai in forum Qt Programming
    Replies: 9
    Last Post: 7th April 2009, 08:48
  5. Models, delegates or views and how?
    By maddogg in forum Newbie
    Replies: 3
    Last Post: 9th November 2007, 13:59

Tags for this Thread

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.