Remove CellWidget from QTableWidget when focus out - qt

I have a QTableWidget in which some cells have QComboBox as cellWidget. I want to create the comboBox only when the user clicks in the cell. For this, I have caught itemClicked signal and created the comboBox and set it as cellWidget.
Now, I want to delete this comboBox in two scenarios - if the user selects some value from the comboBox, or the user simply focusses out (click anywhere in the dialog).
For the first case, I have use the signal QComboBox::activated which is invoked when something is selected in the drop-down. There I delete the comboBox and it works fine.
However, I am not sure how to proceed for the second case (delete the comboBox when the user focusses out of the drop-down without selecting anything).
I tried capturing eventFilter for focusOut for the tablewidget as well as the comboBox, but it didn't help.

You can use QItemDelegate for this purpose:
comboboxdelegate.h
class ComboBoxDelegate : public QItemDelegate
{
Q_OBJECT
public:
ComboBoxDelegate(QObject *parent = nullptr) :
QItemDelegate(parent)
{
}
QWidget* createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QComboBox *editor = new QComboBox(parent);
editor->addItems(QStringList() << "1" << "2" << "3" << "4"); // fill it with your own values
return editor;
}
void setEditorData(QWidget *editor, const QModelIndex &index) const
{
QString text = index.model()->data(index, Qt::EditRole).toString();
QComboBox *cb = static_cast<QComboBox*>(editor);
cb->setCurrentText(text);
}
void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
QComboBox *cb = static_cast<QComboBox*>(editor);
QString text = cb->currentText();
model->setData(index, text, Qt::EditRole);
}
void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
editor->setGeometry(option.rect);
}
};
mainwindow.cpp
...
ui->tableWidget->setItemDelegate(new ComboBoxDelegate);
...

Related

Resizing a QTableView column horizontally to the content of an item delegated column which is painted with a new text in Qt

I want to show my database tabel content in a QTableView. I use the following codes to do that:
QSqlDatabase test = QSqlDatabase::addDatabase("QMYSQL");
test.setDatabaseName("dbText");
test.setHostName("localhost");
test.setUserName("***");
test.setPassword("***");
if (!test.open())
qDebug()<<"ERROR ";
QSqlTableModel *model = new QSqlTableModel(this,test);
model->setTable("textTable");
model->setEditStrategy(QSqlTableModel::OnManualSubmit);
model->select();
ui->tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
ui->tableView->setModel(model);
ui->tableView->show();
As it is depicted in the following picture, ui->tableView columns are resized to the content of the database table columns.
Now, I want to clear the display text of Description column and paint it with new text and color. For this propose, I have used the function QTableView::setItemDelegateForColumn as follow:
ui->tableView->setItemDelegateForColumn(2,new PowerColorDelegate(this));
And here are PowerColorDelegate header and source file content:
PowerColorDelegate.h
#include <QStyledItemDelegate>
class PowerColorDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
PowerColorDelegate(QObject *parent = 0);
protected:
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const;
QString displayText(const QVariant &value, const QLocale &locale) const;
};
PowerColorDelegate.cpp
#include <QApplication>
#include <QPainter>
#include "powercolordelegate.h"
PowerColorDelegate::PowerColorDelegate(QObject *parent) : QStyledItemDelegate(parent)
{
}
void PowerColorDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QStyleOptionViewItem opt = option;
initStyleOption(&opt, index);
if(!index.isValid())
{
QStyledItemDelegate::paint(painter, option, index);
return;
}
// Position our pixmap
const int x = option.rect.left();
const int y = option.rect.top();
QString newColor = "#fcaf9e";
QString newText = "This is my new text which I want to paint!";
painter->fillRect(option.rect, newColor);
painter->drawText(QRect(x, y, 80, 20), newText);
QStyledItemDelegate::paint(painter, opt, index);
}
QString PowerColorDelegate::displayText(const QVariant &value, const QLocale &locale) const
{
return "";
}
The result of using PowerColorDelegate on ui->tableView is shown in this picture:
How can I resize the third ui->tableView column (Description) horizontally to the content of the painted column?
Implement sizeHint for your delegate according to required text format

