Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt
  • Unsolved
Collapse
qtws26_logo

The Qt Event of the Year: Qt World Summit 2026

Save Your Spot >
Brand Logo
  1. Home
  2. Qt Development
  3. QML and Qt Quick
  4. Populating a TabBar from a model
Qt 6.12 LTS is out! See what's new in the release blog

Populating a TabBar from a model

Scheduled Pinned Locked Moved Solved QML and Qt Quick
11 Posts 4 Posters 1.9k Views 2 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.
  • mzimmersM Offline
    mzimmersM Offline
    mzimmers
    wrote on last edited by
    #1

    Hi all -

    I'd like to create a TabBar with buttons derived from a C++ model. Each button would get its text from a property in the model.

    Doable? Perhaps with addItem()?

    Thanks...

    B 1 Reply Last reply
    0
    • mzimmersM mzimmers

      @Bob64 said in Populating a TabBar from a model:

      isn't going to work as seamlessly on the QML side as the approach I mentioned

      I thought I was using the approach you mentioned. Where did I deviate from your suggestion?

      Thanks...

      B Offline
      B Offline
      Bob64
      wrote on last edited by Bob64
      #10

      @mzimmers sorry, there is more than one thing going on here. My original suggestion was to use the Repeater which you have indeed followed. But then you asked about the model exposure in QML.

      The usual way to expose a QAbstractListModel to QML is to override the data function. This returns a QVariant so whatever you return needs to be storable in a QVariant.

      If you have a model that has a number of properties at each index, let's say "name", "age", "occupation", probably the most natural way to expose this is to override roleNames() and data():

      class PersonListModel : public QAbstractListModel
      {
          Q_OBJECT
      public:
          PersonListModel (QObject* parent);
      
          enum MyRoles {
              NameRole = Qt::UserRole + 1,
              AgeRole,
              OccupationRole
          };
          ...
          private:
              QHash<int, QString> m_roles;
      
      PersonListModel ::PersonListModel (QObject* parent) {
          // init roles
          m_roles[NameRole] = "name";
          m_roles[AgeRole] = "age";
          m_roles[OccupationRole] = "occupation";
          ...
      }
      
      // Override QAbstractItemModel function...
      QHash<int, QByteArray> PersonListModel ::roleNames() const
      {
          return m_roles;
      }
      
      // Override QAbstractItemModel function...
      QVariant PersonListModel::data(const QModelIndex &index, int role) const 
      {
          if (!index.isValid())
              return QVariant();
      
          // For illustration only, the below assumes your actual data is stored
          // in something like a list of structs with `name`, `age` fields etc. 
         // Obviously you can implement this in any way you like as long as you
         // can take an index and return the requested value
      
          if (index.row() < 0 || index.row() >= m_myModelData.size())
              return QVariant();
      
          switch (role) {
          case NameRole:
              return m_myModelData[index.row()].name;
          case AgeRole:
              ...
      }
      

      Now, the point about roleNames() in the above is that those strings that you associated with the enum values are what get exposed as property names on the QML side.

      The QML does a lot of work for you given the above. In a delegate that references the model, you can directly refer to name, age etc., and under the hood, Qt will be calling data with the current index and the requested role ID and will be extracting the value from the returned QVariant into a QML-compatible type.

      Relating this to your case, there are two approaches you could take. One is to "flatten" the model in a sense so that you don't need Zone as a type in this particular part of the QML code. So, what you might do is to define a ZoneNameRole mapped to "zoneName". You would implement your data() method something like this:

              case ZoneNameRole:
                  return getZone(index.row()).name;
              ...
      

      Then in your QML, you could assign your button text simply as:

      Repeater {
          model: zoneModel
          delegate: TabButton {
              text: zoneName
          }
      }
      

      The other option is to expose Zone explicitly from the model as a role in its own right. This is possible because Zone is a Q_GADGET so is itself a type that interoperates with QML. The only thing you need to do if you aren't doing so already is to have a line like this in the header where you define Zone:

      Q_DECLARE_METATYPE(Zone)
      

      This allows a Zone to be stored in a QVariant.

      With this approach your code might be more like this:

      class ZoneModel : public QAbstractListModel
      {
          Q_OBJECT
      public:
          ZoneModel (QObject* parent);
      
          enum MyRoles {
             ZoneRole = Qt::UserRole + 1,
          };
          ...
          private:
              QHash<int, QString> m_roles;
      
      ZoneModel ::ZoneModel (QObject* parent) {
          // init roles
          m_roles[ZoneRole] = "zone";
          ...
      }
      
      // Override QAbstractItemModel function...
      QHash<int, QByteArray> ZoneModel ::roleNames() const
      {
          return m_roles;
      }
      
      // Override QAbstractItemModel function...
      QVariant ZoneModel::data(const QModelIndex &index, int role) const 
      {
          // ... check preconditions etc as before ...
      
          switch (role) {
          case ZoneRole:
              return getZone(index.row());
              ...
      }
      

      And in the QML:

      Repeater {
          model: zoneModel
          delegate: TabButton {
              text: zone.name
          }
      }
      

      The official documentation is here:

      https://doc.qt.io/qt-5/qtquick-modelviewsdata-cppmodels.html#qabstractitemmodel-subclass

      mzimmersM 1 Reply Last reply
      1
      • tzorakeT Offline
        tzorakeT Offline
        tzorake
        wrote on last edited by
        #2

        Hello. I read your requirements for desired widget, but didn't understand it completely. Can you do it more clear? I mean more description and visual presentation of it.

        Is it something like this?
        Screenshot_1.png

        1 Reply Last reply
        0
        • mzimmersM mzimmers

          Hi all -

          I'd like to create a TabBar with buttons derived from a C++ model. Each button would get its text from a property in the model.

          Doable? Perhaps with addItem()?

          Thanks...

          B Offline
          B Offline
          Bob64
          wrote on last edited by
          #3

          @mzimmers you could use a Repeater in your TabBar with a delegate that creates a button. It could be driven by something based on QAbstractItemModel or QAbstractListModel.

          You could mock it up using a QML ListModel to start with.

          mzimmersM 1 Reply Last reply
          1
          • B Bob64

            @mzimmers you could use a Repeater in your TabBar with a delegate that creates a button. It could be driven by something based on QAbstractItemModel or QAbstractListModel.

            You could mock it up using a QML ListModel to start with.

            mzimmersM Offline
            mzimmersM Offline
            mzimmers
            wrote on last edited by
            #4

            @Bob64 yep...that did the trick:

            TabBar {
            width: parent.width
            
            Repeater {
                model: zoneModel
                delegate: TabButton {
                    text: zoneModel.getZone(index).name
                }
            }
            

            This works just fine. I wonder, though...is there a way for me to eliminate the inner reference to zoneModel? I tried just model.getZone() but it says it's not a function. There's probably another way to form that line but I don't know what it is.

            Thanks...

            piervalliP B 2 Replies Last reply
            0
            • mzimmersM mzimmers

              @Bob64 yep...that did the trick:

              TabBar {
              width: parent.width
              
              Repeater {
                  model: zoneModel
                  delegate: TabButton {
                      text: zoneModel.getZone(index).name
                  }
              }
              

              This works just fine. I wonder, though...is there a way for me to eliminate the inner reference to zoneModel? I tried just model.getZone() but it says it's not a function. There's probably another way to form that line but I don't know what it is.

              Thanks...

              piervalliP Offline
              piervalliP Offline
              piervalli
              wrote on last edited by
              #5
              This post is deleted!
              mzimmersM 1 Reply Last reply
              0
              • mzimmersM mzimmers

                @Bob64 yep...that did the trick:

                TabBar {
                width: parent.width
                
                Repeater {
                    model: zoneModel
                    delegate: TabButton {
                        text: zoneModel.getZone(index).name
                    }
                }
                

                This works just fine. I wonder, though...is there a way for me to eliminate the inner reference to zoneModel? I tried just model.getZone() but it says it's not a function. There's probably another way to form that line but I don't know what it is.

                Thanks...

                B Offline
                B Offline
                Bob64
                wrote on last edited by
                #6

                @mzimmers can you add a zoneName (or maybe just name) role to your model? That would automatically expose zoneName as a property available to your delegate and allow you to do text: zoneName in your button without even having to mention the index.

                1 Reply Last reply
                0
                • piervalliP piervalli

                  This post is deleted!

                  mzimmersM Offline
                  mzimmersM Offline
                  mzimmers
                  wrote on last edited by
                  #7

                  @piervalli I tried that:

                  Repeater {
                      model: zoneModel
                      delegate: TabButton {
                          text: getZone(index).name
                      }
                  }
                  

                  Still yet runtime error not defined.

                  @Bob64 my ZoneModel class contains a list of Zone objects. In order to get the names of the individual zones, I use getZone() to return the zone, then get its name from one of its properties.

                  class ZoneModel: public QAbstractListModel
                  {
                      Q_OBJECT
                      ZoneList *m_list;
                  public:
                      Q_INVOKABLE Zone getZone(int index);
                  ...
                  
                  class Zone {
                      Q_GADGET
                      QString m_name;
                  public:
                      Q_PROPERTY(QString name MEMBER m_name)
                      QString name() const { return m_name; }
                  ...
                  
                  B 1 Reply Last reply
                  0
                  • mzimmersM mzimmers

                    @piervalli I tried that:

                    Repeater {
                        model: zoneModel
                        delegate: TabButton {
                            text: getZone(index).name
                        }
                    }
                    

                    Still yet runtime error not defined.

                    @Bob64 my ZoneModel class contains a list of Zone objects. In order to get the names of the individual zones, I use getZone() to return the zone, then get its name from one of its properties.

                    class ZoneModel: public QAbstractListModel
                    {
                        Q_OBJECT
                        ZoneList *m_list;
                    public:
                        Q_INVOKABLE Zone getZone(int index);
                    ...
                    
                    class Zone {
                        Q_GADGET
                        QString m_name;
                    public:
                        Q_PROPERTY(QString name MEMBER m_name)
                        QString name() const { return m_name; }
                    ...
                    
                    B Offline
                    B Offline
                    Bob64
                    wrote on last edited by Bob64
                    #8

                    @mzimmers ok, I see. The approach you have taken is a way to expose a backend C++ model to QML but it probably isn't going to work as seamlessly on the QML side as the approach I mentioned, which essentially involves overriding roleNames and data.

                    Another approach is a hybrid, where you define a "zone" role and have data return a Zone corresponding to the requested index. Note that data returns a QVariant but it should be possible to arrange for your Zone type to be storable in a QVariant (it might already just work with it being a Q_GADGET - I forget the details at the moment and can't look them up). On the QML side, you should then be able to do something like text: zone.name.

                    mzimmersM 1 Reply Last reply
                    0
                    • B Bob64

                      @mzimmers ok, I see. The approach you have taken is a way to expose a backend C++ model to QML but it probably isn't going to work as seamlessly on the QML side as the approach I mentioned, which essentially involves overriding roleNames and data.

                      Another approach is a hybrid, where you define a "zone" role and have data return a Zone corresponding to the requested index. Note that data returns a QVariant but it should be possible to arrange for your Zone type to be storable in a QVariant (it might already just work with it being a Q_GADGET - I forget the details at the moment and can't look them up). On the QML side, you should then be able to do something like text: zone.name.

                      mzimmersM Offline
                      mzimmersM Offline
                      mzimmers
                      wrote on last edited by
                      #9

                      @Bob64 said in Populating a TabBar from a model:

                      isn't going to work as seamlessly on the QML side as the approach I mentioned

                      I thought I was using the approach you mentioned. Where did I deviate from your suggestion?

                      Thanks...

                      B 1 Reply Last reply
                      0
                      • mzimmersM mzimmers

                        @Bob64 said in Populating a TabBar from a model:

                        isn't going to work as seamlessly on the QML side as the approach I mentioned

                        I thought I was using the approach you mentioned. Where did I deviate from your suggestion?

                        Thanks...

                        B Offline
                        B Offline
                        Bob64
                        wrote on last edited by Bob64
                        #10

                        @mzimmers sorry, there is more than one thing going on here. My original suggestion was to use the Repeater which you have indeed followed. But then you asked about the model exposure in QML.

                        The usual way to expose a QAbstractListModel to QML is to override the data function. This returns a QVariant so whatever you return needs to be storable in a QVariant.

                        If you have a model that has a number of properties at each index, let's say "name", "age", "occupation", probably the most natural way to expose this is to override roleNames() and data():

                        class PersonListModel : public QAbstractListModel
                        {
                            Q_OBJECT
                        public:
                            PersonListModel (QObject* parent);
                        
                            enum MyRoles {
                                NameRole = Qt::UserRole + 1,
                                AgeRole,
                                OccupationRole
                            };
                            ...
                            private:
                                QHash<int, QString> m_roles;
                        
                        PersonListModel ::PersonListModel (QObject* parent) {
                            // init roles
                            m_roles[NameRole] = "name";
                            m_roles[AgeRole] = "age";
                            m_roles[OccupationRole] = "occupation";
                            ...
                        }
                        
                        // Override QAbstractItemModel function...
                        QHash<int, QByteArray> PersonListModel ::roleNames() const
                        {
                            return m_roles;
                        }
                        
                        // Override QAbstractItemModel function...
                        QVariant PersonListModel::data(const QModelIndex &index, int role) const 
                        {
                            if (!index.isValid())
                                return QVariant();
                        
                            // For illustration only, the below assumes your actual data is stored
                            // in something like a list of structs with `name`, `age` fields etc. 
                           // Obviously you can implement this in any way you like as long as you
                           // can take an index and return the requested value
                        
                            if (index.row() < 0 || index.row() >= m_myModelData.size())
                                return QVariant();
                        
                            switch (role) {
                            case NameRole:
                                return m_myModelData[index.row()].name;
                            case AgeRole:
                                ...
                        }
                        

                        Now, the point about roleNames() in the above is that those strings that you associated with the enum values are what get exposed as property names on the QML side.

                        The QML does a lot of work for you given the above. In a delegate that references the model, you can directly refer to name, age etc., and under the hood, Qt will be calling data with the current index and the requested role ID and will be extracting the value from the returned QVariant into a QML-compatible type.

                        Relating this to your case, there are two approaches you could take. One is to "flatten" the model in a sense so that you don't need Zone as a type in this particular part of the QML code. So, what you might do is to define a ZoneNameRole mapped to "zoneName". You would implement your data() method something like this:

                                case ZoneNameRole:
                                    return getZone(index.row()).name;
                                ...
                        

                        Then in your QML, you could assign your button text simply as:

                        Repeater {
                            model: zoneModel
                            delegate: TabButton {
                                text: zoneName
                            }
                        }
                        

                        The other option is to expose Zone explicitly from the model as a role in its own right. This is possible because Zone is a Q_GADGET so is itself a type that interoperates with QML. The only thing you need to do if you aren't doing so already is to have a line like this in the header where you define Zone:

                        Q_DECLARE_METATYPE(Zone)
                        

                        This allows a Zone to be stored in a QVariant.

                        With this approach your code might be more like this:

                        class ZoneModel : public QAbstractListModel
                        {
                            Q_OBJECT
                        public:
                            ZoneModel (QObject* parent);
                        
                            enum MyRoles {
                               ZoneRole = Qt::UserRole + 1,
                            };
                            ...
                            private:
                                QHash<int, QString> m_roles;
                        
                        ZoneModel ::ZoneModel (QObject* parent) {
                            // init roles
                            m_roles[ZoneRole] = "zone";
                            ...
                        }
                        
                        // Override QAbstractItemModel function...
                        QHash<int, QByteArray> ZoneModel ::roleNames() const
                        {
                            return m_roles;
                        }
                        
                        // Override QAbstractItemModel function...
                        QVariant ZoneModel::data(const QModelIndex &index, int role) const 
                        {
                            // ... check preconditions etc as before ...
                        
                            switch (role) {
                            case ZoneRole:
                                return getZone(index.row());
                                ...
                        }
                        

                        And in the QML:

                        Repeater {
                            model: zoneModel
                            delegate: TabButton {
                                text: zone.name
                            }
                        }
                        

                        The official documentation is here:

                        https://doc.qt.io/qt-5/qtquick-modelviewsdata-cppmodels.html#qabstractitemmodel-subclass

                        mzimmersM 1 Reply Last reply
                        1
                        • B Bob64

                          @mzimmers sorry, there is more than one thing going on here. My original suggestion was to use the Repeater which you have indeed followed. But then you asked about the model exposure in QML.

                          The usual way to expose a QAbstractListModel to QML is to override the data function. This returns a QVariant so whatever you return needs to be storable in a QVariant.

                          If you have a model that has a number of properties at each index, let's say "name", "age", "occupation", probably the most natural way to expose this is to override roleNames() and data():

                          class PersonListModel : public QAbstractListModel
                          {
                              Q_OBJECT
                          public:
                              PersonListModel (QObject* parent);
                          
                              enum MyRoles {
                                  NameRole = Qt::UserRole + 1,
                                  AgeRole,
                                  OccupationRole
                              };
                              ...
                              private:
                                  QHash<int, QString> m_roles;
                          
                          PersonListModel ::PersonListModel (QObject* parent) {
                              // init roles
                              m_roles[NameRole] = "name";
                              m_roles[AgeRole] = "age";
                              m_roles[OccupationRole] = "occupation";
                              ...
                          }
                          
                          // Override QAbstractItemModel function...
                          QHash<int, QByteArray> PersonListModel ::roleNames() const
                          {
                              return m_roles;
                          }
                          
                          // Override QAbstractItemModel function...
                          QVariant PersonListModel::data(const QModelIndex &index, int role) const 
                          {
                              if (!index.isValid())
                                  return QVariant();
                          
                              // For illustration only, the below assumes your actual data is stored
                              // in something like a list of structs with `name`, `age` fields etc. 
                             // Obviously you can implement this in any way you like as long as you
                             // can take an index and return the requested value
                          
                              if (index.row() < 0 || index.row() >= m_myModelData.size())
                                  return QVariant();
                          
                              switch (role) {
                              case NameRole:
                                  return m_myModelData[index.row()].name;
                              case AgeRole:
                                  ...
                          }
                          

                          Now, the point about roleNames() in the above is that those strings that you associated with the enum values are what get exposed as property names on the QML side.

                          The QML does a lot of work for you given the above. In a delegate that references the model, you can directly refer to name, age etc., and under the hood, Qt will be calling data with the current index and the requested role ID and will be extracting the value from the returned QVariant into a QML-compatible type.

                          Relating this to your case, there are two approaches you could take. One is to "flatten" the model in a sense so that you don't need Zone as a type in this particular part of the QML code. So, what you might do is to define a ZoneNameRole mapped to "zoneName". You would implement your data() method something like this:

                                  case ZoneNameRole:
                                      return getZone(index.row()).name;
                                  ...
                          

                          Then in your QML, you could assign your button text simply as:

                          Repeater {
                              model: zoneModel
                              delegate: TabButton {
                                  text: zoneName
                              }
                          }
                          

                          The other option is to expose Zone explicitly from the model as a role in its own right. This is possible because Zone is a Q_GADGET so is itself a type that interoperates with QML. The only thing you need to do if you aren't doing so already is to have a line like this in the header where you define Zone:

                          Q_DECLARE_METATYPE(Zone)
                          

                          This allows a Zone to be stored in a QVariant.

                          With this approach your code might be more like this:

                          class ZoneModel : public QAbstractListModel
                          {
                              Q_OBJECT
                          public:
                              ZoneModel (QObject* parent);
                          
                              enum MyRoles {
                                 ZoneRole = Qt::UserRole + 1,
                              };
                              ...
                              private:
                                  QHash<int, QString> m_roles;
                          
                          ZoneModel ::ZoneModel (QObject* parent) {
                              // init roles
                              m_roles[ZoneRole] = "zone";
                              ...
                          }
                          
                          // Override QAbstractItemModel function...
                          QHash<int, QByteArray> ZoneModel ::roleNames() const
                          {
                              return m_roles;
                          }
                          
                          // Override QAbstractItemModel function...
                          QVariant ZoneModel::data(const QModelIndex &index, int role) const 
                          {
                              // ... check preconditions etc as before ...
                          
                              switch (role) {
                              case ZoneRole:
                                  return getZone(index.row());
                                  ...
                          }
                          

                          And in the QML:

                          Repeater {
                              model: zoneModel
                              delegate: TabButton {
                                  text: zone.name
                              }
                          }
                          

                          The official documentation is here:

                          https://doc.qt.io/qt-5/qtquick-modelviewsdata-cppmodels.html#qabstractitemmodel-subclass

                          mzimmersM Offline
                          mzimmersM Offline
                          mzimmers
                          wrote on last edited by
                          #11

                          @Bob64 first, thank you very much for the detailed answer. This very nicely corroborates and augments the Qt tutorial on this subject.

                          As it turns out, I had already reimplemented data(), setData() and roleNames() in my model. Everything was there; I just wasn't using it (properly) in my QML. This is my roleNames:

                          QHash<int, QByteArray> ZoneModel::roleNames() const
                          {
                              QHash<int, QByteArray> zones;
                              zones[NameRole] = "name";
                              ...
                              return zones;
                          }
                          

                          and my data() (error checking removed):

                          QVariant ZoneModel::data(const QModelIndex &index, int role) const
                          {
                              QVariant qv = QVariant();
                          
                              Zone item = m_list->zones().at(index.row());
                              switch (role) {
                              case NameRole:
                                  qv = item.name();
                                  break;
                              default:
                                  qv = QVariant();
                              }
                              return qv;
                          }
                          

                          (The additional level of indirection is that my actual list is defined in its own class (not the model class); again, this is from the tutorial.

                          So, all I had to do in my QML is:

                          Repeater {
                              model: zoneModel
                              delegate: TabButton {
                                  text: name
                              }
                          }
                          

                          Very easy, once one understands all the pieces of the model and how they work together.

                          Thank you again for the great explanation.

                          1 Reply Last reply
                          0
                          • mzimmersM mzimmers has marked this topic as solved on

                          • Login

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