c++qtqtableviewqabstracttablemodelqstyleditemdelegate

How to handle custom data show in QTableView?


I have a QTableView with custom model, delegate and data class. I want to show different widgets in my table based on data types. this is done in my delegate class. background and checkbox types are handled in show method of my model class. this is the tricky part! if i return value of my data in DisplayRole case of my show method in my model, i can't access my data class in my delegate class to use it's types (return QVariant::fromValue(mData[column]); => qvariant_cast(index.data())). This way nothing shows next to my checkbox because i returned my data class and not a string. this also happens to value type of my data class. how to handle this situation? should i move my checkbox to delegate class and add it there and add text value next to it(if yes, how to handle simple text column?)?

this is a sample which all of my values are 0 but nothing shows in checkbox column(i explained above why this happens) no value for checkbox

custom data:

class Data
{
public:
    enum Colors { WHITE, RED, BLUE };
    enum Types { VALUE, PICTURE, COMBO, COLOR, CHECKBOX, BUTTON };
    Colors mColor;
    Types mType;
    bool isChecked;
    double mValue;
    Data() = default;
    Data(Colors color, Types type);

    QString getValue() const;
    QColor getColor() const;
    Types getType() const
};

custom model data method:

QVariant DataModel::data(const QModelIndex &index, int role) const
{
    int column = index.column();
    switch (role) {
    case Qt::DisplayRole:
        return QVariant::fromValue(mData[column]);
        break;
    case Qt::BackgroundColorRole:
        return mData[column].getColor();
        break;
    case Qt::CheckStateRole:
        if (mData[column].getType() == Data::Types::CHECKBOX) {
            if (mData[column].isChecked) {
                return Qt::Checked;
            } else
                return Qt::Unchecked;
            break;
        }
    }

    return  QVariant();
}

custom delegate paint method:

void CustomDelegate::paint(QPainter *painter,
                          const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    
        if (index.data().canConvert<Data>()) {

            Data data = qvariant_cast<Data>(index.data());
            switch (data.getType())
            {
            case Data::Types::COMBO : {
                QStyleOptionComboBox box;

                QString text = data.getValue();

                box.currentText = text;
                box.rect = option.rect;

                QComboBox tmp;
                QApplication::style()->drawComplexControl(QStyle::CC_ComboBox, &box, painter, &tmp);
                QApplication::style()->drawControl(QStyle::CE_ComboBoxLabel, &box, painter, 0);
                break;
            }
            case Data::Types::BUTTON : {

                QString text = data.getValue();

                QStyleOptionViewItem opt = option;
                initStyleOption(&opt, index);

                QApplication::style()->drawControl(QStyle::CE_ItemViewItem, &opt, painter);

                QStyleOptionButton buttonOption;
                buttonOption.text = text; //use emoji for text, optionally you can use icon + iconSize
                QRect rect = option.rect;
                rect = rect.marginsRemoved(QMargins(3, 3, 3, 3));
                buttonOption.rect = rect;//QRect(option.rect.left()+option.rect.width()-(2*option.rect.height()),option.rect.top(),option.rect.height(),option.rect.height());


                QApplication::style()->drawControl(QStyle::CE_PushButton, &buttonOption, painter);


                break;
            }
            case Data::Types::CHECKBOX: {
                QString text = data.getValue();
                DataModel *model= static_cast<DataModel>(index.model());
     
                model->setData(index, QVariant::fromValue(text));
                QStyledItemDelegate::paint(painter, option, index);
                break;
            }
            case Data::Types::PICTURE:
            case Data::Types::COLOR:
            case Data::Types::VALUE:
                QStyledItemDelegate::paint(painter, option, index);
                break;

            }
        } else {
            QStyledItemDelegate::paint(painter, option, index);
        }

    
}

I tried

case Qt::DisplayRole:
        return QVariant::fromValue(mData[column].getValue());
        break;

but it breaks my delegate class in this line

if (index.data().canConvert<Data>()) {

it is always returns false because index.data() returns getvalue from above which is a string!


Solution

  • The easiest approach would be for your model to return Data::Types values in a role different from Qt::EditRole/Qt::DisplayRole/Qt::CheckStateRole. You are free to use roles from Qt::UserRole up for your own purpose.

    In your case, return mData[column].getType() in Qt::UserRole and have your delegate read values under that role so it knows how it should behave.

    The data method, with the above suggestion and some correction:

    QVariant DataModel::data(const QModelIndex &index, int role) const
    {
        int column = index.column();
        switch (role) {
        case Qt::DisplayRole:
            return mData[column].getValue();
        case Qt::BackgroundColorRole:
            return mData[column].getColor();
        case Qt::CheckStateRole:
            if (mData[column].getType() == Data::Types::CHECKBOX)
                return (mData[column].isChecked ? Qt::Checked : Qt::Unchecked);
        case Qt::UserRole:
            return mData[column].getType();
        }
        return  QVariant();
    }
    

    Then your delegate paint method should look like (mostly copied from your question but untested on my end):

    void CustomDelegate::paint(QPainter *painter,
                              const QStyleOptionViewItem &option, const QModelIndex &index) const
    {
        switch (index.data(Qt::UserRole).value<Data::Types>()) {
        case Data::Types::COMBO: {
            QStyleOptionComboBox box;
            QString text = index.data().toString();
            box.currentText = text;
            box.rect = option.rect;
    
            QComboBox tmp;
            QApplication::style()->drawComplexControl(QStyle::CC_ComboBox, &box, painter, &tmp);
            QApplication::style()->drawControl(QStyle::CE_ComboBoxLabel, &box, painter, 0);
             break;
        }
        case Data::Types::BUTTON: {
            QString text = index.data().toString();
            QStyleOptionViewItem opt = option;
            initStyleOption(&opt, index);
    
            QApplication::style()->drawControl(QStyle::CE_ItemViewItem, &opt, painter);
            QStyleOptionButton buttonOption;
            buttonOption.text = text; //use emoji for text, optionally you can use icon + iconSize
            QRect rect = option.rect;
            rect = rect.marginsRemoved(QMargins(3, 3, 3, 3));
            buttonOption.rect = rect;
            /*buttonOption.rect = QRect(
                  option.rect.left() + option.rect.width() - (option.rect.height() << 1),
                  option.rect.top(),
                  option.rect.height(),
                  option.rect.height()
            );*/
            QApplication::style()->drawControl(QStyle::CE_PushButton, &buttonOption, painter);
            break;
        }
        default: 
            QStyledItemDelegate::paint(painter, option, index);
        }
    }