QStyledItemDelegate: how to make checkbox button to change its state on click

I have a delegate MyDelegate which is used for QListWidget. The delegate is derived from QStyledItemDelegate. One of the goals of MyDelegate is to place a checkbox button on each row of ListWidget. It is done within the paint() event of MyDelegate:
void MyDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QStyledItemDelegate::paint(painter, option, index);
// ... drawing other delegate elements
QStyleOptionButton checkbox;
// setting up checkbox's size and position
// now draw the checkbox
QApplication::style()->drawControl(QStyle::CE_CheckBox, &checkbox, painter);
}
At first I thought the checkbox would automatically change its state on click, since I specified QStyle::CE_CheckBox. But it is not the case. Looks like I have to specify the checkbox visual behavior manually.
Data-wise, When user clicks on that checkbox, certain signal is emitted and the scene data is changed. I perform this action in editorEvent():
bool MyDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index)
{
if (event->type() == QEvent::MouseButtonRelease) {
if (/* within checkbox area */)
emit this->clickedCheckbox(index);
}
}
The backend part works. However, I cannot figure out how to make the checkbox button to change its visual state from checked to unchecked, and backwards.
I realized that I can change the checkbox state manually by doing something like this from paint():
checkbox.state = someCondition? (QStyle::State_Enabled | QStyle::State_On) :
(QStyle::State_Enabled | QStyle::State_Off) ;
QStyle::State_On/Off does the trick of manual checkbox state change.
But I do not know how to set up that someCondition and where I should set it up. I tried to introduce it as a private bool variable which would be set in editorEvent() when the checkbox area gets a click, however, it does not produce the desired behavior: it sets all the other checkboxes of ListWidget to the same visual state. So, it behaved like some global condition for all the checkboxes.
I feel like, to accomplish my task, I have to re-implement the button and make it to change the checkbox state on click. But I'm lost on this way and not sure how to approach the problem. From the QStyleOptionButton API I do not see a clicked() or any other method I could use.
So, the question is: how do I make checkbox to behave like a checkbox, visually? If I need to re-implement a checkbox, then what class do I inherit?
You can set some value that describes your checkbox state in MyDelegate::editorEvent and then use it to paint a proper checkbox:
const int CHECK_ROLE = Qt::UserRole + 1;
bool MyDelegate::editorEvent(QEvent *event,
QAbstractItemModel *model,
const QStyleOptionViewItem &option,
const QModelIndex &index)
{
if (event->type() == QEvent::MouseButtonRelease)
{
bool value = index.data(CHECK_ROLE).toBool();
// invert checkbox state
model->setData(index, !value, CHECK_ROLE);
return true;
}
return QStyledItemDelegate::editorEvent(event, model, option, index);
}
void MyDelegate::paint(QPainter *painter,
const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
QStyleOptionButton cbOpt;
cbOpt.rect = option.rect;
bool isChecked = index.data(CHECK_ROLE).toBool();
if (isChecked)
{
cbOpt.state |= QStyle::State_On;
}
else
{
cbOpt.state |= QStyle::State_Off;
}
QApplication::style()->drawControl(QStyle::CE_CheckBox, &cbOpt, painter);
}
I added the following:
data(Qt:CheckStateRole) is toggled.
enable/disable the item.
center the box in the cell.
only toggle checkmark with left mouse button, and when checkmark is clicked (not the entire cell).
Here's the code:
// -------------------------------- //
class GFQtCheckboxItemDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
const int CHECK_ROLE = Qt::CheckStateRole;
// dont't let the default QStyledItemDelegate create the true/false combobox
QWidget* createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const Q_DECL_OVERRIDE
{
(void)parent;
(void)option;
(void)index;
return nullptr;
}
QRect GetCheckboxRect(const QStyleOptionViewItem &option)const
{
QStyleOptionButton opt_button;
opt_button.QStyleOption::operator=(option);
QRect sz = QApplication::style()->subElementRect(QStyle::SE_ViewItemCheckIndicator, &opt_button);
QRect r = option.rect;
// center 'sz' within 'r'
int dx = (r.width() - sz.width())/2;
int dy = (r.height()- sz.height())/2;
r.setTopLeft(r.topLeft() + QPoint(dx,dy));
r.setWidth(sz.width());
r.setHeight(sz.height());
return r;
}
// click event
bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index)
{
if (event->type() == QEvent::MouseButtonRelease)
{
QMouseEvent* pME = static_cast<QMouseEvent*>(event);
if(pME->button() == Qt::LeftButton)
{
QRect ro = GetCheckboxRect(option);
QPoint pte = pME->pos();
if(ro.contains(pte) )
{
bool value = index.data( CHECK_ROLE).toBool();
// invert checkbox state
model->setData(index, !value, CHECK_ROLE);
return true;
}
}
}
return QStyledItemDelegate::editorEvent(event, model, option, index);
}
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QStyleOptionButton cbOpt;
cbOpt.rect = GetCheckboxRect(option);
bool isChecked = index.data(CHECK_ROLE ).toBool();
if (isChecked)
{
cbOpt.state |= QStyle::State_On;
}
else
{
cbOpt.state |= QStyle::State_Off;
}
QVariant enabled = index.data(Qt::ItemIsEnabled);
if(enabled.isNull() || enabled.toBool() )
{
cbOpt.state |= QStyle::State_Enabled;
}
QApplication::style()->drawControl(QStyle::CE_CheckBox, &cbOpt, painter);
}
};
And for completeness, here's how to apply it:
m_p_QTableView->setItemDelegateForColumn(iTableColumnIndex, new GFQtCheckboxItemDelegate() );
...
bool yesno ...;
pGrid->setData(index, yesno, Qt::CheckStateRole);
qtqcheckboxqtableview
This is much easier if you change the QListWidget to a QListModel and a QListView.
Qt::ItemFlags ListModel::flags(const QModelIndex &index) const
{
return QAbstractListModel::flags(index) | Qt::ItemIsUserCheckable;
}
QVariant ListModel::data(const QModelIndex &index, int role) const
{
if (role == Qt::EditRole) {
return m_checkStateForRow[index.row()];
}
}
bool ListModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (role == Qt::CheckStateRole) {
m_checkStateForRow[index.row()] = value.toInt();
emit dataChanged(index, index, {role});
return true;
}
return false;
}

