Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. QML and Qt Quick
  4. Row Selection and Image Cells with QML TableView & Qt 5.12.11 & Qt Quick Controls 2

Row Selection and Image Cells with QML TableView & Qt 5.12.11 & Qt Quick Controls 2

Scheduled Pinned Locked Moved Unsolved QML and Qt Quick
3 Posts 1 Posters 1.1k 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.
  • J Offline
    J Offline
    james_h_3010
    wrote on last edited by
    #1

    I am having some trouble figuring how to to use the TableView for my situation.

    I have a simple sample project here which demonstrate the problem.

    I've included below what I believe is the relevant source, but the remainder is available in the project link above or I can edit and include more if useful.

    Row Selection

    I am looking at the TableView documentation here. I do not see any mention of how to support row selection. If I look here, I see documentation for 5.15, where row selection is described. And, if I look here, I see some documentation for row selection for Qt Quick Controls 1, but that also does not apply to my situation.

    For Qt 5.12 and Qt Quick Controls 2, I am having trouble locating the appropriate documentation.
    How do I support row selection in a TableView for my case? How can I find the correct documentation for my situation?

    Image Cells

    Based on some research, it appears that I need to use the Qt::DecorationRole in my data function and return an image when the column is 1. However, that part of the code is never executed. I am missing some important and obvious about how the role concept works with Qt QML TableView's.

    What do I need to change so I can draw a circle in Column 1 (average age)? I'd like this circle to be red if the age < 13, yellow if < 35, and green otherwise.

    main.qml

    import QtQuick 2.12
    import QtQuick.Controls 2.12
    import QtQuick.Layouts 1.12
    
    import Backend 1.0
    
    ApplicationWindow
    {
      id:      root
      visible: true
    
      width:  768
      height: 450
    
      minimumWidth:  768
      minimumHeight: 450
    
      property string backendReference: Backend.objectName
    
      TableView
      {
        id: tableView
    
        columnWidthProvider: function( column )
        {
          return 100;
        }
    
        rowHeightProvider: function( column )
        {
          return 23;
        }
    
        anchors.fill: parent
        topMargin:    columnsHeader.implicitHeight
    
        model: Backend.modelResults.list
    
        ScrollBar.horizontal: ScrollBar {}
        ScrollBar.vertical:   ScrollBar {}
    
        clip: true
    
        delegate: Rectangle
        {
          Text
          {
            text: display
            anchors.fill: parent
            anchors.margins: 10
            color: 'black'
            font.pixelSize: 15
            verticalAlignment: Text.AlignVCenter
          }
        }
    
        Rectangle // mask the headers
        {
          z: 3
    
          color: "#222222"
    
          y: tableView.contentY
          x: tableView.contentX
    
          width:  tableView.leftMargin
          height: tableView.topMargin
        }
    
        Row
        {
          id: columnsHeader
          y:  tableView.contentY
    
          z: 2
    
          Repeater
          {
            model: tableView.columns > 0 ? tableView.columns : 1
    
            Label
            {
              width:  tableView.columnWidthProvider(modelData)
              height: 35
    
              text: Backend.modelResults.list.headerData( modelData, Qt.Horizontal )
    
              font.pixelSize:    15
              padding:           10
              verticalAlignment: Text.AlignVCenter
    
              background: Rectangle
              {
                color: "#eeeeee"
              }
            }
          }
        }
    
        ScrollIndicator.horizontal: ScrollIndicator { }
        ScrollIndicator.vertical: ScrollIndicator { }
      }
    }
    

    modeldata.cpp

    #include "modeldata.h"
    
    //
    // ModelList
    //
    ModelList::
    ModelList( QObject* parent )
        : QAbstractTableModel (parent )
    {
    }
    
    
    
    int
    ModelList::
    rowCount(const QModelIndex &) const
    {
       int size = mList.size();
    
       return size;
    }
    
    
    
    int
    ModelList::
    columnCount( const QModelIndex & ) const
    {
       return 2;
    }
    
    
    
    
    QVariant
    ModelList::
    data( const QModelIndex& index, int role ) const
    {
        const ModelItem modelItem = mList.at( index.row() );
    
        QVariant result = QVariant();
    
        if ( role == Qt::DisplayRole )
        {
            if ( index.column() == 0 )
            {
              result = QVariant( QString( modelItem.population ) );
            }
            else
            {
              result = QVariant( QString::number( modelItem.averageAge ) );
            }
        }
        else if ( role == Qt::DecorationRole )
        {
            qDebug() << "decorate 1";
        }
    
        return result;
    }
    
    
    
    QVariant
    ModelList::
    headerData( int section, Qt::Orientation orientation, int role ) const
    {
        if ( section == 0 )
            return QVariant( QString( "Population" ) );
        else
            return QVariant( QString( "Average Age" ) );
    }
    
    
    
    int
    ModelList::
    size() const
    {
        return mList.size();
    }
    
    
    
    const QList<ModelItem>&
    ModelList::
    list() const
    {
        return mList;
    }
    
    
    
    void
    ModelList::
    removeAt( int index )
    {
        if ( index < 0 || index >= mList.size() )
            return;
    
        beginRemoveRows( QModelIndex(), index, index );
        mList.removeAt( index );
        endRemoveRows();
    
        emit sizeChanged();
    }
    
    
    
    void
    ModelList::
    add( const QString& population, const int averageAge )
    {
        ModelItem item;
    
        item.population = population;
        item.averageAge = averageAge;
    
        add( item );
    }
    
    
    
    void
    ModelList::
    add(const ModelItem& item)
    {
        const int index = mList.size();
    
        beginInsertRows( QModelIndex(), index, index );
        mList.append( item );
        endInsertRows();
    
        emit sizeChanged();
    }
    
    
    
    void
    ModelList::
    reset()
    {
        if ( mList.isEmpty() )
            return;
    
        beginRemoveRows( QModelIndex(), 0, mList.size() - 1 );
        mList.clear();
        endRemoveRows();
    
        emit sizeChanged();
    }
    
    
    
    //
    // ModelResults
    //
    ModelResults::ModelResults(QObject* parent)
        : QObject(parent)
    {
        mList = new ModelList( this );
    
        qRegisterMetaType<ModelItem>("ModelItem");
    }
    
    ModelList* ModelResults::list() const
    {
        return mList;
    }
    
    void ModelResults::reset()
    {
        mList->reset();
    }
    
    1 Reply Last reply
    0
    • J Offline
      J Offline
      james_h_3010
      wrote on last edited by
      #2

      I have been able to get the correct circle drawn in the averageAge field.

      My ModelItem looks like:

      struct ModelItem
      {
          Q_GADGET
      
          Q_PROPERTY( QString population MEMBER population )
          Q_PROPERTY( int averageAge MEMBER averageAge )
          Q_PROPERTY( bool selected MEMBER selected )
      
      public:
      
          enum class Role {
            Selection = Qt::UserRole,
            ColumnType,
            ColorValue
          };
          Q_ENUM(Role)
      
          QString population;
          int     averageAge;
          bool    selected    { false };
      
          bool operator!=( const ModelItem& other )
          {
              return other.population != this->population
                  || other.averageAge != this->averageAge;
          }
      
      };
      

      The key point here is the definition of the ColumnType and ColorValue Role.

      I needed a roleNames function for my custom role

      QHash<int, QByteArray>
      ModelList::
      roleNames() const
      {
        return {
          { Qt::DisplayRole, "display" },
          { int( ModelItem::Role::Selection ), "selected" },
          { int( ModelItem::Role::ColumnType ), "type" },
          { int( ModelItem::Role::ColorValue ), "colorValue" }
        };
      }
      

      The custom roles needed to be supplied by roleNames and have the strings "type" and "colorValue" specified.

      My data function looks like:

      QVariant
      ModelList::
      data( const QModelIndex& index, int role ) const
      {
          const ModelItem modelItem = mList.at( index.row() );
      
          QVariant result = QVariant();
      
          if ( role == Qt::DisplayRole )
          {
              if ( index.column() == 0 )
              {
                result = QVariant( QString( modelItem.population ) );
              }
              else
              {
                result = QVariant( QString::number( modelItem.averageAge ) );
              }
          }
      
          if ( role == int( ModelItem::Role::Selection ) )
          {
              result = QVariant( QString( modelItem.selected ? "#eeeeee" : "white" ) );
          }
      
          if ( role == int( ModelItem::Role::ColumnType ) )
          {
            if ( index.column() == 0 )
              result = QVariant( QString( "stringValue" ) );
            else
              result = QVariant( QString( "colorValue" ) );
          }
      
          if ( role == int( ModelItem::Role::ColorValue ) )
          {
            QString color;
      
            if ( modelItem.averageAge < 13 )
              color = "red";
            else if ( modelItem.averageAge < 35 )
              color = "yellow";
            else
              color = "green";
      
            result = QVariant( color );
          }
      
          qDebug() << role << " " << result;
      
          return result;
      }
      

      A key point here is that when the role ColumnType is used, I return whether or not the column is a stringValue or a colorValue.

      Additionally, when the role ColorValue is used, I look at the averageAge of the modelItem and return a string containing the color to be used.

      The final piece is to have the QML the custom roles.

      delegate: DelegateChooser
      {
        role: "type"
      
        DelegateChoice
        {
          roleValue: "colorValue"
      
          delegate: Rectangle
          {
            color: selected
      
            Rectangle
            {
              color: colorValue
      
              width: parent.height
              height: parent.height
      
              radius: width * 0.5;
      
              anchors.horizontalCenter: parent.horizontalCenter;
            }
      
            MouseArea
            {
              anchors.fill: parent
      
              onClicked:
              {
                var idx = Backend.modelResults.list.index( row, column )
      
                console.log( "Clicked cell: ", idx.row, " ", Backend.modelResults.list.data( idx ) )
      
                Backend.modelResults.list.select( idx.row );
              }
            }
          }
        }
      
        DelegateChoice
        {
          delegate: Rectangle
          {
            color: selected
      
            Text
            {
              text: display
              anchors.fill: parent
              anchors.margins: 10
              color: 'black'
              font.pixelSize: 15
              verticalAlignment: Text.AlignVCenter
            }
      
            MouseArea
            {
              anchors.fill: parent
      
              onClicked:
              {
                var idx = Backend.modelResults.list.index( row, column )
      
                console.log( "Clicked cell: ", idx.row, " ", Backend.modelResults.list.data( idx ) )
      
                Backend.modelResults.list.select( idx.row );
              }
            }
          }
        }
      }
      

      First, for the DelegateChooser, the role is specified by our custom "type" role. The system knows to call our data function with this role. When the data function returns "colorValue", the first DelegateChoice is selected based because the roleValue is "colorValue". The second DelegateChoice does not have a roleValue because it appears there needs to be a default DelegateChoice and "stringValue" is the default.

      Second, the "colorValue" delegate choice has defined color: colorValue. This causes the system to again call the data function with the ColorValue role and it then returns the correct color for the cell.

      The example project has been updated.

      Suggested improvement to this solution are welcome.

      1 Reply Last reply
      0
      • J Offline
        J Offline
        james_h_3010
        wrote on last edited by
        #3

        I was able to roll my own selection. The logic needed was simple since I only needed to support the selection of a single row.

        My ModelItem looks like:

        struct ModelItem
        {
            Q_GADGET
        
            Q_PROPERTY( QString population MEMBER population )
            Q_PROPERTY( int averageAge MEMBER averageAge )
            Q_PROPERTY( bool selected MEMBER selected )
        
        public:
        
            enum class Role {
              Selection = Qt::UserRole,
              ColumnType,
              ColorValue
            };
            Q_ENUM(Role)
        
            QString population;
            int     averageAge;
            bool    selected    { false };
        
            bool operator!=( const ModelItem& other )
            {
                return other.population != this->population
                    || other.averageAge != this->averageAge;
            }
        
        };
        

        The key points here are the selected property to hold whether or not an item is selected and the definition of the custom Selection role.

        I needed a roleNames function for my custom role

        QHash<int, QByteArray>
        ModelList::
        roleNames() const
        {
          return {
            { Qt::DisplayRole, "display" },
            { int( ModelItem::Role::Selection ), "selected" },
            { int( ModelItem::Role::ColumnType ), "type" },
            { int( ModelItem::Role::ColorValue ), "colorValue" }
          };
        }
        

        The key here is that I use the string "selected" to refer to my custom Selection role.

        My data function looks like:

        QVariant
        ModelList::
        data( const QModelIndex& index, int role ) const
        {
            const ModelItem modelItem = mList.at( index.row() );
        
            QVariant result = QVariant();
        
            if ( role == Qt::DisplayRole )
            {
                if ( index.column() == 0 )
                {
                  result = QVariant( QString( modelItem.population ) );
                }
                else
                {
                  result = QVariant( QString::number( modelItem.averageAge ) );
                }
            }
        
            if ( role == int( ModelItem::Role::Selection ) )
            {
                result = QVariant( QString( modelItem.selected ? "#eeeeee" : "white" ) );
            }
        
            if ( role == int( ModelItem::Role::ColumnType ) )
            {
              if ( index.column() == 0 )
                result = QVariant( QString( "stringValue" ) );
              else
                result = QVariant( QString( "colorValue" ) );
            }
        
            if ( role == int( ModelItem::Role::ColorValue ) )
            {
              QString color;
        
              if ( modelItem.averageAge < 13 )
                color = "red";
              else if ( modelItem.averageAge < 35 )
                color = "yellow";
              else
                color = "green";
        
              result = QVariant( color );
            }
        
            qDebug() << role << " " << result;
        
            return result;
        }
        

        The key here is to check to see if the role is the custom Selection role and then return a color based on the value of selected in the modelItem.

        The final piece is to have the QML use this custom role:

        delegate: DelegateChooser
        {
          role: "type"
        
          DelegateChoice
          {
            roleValue: "colorValue"
        
            delegate: Rectangle
            {
              color: selected
        
              Rectangle
              {
                color: colorValue
        
                width: parent.height
                height: parent.height
        
                radius: width * 0.5;
        
                anchors.horizontalCenter: parent.horizontalCenter;
              }
        
              MouseArea
              {
                anchors.fill: parent
        
                onClicked:
                {
                  var idx = Backend.modelResults.list.index( row, column )
        
                  console.log( "Clicked cell: ", idx.row, " ", Backend.modelResults.list.data( idx ) )
        
                  Backend.modelResults.list.select( idx.row );
                }
              }
            }
          }
        
          DelegateChoice
          {
            delegate: Rectangle
            {
              color: selected
        
              Text
              {
                text: display
                anchors.fill: parent
                anchors.margins: 10
                color: 'black'
                font.pixelSize: 15
                verticalAlignment: Text.AlignVCenter
              }
        
              MouseArea
              {
                anchors.fill: parent
        
                onClicked:
                {
                  var idx = Backend.modelResults.list.index( row, column )
        
                  console.log( "Clicked cell: ", idx.row, " ", Backend.modelResults.list.data( idx ) )
        
                  Backend.modelResults.list.select( idx.row );
                }
              }
            }
          }
        }
        

        The key here is the color: selected part of the Rectangle for the delegate of each DelegateChoice. selected refers to the string selected I setup in the roleNames function above. The system knows to call the data function with the correct ModelItem::Role so if ( role == int( ModelItem::Role::Selection ) ) resolves to true.

        For each DelegateChoice, I defined a MouseArea for the cell which calls the select function in the model. The select function is:

        void
        ModelList::
        select( int index )
        {
          beginResetModel();
        
          for ( int x = 0; x < this->mList.length(); x++ )
          {
            this->mList[x].selected = ( x == index );
          }
        
          endResetModel();
        }
        

        The begin/endResetModel causes the table to be redrawn when the selection changes.

        The example project has been updated.

        Suggested improvement to this solution are welcome.

        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