Qt - Triggering an event when selection of abstract item delegate changes - qt

I am trying to make a tableview with a row which has a separate dropdown for each column. The user can only select a combination of values. That is, if the user selects "A" from the first drop down, the values in the other drop downs should be updated to that which can match "A".
I have made my AbsractItemDelegate class and the values are being assigned fine. But I am stuck up at how I can trigger an event when a value changes in one of the drop downs.
Thanks.
The following is my delegate class implementation:
FillComboBox::FillComboBox(QStringList the_list) : QItemDelegate() {
//list = new QStringList();
list = the_list; }
QWidget* FillComboBox::createEditor(QWidget* parent,
const QStyleOptionViewItem& /* option */,
const QModelIndex& /* index */) const {
QComboBox* editor = new QComboBox(parent);
editor->addItems(list);
editor->setCurrentIndex(2);
return editor; }
void FillComboBox::setEditorData(QWidget* editor,
const QModelIndex &index) const {
QString text = index.model()->data(index, Qt::EditRole).toString();
QComboBox* combo_box = dynamic_cast<QComboBox*>(editor);
combo_box->setCurrentIndex(combo_box->findText(text)); }
void FillComboBox::setModelData(QWidget* editor, QAbstractItemModel* model,
const QModelIndex &index) const {
QComboBox* combo_box = dynamic_cast<QComboBox*>(editor);
QString text = combo_box->currentText();
model->setData(index, text, Qt::EditRole); }
void FillComboBox::updateEditorGeometry(QWidget* editor,
const QStyleOptionViewItem &option, const QModelIndex &/* index */) const {
editor->setGeometry(option.rect); }

You can update the data of the "other" item as soon as the current item's data is updating, i.e. in FillComboBox::setModelData(). Please find the pseudo code that implements desired behavior (see comments):
void FillComboBox::setModelData(QWidget* editor, QAbstractItemModel* model,
const QModelIndex &index) const
{
QComboBox* combo_box = dynamic_cast<QComboBox*>(editor);
QString text = combo_box->currentText();
model->setData(index, text, Qt::EditRole);
// Find the model index of the item that should be changed and its data too
int otherRow = ...; // find the row of the "other" item
int otherColumn = ...; // find the column of the "other" item
QModelIndex otherIndex = model->index(otherRow, otherColumn);
QString newText = text + "_new";
// Update other item too
model->setData(otherIndex, newText, Qt::EditRole);
}

Related

Remove CellWidget from QTableWidget when focus out

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

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

Drawing a check mark in Qt

I am working with simple QTreeView. Each row of tree is a specific class inherited from one class EditorRow.
EditorRow has these functions:
virtual QVariant data(ColumnIndexEnum index, int role = Qt::DisplayRole) const = 0;
virtual void setData(const QVariant& data, ColumnIndexEnum index, int role = Qt::UserRole);
virtual QWidget* getEditor(QWidget* parent) const;
Each row has its specific widget, which is shown in the right column when selecting that row. When the row is not selected data function returns appropriate value for each row(f.e. the value which was chosen in the QComboBox).
But in case of row, which's widget is QCheckBox, I need to draw a checked(or unchecked) checkbox when the row is not selected.
I have tried to use Decoration role like this:
if(Qt::DecorationRole == role)
{
if(ValueColumn == index)
{
QStyle* style = QApplication::style();
QStyleOptionButton opt;
opt.state |= QStyle::State_Enabled;
if(isChecked())
opt.state = QStyle::State_On;
else
opt.state = QStyle::State_Off;
const int indicatorWidth = style->pixelMetric(QStyle::PM_IndicatorWidth, &opt);
const int indicatorHeight = style->pixelMetric(QStyle::PM_IndicatorHeight, &opt);
const int listViewIconSize = indicatorWidth;
const int pixmapWidth = indicatorWidth;
const int pixmapHeight = qMax(indicatorHeight, listViewIconSize);
opt.rect = QRect(0, 0, indicatorWidth, indicatorHeight);
QPixmap pixmap = QPixmap(pixmapWidth, pixmapHeight);
pixmap.fill(Qt::transparent);
{
QPainter painter(&pixmap);
QCheckBox cb;
cb.setLayoutDirection(Qt::RightToLeft);
style->drawPrimitive(QStyle::PE_IndicatorCheckBox, &opt, &painter, &cb);
}
return QIcon(pixmap);
}
}
It actually works, but the icon is shown always, even when the row is selected.
I think it is because of DecorationRole.
Do You have any ideas how to handle this problem ?
Thank You.

