1

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.

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?

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 { }
  }
}
James Hudson
  • 844
  • 6
  • 19
  • I believe TableView in QtQuick 2 was a complete re-write compared to that from QtQuick.Controls 1 due to performance reasons. From your question, as well as the documentation provided, I don't see anything that indicates that TableView 2 actually includes this functionality (rather, you can implement it yourself). Aside from it appearing in the TableView 1 API, do you have some reason to believe this? – fallerd Jun 10 '21 at 17:47
  • How do I implement it myself? Is there any example code that would demonstrate how to do this? I have looked, but cannot find anything. – James Hudson Jun 10 '21 at 18:02
  • "Selection" is an arbitrary concept, so how something becomes selected would be up to you. A simple example: add a "selected" flag to your model entries and then toggle it via interaction - each delegate could contain a MouseArea that, when clicked, will do something like `model.selected = !model.selected`. You would need to generate any further logic for more complex selection of course. Related questions: https://stackoverflow.com/questions/65733392/how-to-select-a-row-in-tableview-qml https://stackoverflow.com/questions/62633171/qml-tableview-get-row-clicked-with-qtquick-controls-2 – fallerd Jun 10 '21 at 18:55
  • It would appear I would need to also understand how roles work and make use of Qt::DecorationRole somehow to indicate the row was selected. However, how they work is also eluding me ( https://stackoverflow.com/questions/67895259/how-do-i-draw-a-circle-in-a-qt-qml-tableview-cell ). I only need to support the selection of a single row at a time. – James Hudson Jun 10 '21 at 19:02

1 Answers1

1

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.

James Hudson
  • 844
  • 6
  • 19