Access pointer to QWidget(Combobox) of the customdelegate

I have derived a class from QStyledItemDelegate. I am using a QComboBox in this delegate. This delegate is used in QTableView.
My question is, how can i change the index of the Combobox in the delegate programatically i.e how to access the pointer to that widget outside the delegate class ?
I have checked that CreateEditor, SetEditorData, SetModelData functions (of QStyledItemDelegate) are called automatically when we click on the combobox and we cannot call them manually to maniplate the data in the model.
afaik any time you start editing and the combobox is shown, it will allocate a new one. if you want to have a permanent combobox, you should look at
QTableView::setIndexWidget(const QModelIndex&, QWidget*)
so you could access the combobox with the following code:
const QMoodelIndex idx = model->index(row, column);
QWidget* wid = view->indexWidget(idx);
QComboBox* box = qobject_cast<QComboBox*>(wid);
if (box)
// do your thing
You can have the contents of your combobox as a class member of your delegate in a QStringList. Your item delegate can be like :
#include <QStyledItemDelegate>
#include <QComboBox>
class ComboBoxDelegate: public QStyledItemDelegate
{
Q_OBJECT
public:
ComboBoxDelegate(QObject *parent = 0);
QWidget *createEditor( QWidget *parent,
const QStyleOptionViewItem &option,
const QModelIndex &index ) const;
void setEditorData( QWidget *editor,
const QModelIndex &index ) const;
void setModelData( QWidget *editor,
QAbstractItemModel *model,
const QModelIndex &index ) const;
void updateEditorGeometry( QWidget *editor,
const QStyleOptionViewItem &option,
const QModelIndex &index ) const;
QStringList comboItems;
mutable QComboBox *combo;
private slots:
void setData(int val);
};
ComboBoxDelegate::ComboBoxDelegate(QObject *parent ):QStyledItemDelegate(parent)
{
}
QWidget *ComboBoxDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
combo = new QComboBox( parent );
QObject::connect(combo,SIGNAL(currentIndexChanged(int)),this,SLOT(setData(int)));
combo->addItems(comboItems);
combo->setMaxVisibleItems(comboItems.count());
return combo;
}
void ComboBoxDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
QString text = index.model()->data( index, Qt::DisplayRole ).toString();
int comboIndex = comboItems.indexOf(QRegExp(text));
if(comboIndex>=0)
(static_cast<QComboBox*>( editor ))->setCurrentIndex(comboIndex);
}
void ComboBoxDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
model->setData( index, static_cast<QComboBox*>( editor )->currentText() );
}
void ComboBoxDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
editor->setGeometry( option.rect );
}
void ComboBoxDelegate::setData(int val)
{
emit commitData(combo);
//emit closeEditor(combo);
}
When you want to update the items in combobox somewhere in your code just get a pointer to the item delegate by calling itemDelegateForColumn and access the comboItems member :
ComboBoxDelegate * itemDelegate = qobject_cast<ComboBoxDelegate *>(ui->tableView->itemDelegateForColumn(columnIndex));
//Updating combobox items
itemDelegate->comboItems.append("newItem");
...

