Reputation: 1926
I have implemented PushButtonDelegate
for a column in QTableView
since we are using QSortFilterProxyModel
to filter the table view.
I am setting this delegate to one of the columns as shown below...
table_->setItemDelegateForColumn(11, new PushButtonDelegate(table_));
The PushButtonDelegate
is derived from QStyledItemDelegate
and I have overridden createEditor, setEditorData, setModelData, updateEditorGeometry, and paint
methods as shown below...
PushButtonDelegate::PushButtonDelegate(QObject* parent) :QStyledItemDelegate(parent)
{
if (const auto table_view = qobject_cast<QTableView*>(parent))
{
my_widget_ = table_view;
btn_ = new QPushButton(button_text_, my_widget_);
btn_->hide();
//my_widget_->setMouseTracking(true);
connect(my_widget_, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(CellEntered(QModelIndex)));
is_one_cell_in_edit_mode_ = false;
}
}
QWidget* PushButtonDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
if (index.model()->headerData(index.column(), Qt::Horizontal, Qt::DisplayRole).toString() == constants::kUse)
{
const auto btn = new QPushButton(parent);
const auto value = index.data().toString();
btn->setCheckable(true);
btn->setChecked(value == "Yes" ? true : false);
btn->setFocusPolicy(Qt::NoFocus);
connect(btn, SIGNAL(clicked(bool)), this, SLOT(UpdateUseButton(bool)));
return btn;
}
else
{
return QStyledItemDelegate::createEditor(parent, option, index);
}
}
void PushButtonDelegate::UpdateUseButton(bool checked) const
{
if (const auto selection_button = dynamic_cast<QPushButton*>(sender()))
{
const QString value = selection_button->text();
selection_button->setText(value == "Yes" ? "No" : "Yes");
selection_button->setChecked(value == "Yes" ? false : true);
selection_button->setCheckable(true);
}
}
void PushButtonDelegate::setEditorData(QWidget* editor, const QModelIndex& index) const
{
if (index.model()->headerData(index.column(), Qt::Horizontal, Qt::DisplayRole).toString() == constants::kUse)
{
const auto value = index.model()->data(index, Qt::EditRole).toString();
const auto pb = dynamic_cast<QPushButton*>(editor);
pb->setChecked(value == "Yes" ? true : false);
pb->setText(value);
pb->setCheckable(true);
}
else
{
QStyledItemDelegate::setEditorData(editor, index);
}
}
void PushButtonDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const
{
if (index.model()->headerData(index.column(), Qt::Horizontal, Qt::DisplayRole).toString() == constants::kUse)
{
const auto pb = dynamic_cast<QPushButton*>(editor);
const auto value = pb->text();
pb->setChecked(value == "Yes" ? true : false);
pb->setCheckable(true);
const auto ptr = dynamic_cast<QSortFilterProxyModel*>(model);
ptr->setData(index, value, Qt::EditRole);
}
else
{
QStyledItemDelegate::setModelData(editor, model, index);
}
}
void PushButtonDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
if (index.model()->headerData(index.column(), Qt::Horizontal, Qt::DisplayRole).toString() == constants::kUse)
{
painter->save();
btn_->setGeometry(option.rect);
const auto value = index.data().toString();
btn_->setCheckable(true);
btn_->setText(value);
btn_->setChecked(value == "Yes" ? true : false);
if (value == "Yes")
painter->fillRect(option.rect, option.palette.highlight());
else
painter->fillRect(option.rect, option.palette.shadow());
const QPixmap map = btn_->grab();
painter->drawPixmap(option.rect.x(), option.rect.y(), map);
painter->restore();
}
else
{
QStyledItemDelegate::paint(painter, option, index);
}
}
void PushButtonDelegate::updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
editor->setGeometry(option.rect);
}
I am facing the following issues...
yes
)...Need help...
Upvotes: 3
Views: 463
Reputation: 3823
The reason for having to click a cell 3 times to change the value is because you need a double click to edit (which creates the editor) and another click to call UpdateUseButton
. You don't need to do it this way, you can instead perform the click when you create your editor. In fact, you don't even need the UpdateUseButton
as you can make the necessary changes in setModelData
. Add the following slot to your PushButtonDelegate
:
void commitAndCloseEditor();
and then implement the class like this:
QWidget* PushButtonDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
if (index.model()->headerData(index.column(), Qt::Horizontal, Qt::DisplayRole).toString() == constants::kUse)
{
const auto btn = new QPushButton(parent);
const auto value = index.data().toString();
btn->setCheckable(true);
btn->setChecked(value == "Yes" ? true : false);
btn->setFocusPolicy(Qt::NoFocus);
// You don't need this, you can update your data in setModelData instead
// connect(btn, SIGNAL(clicked(bool)), this, SLOT(UpdateUseButton(bool)));
// the click will commit the data and close the editor
// we connect it with a queued connection so that the slot is called after this function exits
connect(btn, &QPushButton::clicked, this, &PushButtonDelegate::commitAndCloseEditor, Qt::QueuedConnection);
emit btn->clicked(false); // perform a click programatically to that the commitAndCloseEditor will be called after we exit this function
return btn;
}
else
{
return QStyledItemDelegate::createEditor(parent, option, index);
}
}
void PushButtonDelegate::setEditorData(QWidget* editor, const QModelIndex& index) const
{
if (index.model()->headerData(index.column(), Qt::Horizontal, Qt::DisplayRole).toString() == constants::kUse)
{
//nothing to do when you are in your delegate column
}
else
{
QStyledItemDelegate::setEditorData(editor, index);
}
}
void PushButtonDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const
{
if (index.model()->headerData(index.column(), Qt::Horizontal, Qt::DisplayRole).toString() == constants::kUse)
{
// set your model data here
model->setData(index, (index.data().toString() == "Yes") ? "No" : "Yes");
}
else
{
QStyledItemDelegate::setModelData(editor, model, index);
}
}
void PushButtonDelegate::commitAndCloseEditor()
{
auto* editor = qobject_cast<QWidget*>(sender());
emit commitData(editor);
emit closeEditor(editor);
}
Notice that I've used a Qt::QueuedConnnection
for connecting the clicked
signal. This ensures that the commitAndCloseEditor
slot will be called after both createEditor
and setEditorData
have exited (easy to check by using print statements in the beginning of each function). If you remove the queued connection, then the commitAndCloseEditor
will be called before createEditor
returns the actual editor, hence the slot will do nothing as the editor is not open yet.
Now, for the paint
method... it would seem that btn_->setChecked
is the reason for not properly showing the background color in the buttons. I'm not sure why it behaves like that, but a workaround would be to use stylesheets like so:
void PushButtonDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
if (index.model()->headerData(index.column(), Qt::Horizontal, Qt::DisplayRole).toString() == constants::kUse)
{
painter->save();
btn_->setGeometry(option.rect);
const auto value = index.data().toString();
btn_->setText(value);
QString stylesheet = QString("background-color: %1").arg((value == "Yes") ? "green" : "red" );
btn_->setStyleSheet(stylesheet);
const QPixmap map = btn_->grab();
painter->drawPixmap(option.rect.x(), option.rect.y(), map);
painter->restore();
}
else
{
QStyledItemDelegate::paint(painter, option, index);
}
}
Toggling the value should now work with a double click and the result (on my Windows box) looks like this:
Upvotes: 2