Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. General and Desktop
  4. 'simpletreemodel' based QAbstractItemModel: NOT editable but DYNAMIC

'simpletreemodel' based QAbstractItemModel: NOT editable but DYNAMIC

Scheduled Pinned Locked Moved Unsolved General and Desktop
4 Posts 2 Posters 322 Views
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • N Offline
    N Offline
    natxo14
    wrote on last edited by natxo14
    #1

    Hello,

    I'm developing a simple application based on a classic model/view structure.
    I've read all the documentation at https://doc.qt.io/qt-5/modelview.html and https://doc.qt.io/qt-5/model-view-programming.html and also based my work on the https://doc.qt.io/qt-5/qtwidgets-itemviews-simpletreemodel-example.html example, but I have still some questions/doubts about how to operate.

    My Model is basically 'TreeModel', heritating from QAbstractItemModel.

    My goal is to display the progress of some tasks in two columns: an unique 'id' column and a 'progress' one. Tasks can be 'complex', i.e., can have children, so, as in the simpletreemodel, I have a *rootItem with a tree structure beneath it.

    Data is coming from a JSON file that I parse recursively to create the tree structure.

    My model is NOT editable, i.e. user can't modify data on the view, data is read-only, BUT is dynamic, meaning that new data arrive from time to time from an external server : it is needed to update the progress of already existing ID's (ongoing tasks), new ID's (new tasks), 'leafs' on the tree that become nodes (simple tasks that transform into 'complex' task: creation of new children on the go), IDs that disappear (finished tasks)...

    The way I work so far is:

    The first time I get data from server, I parse it and 'fill' the *rootItem/tree structure. It shows perfectly on my view, respecting the tree structure.

    MY ISSUES comes when I have to update the data:
    On the fast and ugly version, I create a new *newDataRoot with incoming data and I swap it with *rootItem:

    void TreeModel::updateProgress(QJsonObject newProgress)
    {
        ProgressTreeItem *newDataRoot = new ProgressTreeItem(newProgress);
        setupJSONModelData(newProgress, newDataRoot); // parse JSON file, fill *newDataRoot structure same way as done with *rootItem
    
        beginResetModel();
        delete rootItem;
        rootItem    = newDataRoot;
        newDataRoot = nullptr;
        endResetModel();
    }
    

    It 'works', meaning displayed data is updated, but each time the model is reset, the view's expanded items are collapsed. I resolved this issue with this https://stackoverflow.com/questions/3253301/howto-restore-qtreeview-last-expanded-state .

    And it works. Fine. But I still feel this is kinda of 'cheating', or at least a 'dirty' solution.

    So, my idea to update the data on *rootItem structure is to update each 'node'/'leaf' of the tree structure using their unique 'id' to identify them.

    • Anytime new data arrives, I create/fill another temporary *tmpRootItem.
    • I create a function to iterate over all the nodes on *tmpRootItem, and make a search by 'id' on original *rootItem.
      If 'id' existed, update that id's progress (case a)
      If 'id' didn't exist (new task), add it to rootItem (case b)
    • Do reverse search: iterate over original *rootItem and search each 'id' on *tmpRootItem
      if 'id' exists, do nothing
      if 'id' does not exist, it corresponds to a finished task: delete that node. (case c)
      My questions:

    As data is not editable by user, I assume that model's setData() function shouldn't be implemented. Is this correct??
    As data is dynamic, I should call signal dataChanged() at the end of my algorithm with topLeft my 'first' index and bottomRight my 'last' index??
    Or should it better to call, in each of the cases on the algorithm:
    * (case a) dataChanged() signal on indexes that have changed
    * (case b) rowsInserted() signal on indexes that have been added ( new rows on the model, as new tasks are created)
    * (case c) rowsRemoved() signal in case the task has finished and thus no longer needed its display.

    I know that this seems a very personal/particular case of use, but I really feel that it could help lots of people (including myself) to learn more on Models and how they work.

    Thank you very much!

    VRoninV 1 Reply Last reply
    0
    • N natxo14

      Hello,

      I'm developing a simple application based on a classic model/view structure.
      I've read all the documentation at https://doc.qt.io/qt-5/modelview.html and https://doc.qt.io/qt-5/model-view-programming.html and also based my work on the https://doc.qt.io/qt-5/qtwidgets-itemviews-simpletreemodel-example.html example, but I have still some questions/doubts about how to operate.

      My Model is basically 'TreeModel', heritating from QAbstractItemModel.

      My goal is to display the progress of some tasks in two columns: an unique 'id' column and a 'progress' one. Tasks can be 'complex', i.e., can have children, so, as in the simpletreemodel, I have a *rootItem with a tree structure beneath it.

      Data is coming from a JSON file that I parse recursively to create the tree structure.

      My model is NOT editable, i.e. user can't modify data on the view, data is read-only, BUT is dynamic, meaning that new data arrive from time to time from an external server : it is needed to update the progress of already existing ID's (ongoing tasks), new ID's (new tasks), 'leafs' on the tree that become nodes (simple tasks that transform into 'complex' task: creation of new children on the go), IDs that disappear (finished tasks)...

      The way I work so far is:

      The first time I get data from server, I parse it and 'fill' the *rootItem/tree structure. It shows perfectly on my view, respecting the tree structure.

      MY ISSUES comes when I have to update the data:
      On the fast and ugly version, I create a new *newDataRoot with incoming data and I swap it with *rootItem:

      void TreeModel::updateProgress(QJsonObject newProgress)
      {
          ProgressTreeItem *newDataRoot = new ProgressTreeItem(newProgress);
          setupJSONModelData(newProgress, newDataRoot); // parse JSON file, fill *newDataRoot structure same way as done with *rootItem
      
          beginResetModel();
          delete rootItem;
          rootItem    = newDataRoot;
          newDataRoot = nullptr;
          endResetModel();
      }
      

      It 'works', meaning displayed data is updated, but each time the model is reset, the view's expanded items are collapsed. I resolved this issue with this https://stackoverflow.com/questions/3253301/howto-restore-qtreeview-last-expanded-state .

      And it works. Fine. But I still feel this is kinda of 'cheating', or at least a 'dirty' solution.

      So, my idea to update the data on *rootItem structure is to update each 'node'/'leaf' of the tree structure using their unique 'id' to identify them.

      • Anytime new data arrives, I create/fill another temporary *tmpRootItem.
      • I create a function to iterate over all the nodes on *tmpRootItem, and make a search by 'id' on original *rootItem.
        If 'id' existed, update that id's progress (case a)
        If 'id' didn't exist (new task), add it to rootItem (case b)
      • Do reverse search: iterate over original *rootItem and search each 'id' on *tmpRootItem
        if 'id' exists, do nothing
        if 'id' does not exist, it corresponds to a finished task: delete that node. (case c)
        My questions:

      As data is not editable by user, I assume that model's setData() function shouldn't be implemented. Is this correct??
      As data is dynamic, I should call signal dataChanged() at the end of my algorithm with topLeft my 'first' index and bottomRight my 'last' index??
      Or should it better to call, in each of the cases on the algorithm:
      * (case a) dataChanged() signal on indexes that have changed
      * (case b) rowsInserted() signal on indexes that have been added ( new rows on the model, as new tasks are created)
      * (case c) rowsRemoved() signal in case the task has finished and thus no longer needed its display.

      I know that this seems a very personal/particular case of use, but I really feel that it could help lots of people (including myself) to learn more on Models and how they work.

      Thank you very much!

      VRoninV Offline
      VRoninV Offline
      VRonin
      wrote on last edited by
      #2

      But I still feel this is kinda of 'cheating', or at least a 'dirty' solution.

      it is

      Anytime new data arrives, I create/fill another temporary *tmpRootItem.

      No need to create a new root. just act on the one that already exists

      As data is not editable by user, I assume that model's setData() function shouldn't be implemented. Is this correct??

      correct

      Or should it better to call, in each of the cases on the algorithm:

      • (case a) dataChanged() signal on indexes that have changed
      • (case b) rowsInserted() signal on indexes that have been added ( new rows on the model, as new tasks are created)
      • (case c) rowsRemoved() signal in case the task has finished and thus no longer needed its display.

      this is the way
      As a side note, in dataChanged, topLeft and bottomRight must have the same parent (i.e. you have to send a different signal for each affected branch-level of the tree

      but I really feel that it could help lots of people (including myself) to learn more on Models and how they work.

      Best advice ever is to use the model test, it spots SOOOOO many mistakes you could make.

      "La mort n'est rien, mais vivre vaincu et sans gloire, c'est mourir tous les jours"
      ~Napoleon Bonaparte

      On a crusade to banish setIndexWidget() from the holy land of Qt

      1 Reply Last reply
      2
      • N Offline
        N Offline
        natxo14
        wrote on last edited by
        #3

        Then, I'll follow the way, @VRonin

        Thanks a lot for your answer and the advice.
        I'll take a look to that model test if you consider it is (so) worth it.

        ps : good luck in your fight to destroy setIndexWidget() !

        1 Reply Last reply
        0
        • N Offline
          N Offline
          natxo14
          wrote on last edited by
          #4

          Hi there,
          Thanks again for the advice on the Model Test : it was really helpful!

          So I finally got something working and I wanted to shared it, to help people, resolve some doubts and to find possible improving suggestions.

          What I've got so far:
          For the first data coming (a json file that I transform into a QJsonObject, I create the model, I set it to the view, I iterate recursively over the file and I create/fill the TreeItem *rootItem.
          Each of the 'nodes' of the tree have an unique ID, that I use later to get the QModelIndex of internal data as proposed in https://wiki.qt.io/Technical_FAQ#How_can_a_QModelIndex_be_retrived_from_the_model_for_an_internal_data_item.3F / Option "Use unique identifiers**"

          Then, anytime new data is coming, I iterate recursively on the new file/QJsonObject and do:
          a) Search each 'id' on *rootItem (I have a recursive function to look for an ID in any tree node, including its children).

          • if 'id' exists, update its data. QUESTIONS BELOW on match() and createIndex
          • if not, create the TreeItem structure of this node, including the possible children, get what node in *rootItem structure should be the parent of the new node and add/append it to that node.

          My Code:

          void TreeModel::updateModelData(const QJsonObject &p_object, TreeItem *p_TreeItem)
          {
              /*
               * Iterate over all p_object tasks:
               * if their ID existed, update item and view
               * if not, create TreeItem, add it to rootItem structure and update view
               */
              if (!p_object.isEmpty())
              {
                  QJsonArray children = p_object["children"].toArray();
                  if (!children.isEmpty())
                  {
                      for (auto child : qAsConst(children))
                      {
                          TreeItem *progItem = p_TreeItem->searchId(child["id"].toString());
                          if (progItem == nullptr)
                          {
                              // 'ID' NOT FOUND : ADD IT TO p_TreeItem
                              TreeItem *parentItem = p_TreeItem->searchId(p_object["id"].toString());
          
                              // build TreeItem from child QJsonObject , INCLUDING CHILDREN and children of children and...
                              // parentProgItem will be the parent of all the structure. It could be rootItem
                              TreeItem *newItem = new TreeItem(child.toObject(), parentProgItem);
                              setupJSONModelData(child.toObject(), newItem);
                              
                              // QUESTION/DOUBT 0
          		    // As a new node is added to the data structure, createIndex should be called for these new nodes. Is this correct??
                              QModelIndex parentIndex;
                              if (parentItem == rootItem)
                              {
                                  // If parent item is rootItem, then create the index
                                  // The null/empty QModelndex represents the root of a tree model
                                  parentIndex = QModelIndex();
                              }
                              else
                              {
                                  parentIndex = createIndex(parentItem->row(), 0, parentItem);
                              }
          
                              int first = parentProgItem->childCount();
                              
                              // QUESTION/DOUBT 1:
          		    // As the following works, I guess that it's correct, but in the documentation...
                              // ...I find it not clear that when adding a row, if that 'node' has children then ...
                              // ... those children's rows are automatically added. Lost quite time trying to call...
                              // begin/endInsertRows for EACH child-node of the new node...
          		    beginInsertRows(parentIndex, first, first);
                              parentProgItem->appendChild(newItem);
                              endInsertRows();
                          }
                          else
                          {
                              // udpate values of the existing nodes.
                              progItem->updateItem(child.toObject());
          
                              // look for THE index that match the ID to call dataChanged()
                              
          		    // QUESTION 2 : 
                              QModelIndexList matchesOnID =
                                  match(index(0, 1), Qt::UserRole, progItem->id(), 1, Qt::MatchRecursive);
                              for (QModelIndex match : qAsConst(matchesOnID))
                              {
                                  // QUESTION 3 :
                                  QModelIndex nextColIndex =
                                      createIndex(match.row(), match.column() + 1, match.internalPointer());
                                  emit dataChanged(match, nextColIndex);
                              }
          
                              // recursive call
                              if (child["type"].toString() == "Node")
                              {
                                  updateModelData(child.toObject(), p_progressTreeItem);
                              }
                          }
                      }
                  }
              }
          }
          

          QUESTION 2:
          TreeModel::data() returns the ID for role == Qt::UserRole.
          That ID value is displayed in the second column of the view.
          According to Qt doc QAIM::match() "returns a list of indexes for the items in the column of the start index where data stored under the given role matches the specified value"
          So, if I look for matches of my ID, what should it be the start QModelIndex ??
          I've tried both match(index(0, 1), Qt::UserRole, progItem->id(), 1, Qt::MatchRecursive); and match(index(0, 0), Qt::UserRole,...) and they 'worked' both, but I don't if it's because the Recursive nature of the search or another reason. Should the start index correspond to the colum 1 (the column where ID is displayed) or the view and the model are not linked for this matter??

          QUESTION 3:
          I create an index nextColIndex based on the previous match to try to apply the dataChanged signal to the whole row : the third column of the view is the one that changes really(display needed to be updated), it shows some data associated to the item.

          To end it up: how to handle the items that 'dissapear', finished tasks:
          So, related to "No need to create a new root. just act on the one that already exists", I didn't manage to act directly on the original root (lack of programming competences, I guess... didn't figure it out how to do it): I need to create a temporary root tree with new incoming data to compare it to the previously existing one. If someone has pseudocode on how to implement this, I'd be thankfull to hear about it!
          My 'algorithm':
          Iterate over rootItem. Search each 'id' on newRootItem

          • If 'id' does not exist (the task with that 'id' is finished), delete that 'id' (and possible children) from rootItem structure.

          As deleting items (in the underlying TreeItem QVector<TreeItem*> m_childItems; structure) caused memory issues, I added a QMutableVectorIterator<TreeItem *> *m_childIterator; member on ItemTree class and expose its next(), hasNext(), toFront() and remove() functions.
          The code:

          void TreeModel::recursiveCleanProgress(TreeItem *p_oldProgressParent, TreeItem *p_newProgressParent)
          {
              p_oldProgressParent->iteratorToFront();
              while (p_oldProgressParent->iteratorHasNext())
              {
                  CProgressTreeItem *nextChild = p_oldProgressParent->iteratorNext();
          
                  CProgressTreeItem *progressItem = p_newProgressParent->searchId(nextChild->id());
                  if (progressItem == nullptr)
                  {
                      int childRow = nextChild->row();
                      // Look for PARENT QModelIndex : needed to pass to beginRemoveRows
                      QModelIndexList matches;
                      if (p_oldProgressParent->id() == "")
                          matches.push_back(QModelIndex());
                      else
                          matches =
                              match(index(0, 1, QModelIndex()), Qt::UserRole, p_oldProgressParent->id(), 1, Qt::MatchRecursive);
          
                      for (QModelIndex match : qAsConst(matches))
                      {
                          beginRemoveRows(match, childRow, childRow);
          		// remove() does not 'destroy' the item from the QVector: delete it later
                          p_oldProgressParent->iteratorRemove();
          		delete nextChild;
                          endRemoveRows();
                      }
                  }
                  else
                  {
                      recursiveCleanProgress(nextChild, p_newProgressParent);
                  }
              }
          }
          

          ps : I'm reopening this thread because I feel it can be a useful complement to the previous questions. If another thread should be open, no problem with that.

          1 Reply Last reply
          1

          • Login

          • Login or register to search.
          • First post
            Last post
          0
          • Categories
          • Recent
          • Tags
          • Popular
          • Users
          • Groups
          • Search
          • Get Qt Extensions
          • Unsolved