Qt delegate not calling createEditor()

I have ComboBox filled with CheckBoxes when the ComboBox is opened the first item's delegate createEditor is not called but when you move to second item it is called and delegate is properly created. After this when you move back to first item then delegate is working. The problem is only with selecting first item for first time. If you have got only one item in comboBox this item is unselectable.
This is my delegate's code:
#include "checkboxlistdelegate.h"
CheckBoxListDelegate::CheckBoxListDelegate(QObject *parent)
: QItemDelegate(parent) {
}
void CheckBoxListDelegate::paint(QPainter *painter,
const QStyleOptionViewItem &option,
const QModelIndex &index) const {
//Get item data
bool value = index.data(Qt::UserRole).toBool();
QString text = index.data(Qt::DisplayRole).toString();
// fill style options with item data
const QStyle *style = QApplication::style();
QStyleOptionButton opt;
opt.state |= value ? QStyle::State_On : QStyle::State_Off;
opt.state |= QStyle::State_Enabled;
opt.text = text;
opt.rect = option.rect;
opt.palette = QPalette(Qt::white);
// draw item data as CheckBox
style->drawControl(QStyle::CE_CheckBox,&opt,painter);
}
QWidget* CheckBoxListDelegate::createEditor(QWidget *parent,
const QStyleOptionViewItem& option,
const QModelIndex & index ) const {
QCheckBox *editor = new QCheckBox(parent);
editor->setStyleSheet("QCheckBox {background-color: #aaaaaa; color: white;}");
return editor;
}
void CheckBoxListDelegate::setEditorData(QWidget *editor,
const QModelIndex &index) const {
QCheckBox *myEditor = static_cast<QCheckBox*>(editor);
myEditor->setText(index.data(Qt::DisplayRole).toString());
myEditor->setChecked(index.data(Qt::UserRole).toBool());
}
void CheckBoxListDelegate::setModelData(QWidget *editor,
QAbstractItemModel *model,
const QModelIndex &index) const {
//get the value from the editor (CheckBox)
QCheckBox *myEditor = static_cast<QCheckBox*>(editor);
bool value = myEditor->isChecked();
//set model data
QMap<int,QVariant> data;
data.insert(Qt::DisplayRole,myEditor->text());
data.insert(Qt::UserRole,value);
model->setItemData(index,data);
emit indexChanged(index);
}
void CheckBoxListDelegate::updateEditorGeometry(QWidget *editor,
const QStyleOptionViewItem &option,
const QModelIndex &index ) const {
Q_UNUSED(index);
editor->setGeometry(option.rect);
}
And this is code of my QComboBox subclass:
CheckBoxList::CheckBoxList(QWidget *widget)
: QComboBox(widget),title(""),selection() {
// set delegate items view
view()->setItemDelegate(new CheckBoxListDelegate(this));
view()->setStyleSheet("QAbstractItemView {background-color:white;}");
// Enable editing on items view
view()->setEditTriggers(QAbstractItemView::AllEditTriggers);
connect(view()->model(), SIGNAL(dataChanged(QModelIndex,QModelIndex)),
this, SLOT(onItemClicked(QModelIndex)));
}
void CheckBoxList::paintEvent(QPaintEvent *) {
QStylePainter painter(this);
painter.setPen(palette().color(QPalette::Text));
QStyleOptionComboBox opt;
initStyleOption(&opt);
opt.currentText = title;
painter.drawComplexControl(QStyle::CC_ComboBox, opt);
painter.drawControl(QStyle::CE_ComboBoxLabel, opt);
}
void CheckBoxList::setDisplayText(QString text) {
title = text;
}
QString CheckBoxList::getDisplayText() const {
return title;
}
bool CheckBoxList::isChecked(const QModelIndex& index) const {
return view()->model()->itemData(index).value(Qt::UserRole).toBool();
}
Thank you.

