如何在 Qt QML TableView 单元格中绘制圆圈?

How do I draw a circle in a Qt QML TableView cell?

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

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

根据一些研究,当列为 1 时,我似乎需要在我的数据函数中使用 Qt::DecorationRole 和 return 图像。但是,那部分代码永远不会执行。我遗漏了一些关于角色概念如何与 Qt QML TableView 一起工作的重要且明显的内容。

我需要更改什么才能在第 1 列(平均年龄)中画一个圆圈?我希望这个圆圈在 age < 13 时为红色,在 < 35 时为黄色,否则为绿色。

main.qml

import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12
import Qt.labs.qmlmodels 1.0

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: DelegateChooser {
//      role: "type"

      DelegateChoice {
        roleValue: "decoration"

        Rectangle
        {
          color: 'red'

          anchors.fill: parent
        }
      }

      DelegateChoice {
//        roleValue: "display"

        Rectangle
        {
          Text
          {
            text: display
            anchors.fill: parent
            anchors.margins: 10
            color: 'black'
            font.pixelSize: 15
            verticalAlignment: Text.AlignVCenter
          }
        }
      }

    }

//        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 )
{
//  mRoleNames = QAbstractTableModel::roleNames();
//  mRoleNames.insert( 1, QByteArray( "type" ) );

}



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

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

QHash<int, QByteArray>
ModelList::
roleNames() const
{
  return {
    { Qt::DisplayRole, "display" },
    { Qt::DecorationRole, "decorations" }
  };

//  return this->mRoleNames;
}



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

我已经能够在 averageAge 字段中绘制正确的圆圈。

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

};

这里的重点是 ColumnType 和 ColorValue 角色的定义。

我的自定义角色需要一个 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" }
  };
}

自定义角色需要由 roleNames 提供并指定字符串“type”和“colorValue”。

我的数据函数如下所示:

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

这里的一个关键点是,当使用角色 ColumnType 时,我 return 该列是 stringValue 还是 colorValue。

此外,当使用角色 ColorValue 时,我会查看 modelItem 的 averageAge 和 return 包含要使用的颜色的字符串。

最后一步是让 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 );
        }
      }
    }
  }
}

首先,对于 DelegateChooser,角色由我们自定义的“类型”角色指定。系统知道用这个角色调用我们的数据函数。当数据函数returns "colorValue"时,第一个DelegateChoice被选中,因为roleValue是"colorValue"。第二个 DelegateChoice 没有 roleValue,因为它似乎需要一个默认的 DelegateChoice,而“stringValue”是默认值。

其次,“colorValue”委托选择已定义color: colorValue。这会导致系统再次调用具有 ColorValue 角色的数据函数,然后 return 为单元格设置正确的颜色。

示例项目已更新。

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