QTableView & QDelegate -- QTextEdit opening on left corner

I am working on QTableview inside a QStandardItemModel. I am using QTextEdit inside a class derived from Qdeligates. This allows me to insert newline inside tableview.
Most of the things are working fine but now i am facing a challenge that when ever i click a cell to edit QTextEdit is opening on the left corner of the GUI. When I click cell row 0, column 3 text edit opens on the left corner.
How can i make my QTextEdit to open near the cell which i am editing ?
Also what should i put inside this function updateEditorGeometry ?
Here is my initialising code for tableview :--
ui->testCaseTableView->verticalHeader()->resizeSections(QHeaderView::ResizeToContents); //---> original
ui->testCaseTableView->verticalHeader()->setResizeMode(QHeaderView::ResizeToContents);
ui->testCaseTableView->horizontalHeader()->setStretchLastSection(true);
//Set model & deligate
ui->testCaseTableView->setModel(model);
ui->testCaseTableView->setItemDelegate(mydeligate);
Here is my code for the deligate :---
QWidget* textViewDeligate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QTextEdit *tableEdit = new QTextEdit(parent);
return tableEdit;
}
void textViewDeligate::setEditorData ( QWidget * editor, const QModelIndex & index ) const
{
QString value = index.model()->data(index,Qt::EditRole).toString();
QTextEdit *tableEditCopy = static_cast<QTextEdit*>(editor);
tableEditCopy->setPlainText(value);
}
void textViewDeligate::setModelData ( QWidget * editor, QAbstractItemModel * model, const QModelIndex & index ) const
{
QTextEdit *tableEditCopy = static_cast<QTextEdit*>(editor);
QString str = tableEditCopy->toPlainText();
model->setData(index, str, Qt::EditRole);
}
void textViewDeligate::updateEditorGeometry ( QWidget *editor, const QStyleOptionViewItem & option, const QModelIndex & index ) const
{
}
You need to setup the editor geometry of all widgets you create yourself. This is so that the widgets get given the correct dimensions of the tables' cells.
void textViewDeligate::updateEditorGeometry ( QWidget *editor, const QStyleOptionViewItem & option, const QModelIndex & index ) const
{
editor->setGeometry(option.rect);
}
This was what improved it further :---
editor->setGeometry(option.rect.x(),option.rect.y(),editor->sizeHint().width(),editor->sizeHint().height());

Determine if QTableView has an open editor

