如何支持 Qt 5.12 和 Qt Quick Controls 2 的 TableView 中的行选择?

How to support row selection in a TableView for Qt 5.12 and Qt Quick Controls 2?

我有一个简单的示例项目 here 来演示问题。

我在下面包含了我认为是相关来源的内容,但其余部分在上面的项目 link 中可用,或者我可以编辑并包含更多有用的内容。

我正在查看 TableView 文档 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,我看到一些有关 Qt Quick Controls 1 行选择的文档,但这也不适用于我的情况。

对于 Qt 5.12 和 Qt Quick Controls 2,我找不到合适的文档。

如何为我的案例支持 TableView 中的行选择?如何找到适合我的情况的正确文档?

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 { }
  }
}

我能够自己滚动 selection。所需的逻辑很简单,因为我只需要支持单行的 selection。

我的 ModelItem 看起来像:

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;
    }

};

这里的关键点是 selected 属性 来保存项目是否 selected 和自定义选择角色的定义。

我的自定义角色需要一个 roleNames 函数

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" }
  };
}

这里的关键是我使用字符串“selected”来指代我的自定义选择角色。

我的数据函数如下所示:

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;
}

这里的关键是检查角色是否是自定义选择角色,然后 return 根据 modelItem 中 selected 的值选择颜色。

最后一步是让 QML 使用这个自定义角色:

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 );
        }
      }
    }
  }
}

这里的关键是每个 DelegateChoice 的代表的 Rectangle 的 color: selected 部分。 selected指的是我在上面roleNames函数中设置的字符串selected。系统知道使用正确的 ModelItem::Role 调用数据函数,因此 if ( role == int( ModelItem::Role::Selection ) ) 解析为 true.

对于每个 DelegateChoice,我为调用模型中 select 函数的单元格定义了一个 MouseArea。 select函数是:

void
ModelList::
select( int index )
{
  beginResetModel();

  for ( int x = 0; x < this->mList.length(); x++ )
  {
    this->mList[x].selected = ( x == index );
  }

  endResetModel();
}

begin/endResetModel 导致 table 在 selection 改变时重新绘制。

示例项目已更新。

欢迎提出对此解决方案的改进建议。