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