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. Why is QStandardItemModel forcing me to use pointers to my data? My data is const vector of vectors

Why is QStandardItemModel forcing me to use pointers to my data? My data is const vector of vectors

Scheduled Pinned Locked Moved Unsolved General and Desktop
8 Posts 3 Posters 1.4k Views 1 Watching
  • 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.
  • T Offline
    T Offline
    toglia3d
    wrote on last edited by toglia3d
    #1

    I have a large collection of objects "A" that was specifically designed to be contiguous in memory for cache efficiency reasons and comes in the form of a const vector of vectors. I want to show this data as a collection of QListViews (UX) but it is also important that these QListViews share the same selection model so I was very inclined to have a single QStandardItemModel for all the groups of items. I saw this could possibly be achieved with the QListView::setModel and QListView::setModelColumn combo.

    So I proceed to write my view model like:

    class MyViewModel final : public QStandardItemModel
    {
    public:
        explicit      MyViewModel(std::vector<std::vector<A>> const& groups, QObject* parent = nullptr);
        QModelIndex   index(int row, int column, const QModelIndex& parent) const override;
        QModelIndex   parent(const QModelIndex& child) const override;
        int           rowCount(const QModelIndex& parent) const override;
        int           columnCount(const QModelIndex& parent) const override;
        QVariant      data(const QModelIndex& index, int role) const override;
    
        std::vector<std::vector<A>> const& m_groups;
    }
    
    QModelIndex MyViewModel::index(int row, int column, const QModelIndex& parent) const
    {
       auto const validColumn = column >= 0 && column < static_cast<int>(m_groups.size());
       if (validColumn)
       {
          auto const validRow = row >= 0 && row < static_cast<int>(m_groups[row].items.size());
          if (validRow)
          {
             return createIndex(row, column, nullptr);
          }
       }
       return m_nullParent;
    }
    
    QModelIndex MyViewModel::parent(const QModelIndex& index) const
    {
       if (!index.isValid() || index.row() == 0)
       {
          return m_nullParent;
       }
       return createIndex(0, index.column(), nullptr);
    }
    
    bool MyViewModel::isValidColumn(const QModelIndex & index) const
    {
       auto const column = index.column();
       return column >= 0 && column < static_cast<int>(m_groups.size());
    }
    
    int MyViewModel::rowCount(const QModelIndex& index) const
    {   
       if (!index.isValid())
       {
          return 1;
       }
       // This section is never reached as all index seem to be invalid
    
       auto const column = index.column();
       if (isValidColumn(index))
       {
          return static_cast<int>(m_groups[column].items.size());
       }   
       return m_groups.size();
    }
    
    int MyViewModel::columnCount(const QModelIndex& index) const
    {
       if (!index.isValid())
       {
          return static_cast<int>(m_groups.size());
       }
       return 1;
    }
    

    The problem is that I can't get MyViewModel::rowCount to work correctly. All I ever get as a QModelIndex parameter is invalid so I'm not able to tell how many items each column has.

    From everything I've seen in other examples, a parent data pointer (void*) is attached to the createIndex command and it's casted back in the QStandardItemModel::rowCount method using the "internalPointer" so this query can be performed. Now you can see that firstly I have no pointer to group (and it would be potentially invalidated on every push), secondly, my data is handed as a const so even if I had the pointer of the "items" vector (which would be dangerous) I would have to remove the const - all of it looks convoluted.

    Now I really don't want to change the original data structure but I can't seem to find any other way to correctly implement MyViewModel::rowCount which seems to be the only problem I have so far.

    1 Reply Last reply
    0
    • Chris KawaC Offline
      Chris KawaC Offline
      Chris Kawa
      Lifetime Qt Champion
      wrote on last edited by
      #2

      You're inheriting the wrong class for this. QStandardItemModel is a concrete model implementation that operates on QStandardItems for manipulating and storing data. Reimplementing stuff like rowCount, index and parent in it will lead to all sorts of nastiness as you're breaking internal state of the model.

      Since you already have your data structure and don't need any extra storage provided by QStandardItem you should rather inherit QAbstractItemModel which lets you provide your own data storage.

      T 1 Reply Last reply
      4
      • Chris KawaC Chris Kawa

        You're inheriting the wrong class for this. QStandardItemModel is a concrete model implementation that operates on QStandardItems for manipulating and storing data. Reimplementing stuff like rowCount, index and parent in it will lead to all sorts of nastiness as you're breaking internal state of the model.

        Since you already have your data structure and don't need any extra storage provided by QStandardItem you should rather inherit QAbstractItemModel which lets you provide your own data storage.

        T Offline
        T Offline
        toglia3d
        wrote on last edited by
        #3

        @Chris-Kawa said in Why is QStandardItemModel forcing me to use pointers to my data? My data is const vector of vectors:

        Hi Chris, Thanks for your reply.

        I've made the changes but I'm still not able to find a way of implementing rowCount without using the internalData pointer.

        Say my data looks like (vector of vector):
        group1 : item1_1, item1_2, item1_3
        group2 : item2_1, item2_2

        So my first question is: do I need to create a QModelIndex for the groups? if so, how? Because my data is packed in vectors I don't really have the concept of a parent.

        QModelIndex MyViewModel::index(int row, int column, const QModelIndex& parent) const
        {
           auto const validColumn = column >= 0 && column < static_cast<int>(m_groups.size());
           if (validColumn)
           {
              auto const validRow = row >= 0 && row < static_cast<int>(m_groups[column].items.size());
              if (validRow)
              {
                 return createIndex(row, column, nullptr);
              }
           }
           return m_nullParent;
        }
        
        QModelIndex MyViewModel::parent(const QModelIndex& index) const
        {
           //I'm unsure how this should work 
           if (!index.isValid() || (index.row() == 0 && index.column() == 0))
           {
              return m_nullParent;
           }
        
           if (index.row() == 0 && index.column() > 0)
           {
              createIndex(0, 0, nullptr);
           }
        
           return createIndex(0, index.column(), nullptr);
        }
        
        int MyViewModel::rowCount(const QModelIndex& parent) const
        {   
           if (!index.isValid())
           {
              return 1;
           }
           // This section is never reached as all index seem to be invalid (-1, -1)
           auto const column = index.column();
           if (isValidColumn(index))
           {
              return static_cast<int>(m_groups[column].items.size());
           }   
           return m_groups.size();
        }
        
        int MyViewModel::columnCount(const QModelIndex& index) const
        {
           if (!index.isValid())
           {
              return static_cast<int>(m_groups.size());
           }
           return 1;
        }
        
        1 Reply Last reply
        0
        • Chris KawaC Offline
          Chris KawaC Offline
          Chris Kawa
          Lifetime Qt Champion
          wrote on last edited by Chris Kawa
          #4

          A Qt models are designed to store N-dimensional data structures. Views show a slice of that N-dimensional data. You have a two dimensional data set (groups of items). So the task is to make a model that would represent your 2 dimensional data in a way the view can look at.

          A list view needs a flat 1 dimensional list. In your case this translates to a group vector.
          A model has rows and columns. A single column from this 2 dimensional grid is used as the flat list for the view. You choose which with the QListView::setModelColumn method you mentioned.

          As for parents in the model. Parents and children are used to model a tree structure. You want a flat grid of depth 1 in which each column represents a group. This imposes a restriction for your data - vectors in each group need to be the same length. To visualize this a list view expects a model like this:

          item1_1  item2_1  item3_1
          item1_2  item2_2  item3_2
          item1_3  item2_3  item3_3
          

          and what you have is

          item1_1  item2_1  item3_1
          item1_2           item3_2
          item1_3  
          

          so this won't work here. You'll need to artificially "pad" them in the model. If you want to avoid that restriction you'll have to use some other way to feed the views e.g a separate model for each group or proxy models.

          So how to implement the model methods?

          • parent - this returns a parent for an item in a tree. Since your tree has only one level you just always return an invalid index (the "invisibile root").

          • rowCount - this returns the number of elements in a group. As mentioned above this is a bit problematic in your case, because you have different number of elements in each vector, but assuming you can "pretend" all are the same size then for an invalid index (the "invisible root") rowCount should return the length of longest of your inner vectors. For any valid index you return 0 because you only have one level in your tree.

          • columnCount - this returns the number of groups, so for an invalid index (the "invisible root") this is simply the size of your outer vector. For any valid index this is again 0, because it's a one level tree.

          • index - this is used to represent a single item in the model. When parent is invalid (which is the "invisible root" of data) column is the group index and row is the element of a vector in that group. Return an index with a pointer to the [column][row] element of your data. If it's one of the "padded" elements you can use a nullptr as the internal pointer. If parent is valid then it means you're being asked for a child of any of your data elements. You have none since this is not a tree so return an invalid index in that case.

          T 1 Reply Last reply
          5
          • Chris KawaC Chris Kawa

            A Qt models are designed to store N-dimensional data structures. Views show a slice of that N-dimensional data. You have a two dimensional data set (groups of items). So the task is to make a model that would represent your 2 dimensional data in a way the view can look at.

            A list view needs a flat 1 dimensional list. In your case this translates to a group vector.
            A model has rows and columns. A single column from this 2 dimensional grid is used as the flat list for the view. You choose which with the QListView::setModelColumn method you mentioned.

            As for parents in the model. Parents and children are used to model a tree structure. You want a flat grid of depth 1 in which each column represents a group. This imposes a restriction for your data - vectors in each group need to be the same length. To visualize this a list view expects a model like this:

            item1_1  item2_1  item3_1
            item1_2  item2_2  item3_2
            item1_3  item2_3  item3_3
            

            and what you have is

            item1_1  item2_1  item3_1
            item1_2           item3_2
            item1_3  
            

            so this won't work here. You'll need to artificially "pad" them in the model. If you want to avoid that restriction you'll have to use some other way to feed the views e.g a separate model for each group or proxy models.

            So how to implement the model methods?

            • parent - this returns a parent for an item in a tree. Since your tree has only one level you just always return an invalid index (the "invisibile root").

            • rowCount - this returns the number of elements in a group. As mentioned above this is a bit problematic in your case, because you have different number of elements in each vector, but assuming you can "pretend" all are the same size then for an invalid index (the "invisible root") rowCount should return the length of longest of your inner vectors. For any valid index you return 0 because you only have one level in your tree.

            • columnCount - this returns the number of groups, so for an invalid index (the "invisible root") this is simply the size of your outer vector. For any valid index this is again 0, because it's a one level tree.

            • index - this is used to represent a single item in the model. When parent is invalid (which is the "invisible root" of data) column is the group index and row is the element of a vector in that group. Return an index with a pointer to the [column][row] element of your data. If it's one of the "padded" elements you can use a nullptr as the internal pointer. If parent is valid then it means you're being asked for a child of any of your data elements. You have none since this is not a tree so return an invalid index in that case.

            T Offline
            T Offline
            toglia3d
            wrote on last edited by toglia3d
            #5

            @Chris-Kawa said in Why is QStandardItemModel forcing me to use pointers to my data? My data is const vector of vectors:

            Thanks for your response! It is definitely shedding some light but now I'm getting into a different issue.

            First of all, I wanted to mention that the reason why I'm trying to have the multiple QListViews, share the same data model is to be able to share the selection model between them too. Initially, I had every QListView have its own QAbstractItemModel instance and I found it very convoluted and quirky to share their selection, especially because multi-selection is needed and shift/control-clicking was tricky to get right.

            So I've gone with the padding solution like this:

            QModelIndex MyViewModel::index(int row, int column, const QModelIndex& parent) const
            {
               if (parent.isValid())
               {
                  return m_nullParent;
               }
               auto const validColumn = column >= 0 && column < static_cast<int>(m_groups.size());
               if (validColumn)
               {
                  auto const validRow = row >= 0 && row < static_cast<int>(m_groups[column].items.size());
                  if (validRow)
                  {
                     return createIndex(row, column, nullptr);
                  }
               }
               return m_nullParent;
            }
            
            QModelIndex MyViewModel::parent(const QModelIndex& parent) const
            {   
               return m_nullParent;
            }
            
            int MyViewModel::rowCount(const QModelIndex& parent) const
            {
               if (parent.isValid())
               {
                  return 0;
               }
               size_t maxRowCount = 0;
               for (auto const& group : m_groups)
               {
                  maxRowCount = std::max(maxRowCount, group.items.size());
               }   
               return maxRowCount;
            }
            
            int MyViewModel::columnCount(const QModelIndex& parent) const
            {
               if (parent.isValid())
               {
                  return 0;
               }
               return static_cast<int>(m_groups.size());
            }
            

            But now the QListView asserts in QIconModeViewBase::addLeaf line 3142.

            if (vi->isValid() && vi->rect().intersects(area) && vi->visited != visited) {
               QModelIndex index  = _this->dd->listViewItemToIndex(*vi);
               Q_ASSERT(index.isValid()); //<-here
            

            Seems like the QListView doesn't like having a row count / created indices mismatch? Then I thought I could fully create these padded indices but then my UI would just like really off as it would think it has all these items when doing the drawing...

            So maybe the only way of doing this would be would the proxy model but then I would be going back to square one as I've read it's also convoluted the share the same selection model between different proxy models

            Chris KawaC 1 Reply Last reply
            0
            • VRoninV Offline
              VRoninV Offline
              VRonin
              wrote on last edited by
              #6

              @toglia3d said in Why is QStandardItemModel forcing me to use pointers to my data? My data is const vector of vectors:

              I had every QListView have its own QAbstractItemModel instance and I found it very convoluted and quirky to share their selection

              It shouldn't be hard at all. You just connect to view->selectionModel(),&QItemSelectionModel::secelctionChanged and replicate the selection in the other views. Each view has a mono-dimensional model so it should be super easy

              "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
              3
              • T toglia3d

                @Chris-Kawa said in Why is QStandardItemModel forcing me to use pointers to my data? My data is const vector of vectors:

                Thanks for your response! It is definitely shedding some light but now I'm getting into a different issue.

                First of all, I wanted to mention that the reason why I'm trying to have the multiple QListViews, share the same data model is to be able to share the selection model between them too. Initially, I had every QListView have its own QAbstractItemModel instance and I found it very convoluted and quirky to share their selection, especially because multi-selection is needed and shift/control-clicking was tricky to get right.

                So I've gone with the padding solution like this:

                QModelIndex MyViewModel::index(int row, int column, const QModelIndex& parent) const
                {
                   if (parent.isValid())
                   {
                      return m_nullParent;
                   }
                   auto const validColumn = column >= 0 && column < static_cast<int>(m_groups.size());
                   if (validColumn)
                   {
                      auto const validRow = row >= 0 && row < static_cast<int>(m_groups[column].items.size());
                      if (validRow)
                      {
                         return createIndex(row, column, nullptr);
                      }
                   }
                   return m_nullParent;
                }
                
                QModelIndex MyViewModel::parent(const QModelIndex& parent) const
                {   
                   return m_nullParent;
                }
                
                int MyViewModel::rowCount(const QModelIndex& parent) const
                {
                   if (parent.isValid())
                   {
                      return 0;
                   }
                   size_t maxRowCount = 0;
                   for (auto const& group : m_groups)
                   {
                      maxRowCount = std::max(maxRowCount, group.items.size());
                   }   
                   return maxRowCount;
                }
                
                int MyViewModel::columnCount(const QModelIndex& parent) const
                {
                   if (parent.isValid())
                   {
                      return 0;
                   }
                   return static_cast<int>(m_groups.size());
                }
                

                But now the QListView asserts in QIconModeViewBase::addLeaf line 3142.

                if (vi->isValid() && vi->rect().intersects(area) && vi->visited != visited) {
                   QModelIndex index  = _this->dd->listViewItemToIndex(*vi);
                   Q_ASSERT(index.isValid()); //<-here
                

                Seems like the QListView doesn't like having a row count / created indices mismatch? Then I thought I could fully create these padded indices but then my UI would just like really off as it would think it has all these items when doing the drawing...

                So maybe the only way of doing this would be would the proxy model but then I would be going back to square one as I've read it's also convoluted the share the same selection model between different proxy models

                Chris KawaC Offline
                Chris KawaC Offline
                Chris Kawa
                Lifetime Qt Champion
                wrote on last edited by
                #7

                @toglia3d said:

                Seems like the QListView doesn't like having a row count / created indices mismatch?

                Yes, that's the restriction I mentioned. You really need to have these indices there in this setup, even if they don't represent any actual data. The model needs to function as it were a full rectangular grid.

                So maybe the only way of doing this would be would the proxy model

                I'm not sure what problems you actually had but as @VRonin said, it doesn't seem too hard to have separate selection models syncing.

                Btw. I'm just throwing ideas around but since you want to display all the groups and have their selection synced can't you just use one table view instead of a series of synced list views? Obviously this wouldn't work if they're not next to each other or you want to have independent scrolling but I thought I'd ask.

                T 1 Reply Last reply
                2
                • Chris KawaC Chris Kawa

                  @toglia3d said:

                  Seems like the QListView doesn't like having a row count / created indices mismatch?

                  Yes, that's the restriction I mentioned. You really need to have these indices there in this setup, even if they don't represent any actual data. The model needs to function as it were a full rectangular grid.

                  So maybe the only way of doing this would be would the proxy model

                  I'm not sure what problems you actually had but as @VRonin said, it doesn't seem too hard to have separate selection models syncing.

                  Btw. I'm just throwing ideas around but since you want to display all the groups and have their selection synced can't you just use one table view instead of a series of synced list views? Obviously this wouldn't work if they're not next to each other or you want to have independent scrolling but I thought I'd ask.

                  T Offline
                  T Offline
                  toglia3d
                  wrote on last edited by toglia3d
                  #8

                  @Chris-Kawa said in Why is QStandardItemModel forcing me to use pointers to my data? My data is const vector of vectors:
                  I've gone back to my initial implementation of having multiple QAbstractViewModels. It's not that it is difficult to implement it's just convoluted having these 2 models in sync, especially because sometimes the global (my model) wants to control the selection and some it's the QListView that wants to control the selection. And there are so many cases to take into account - shift/control clicks between listview, box selections etc, then there's the drag and drop between groups etc.

                  For this, I've implemented a unidirectional data flow where I capture all the selection events from the QListView update my selection model and send the signals back to the QListViews.

                  1 Reply Last reply
                  0

                  • Login

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