Is there any way to determine if a QTableView has an open editor in the current cell? I need to handle the following situation:
A user double-clicks a cell and edits the data, but leaves the cell in the "edit" state.
On another part of the UI, an action is taken that changes the selected row of the underlying model.
Back on my view, I want to determine if the newly selected row is the same as the open row. If not, I need to take an action. (Prompt the user? Commit automatically? Revert?)
I see how to get the current item, and can get the delegate on that item, but I don't see any isEditMode() property I was hoping to find.
Can someone point me in the right direction?
Just check whether the return value of
State QAbstractItemView::state () const
is
QTableView::EditingState
Connect to underlying model dataChanged signal
void QAbstractItemModel::dataChanged ( const QModelIndex & topLeft, const QModelIndex & bottomRight )
You can check if the cell where data has changed is the same than the currentIndex
QModelIndex QAbstractItemView::currentIndex () const
You cannot know if the current cell had an open editor straight, but can check if the view is in QAbstractItemView::EditingState
State QAbstractItemView::state () const
It should be enough to do what you want.
You can subclass QTableView in order to be able to access the state() function, which is unfortunately protected. However, I did not try that.
If you already have an QStyledItemDelegate subclass, you can use it to track whether an editor is currently open. However, you can't just use setEditorData/setModelData, because setModelData won't be called, when the user cancels editing. Instead, you can track the creation and destruction of the editor itself.
class MyItemDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
MyItemDelegate( QObject* parent = nullptr );
~MyItemDelegate();
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;
bool isEditorOpen() const { return *m_editorCount > 0; }
protected:
int* m_editorCount;
protected slots:
void onEditorDestroyed( QObject* obj );
};
Implementation:
MyItemDelegate::MyItemDelegate( QObject* parent ) :
QStyledItemDelegate( parent )
{
m_editorCount = new int;
*m_editorCount = 0;
}
MyItemDelegate::~MyItemDelegate()
{
delete m_editorCount;
}
QWidget* MyItemDelegate::createEditor( QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index ) const
{
// create an editor, can be changed as needed
QWidget* editor = QStyledItemDelegate::createEditor( parent, option, index );
connect( editor, SIGNAL(destroyed(QObject*)), SLOT(onEditorDestroyed(QObject*)));
printf( "editor %p created\n", (void*) editor );
(*m_editorCount)++;
return editor;
}
void MyItemDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
...
}
void MyItemDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
...
}
void MyItemDelegate::onEditorDestroyed( QObject* obj )
{
printf( "editor %p destroyed\n", (void*) obj );
(*m_editorCount)--;
}
On some occasions, e.g. when moving to the next item in the tree using the cursor keys, Qt will create the new editor first and then destroy the old one. Hence, m_editorCount must be an integer instead of a bool.
Unfortunately, createEditor() is a const function. Therefore, you cannot create an int-member. Instead, create a pointer to an int and use that.
Subclass your delegate so that it includes an accessor that tells you when it's editing:
void MyDelegate::setEditorData ( QWidget * editor, const QModelIndex & index ) const {
// _isEditing will have to be mutable because this method is const
_isEditing = true;
QStyledItemDelegate::setEditorData(editor, index);
}
void MyDelegate::setModelData ( QWidget * editor, QAbstractItemModel * model, const QModelIndex & index ) const {
QStyledItemDelegate::setModelData(editor, model, index);
_isEditing = false;
}
bool MyDelegate::isEditing() const { return _isEditing; }
Then you can just check the delegate to see what's going on. Alternatively and/or if you don't like the mutable, you can emit signals so you know what state the delegate is in.
If you know the index of the item being edited, you can call indexWidget() and attempt to cast it. If it's valid, you not only know you're editing, but you also have your editor widget handy.
EditWidget *editWidget = qobject_cast<EditWidget*>(tableView->indexWidget(tableView->currentIndex()));
if(editWidget)
{
//yep, ur editing bro
}
Here is an idea, its even helpful to get the edit/combo widget before the edit begins...
just emit a signal and consume it in the mainwindow... this is what I used one to get combo box in QTableWidget before editing...
first create a signal in ComoBoxItemDelegate...
signals:
void OnComboEdit(QComboBox* pCombo) const;
then emit the signal in the createEditor method...
QWidget* ComboBoxItemDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
// Create the combobox and populate it
QComboBox* cb = new QComboBox(parent);
emit OnComboEdit(cb);
return cb;
}
and in the MainWindow declare a function to receive the singal...
void MainWindow::OnComboEidt(QComboBox *pCB) const
{
qDebug() << "Combo Eidt Singal Received";
}
Then finally in the constructor of MainWindow connect it...
ComboBoxItemDelegate* cbid = new ComboBoxItemDelegate(ui->tableWidget);
connect(cbid, &ComboBoxItemDelegate::OnComboEdit, this, &MainWindow::OnComboEidt);
ui->tableWidget->setItemDelegateForColumn(0, cbid);

Resources