QStandardItem + QComboBox

I am trying to put a QComboBox into a QStandardItem to be used in a QStandardItemModel. I have been looking around and I cannot find an answer, any ideas?
You don't store a QComboBox in a QStandardItemModel. Let's say you have the following choices:
A
B
C
D
and you have a list with two items in a QListView, the first value being A the second being D:
QListView* pView = new QListView();
QStandardItemModel* pModel = new QStandardItemModel();
pView->setModel(pModel);
pModel->appendRow(new QStandardItem("A"));
pModel->appendRow(new QStandardItem("D"));
What we created above is a list widget which will display the values of "A" and "D". Now, to the QComboBox. I assume you want that to edit the values of "A" and "D" in the list. For this, you need to create a QItemDelegate.
See http://doc.qt.io/qt-4.8/qitemdelegate.html
An attempt:
class ComboBoxDelegate : public QItemDelegate
{
Q_OBJECT
public:
ComboBoxDelegate(QObject *parent = 0);
QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option,
const QModelIndex &index) const;
void setEditorData(QWidget *editor, const QModelIndex &index) const;
void setModelData(QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index) const;
void updateEditorGeometry(QWidget *editor,
const QStyleOptionViewItem &option, const QModelIndex &index) const;
};
ComboBoxDelegate::ComboBoxDelegate(QObject *parent)
: QItemDelegate(parent)
{
}
QWidget *ComboBoxDelegate::createEditor(QWidget *parent,
const QStyleOptionViewItem &/* option */,
const QModelIndex &/* index */) const
{
QComboBox *editor = new QComboBox(parent);
editor->addItem("A");
editor->addItem("B");
editor->addItem("C");
editor->addItem("D");
return editor;
}
void ComboBoxDelegate::setEditorData(QWidget *editor,
const QModelIndex &index) const
{
QString value = index.model()->data(index, Qt::EditRole).toString();
QComboBox *cBox = static_cast<QComboBox*>(editor);
cBox->setCurrentIndex(cBox->findText(value));
}
void ComboBoxDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index) const
{
QComboBox *cBox = static_cast<QComboBox*>(editor);
QString value = cBox->currentText();
model->setData(index, value, Qt::EditRole);
}
void ComboBoxDelegate::updateEditorGeometry(QWidget *editor,
const QStyleOptionViewItem &option, const QModelIndex &/* index */) const
{
editor->setGeometry(option.rect);
}
And then you need to set the delegate on the QListView to make it work, see:
pView->setItemDelegate(new ComboBoxDelegate(pView));

Resources