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);
Related
We are using a QTableView with a custom QAbstractTableModel. Some data is too long to display in the cells directly or we would like to show additional information.
In the model we use the following code:
QVariant MyTableModel::data(const QModelIndex &index, int role) const
{
if (role == Qt::DisplayRole) {
return "Short Content";
}
if (role == Qt::ToolTipRole) {
return "Super long content which contains line\nbreaks, tabs\t and more.";
}
return QVariant();
}
This works and when hovering the cells the tooltip is displayed. However, this takes a few seconds for the tooltip to appear and sometimes some mouse-wiggling.
Is there a built-in way in Qt 5 to disable the timeout and always display the tooltip?
As they point out in the Qt forum the delay depends on the style and is returned through SH_ToolTip_WakeUpDelay which is 700 ms by default.
Considering the above, a possible solution is to use the QProxyStyle method override:
class ProxyStyle : public QProxyStyle
{
public:
using QProxyStyle::QProxyStyle;
int styleHint(StyleHint hint, const QStyleOption* option = nullptr, const QWidget* widget = nullptr, QStyleHintReturn* returnData = nullptr) const override
{
if (hint == QStyle::SH_ToolTip_WakeUpDelay)
return 0;
return QProxyStyle::styleHint(hint, option, widget, returnData);
}
};
tableview->setStyle(new ProxyStyle(tableview->style()));
Is it possible to set a QItemDelegate on a particular QTreeWidgetItem? I need to color some of the QTreeWidgetItems with a particular color.
I assume it is possible as we have QAbstractItemView::setItemDelegateForRow but I can't figure out how. I can't use QAbstractItemView::setItemDelegateForRow because I need to set a custom delegate on a child row inside the QTreeWidget.
Does anyone know a solution for that?
You can't use QTreeWidgetItem in delegate directly (probably you can store list of this items inside delegates but I think that it is not efficient), because delegates works with QModelIndex and data inside different roles. You can set data to Qt::UserRole+1 and access it inside delegate. For example:
QTreeWidgetItem *cities = new QTreeWidgetItem(ui->treeWidget);
//...
cities->setData(0,Qt::UserRole+1,"chosen one");
QTreeWidgetItem *osloItem = new QTreeWidgetItem(cities);
//...
QTreeWidgetItem *berlinItem = new QTreeWidgetItem(cities);
//...
berlinItem->setData(0,Qt::UserRole+1,"chosen one");
Inside delegate (just example):
void ItemDelegatePaint::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QString txt = index.model()->data( index, Qt::DisplayRole ).toString();
if( option.state & QStyle::State_Selected )
{
if(index.data(Qt::UserRole+1).toString() == "chosen one")
painter->fillRect( option.rect,Qt::green );
else
painter->fillRect( option.rect, option.palette.highlight() );
}else
if(option.state & QStyle::State_MouseOver)
{
if(index.data(Qt::UserRole+1).toString() == "chosen one")
painter->fillRect( option.rect,Qt::yellow );
else
painter->fillRect( option.rect, Qt::transparent );
}
else
{
QStyledItemDelegate::paint(painter,option,index);
}
}
You can access the QTreeWidget from you delegate's paint routine to check if a condition for painting the background is met
void custom_delegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
const QTreeWidget* tree_widget = qobject_cast<const QTreeWidget*>(qstyleoption_cast<const QStyleOptionViewItemV3*>(&option)->widget);
....
}
or you store something in the QModelIndex UserData as Chernobyl suggested. In that case I would however create an enum for flags (if this is applicable in your case):
enum custom_painter_flags{
paint_default = 0,
paint_background = 1
};
void somewhere_creating_the_items()
{
QTreeWidgetItem* newitem = new QTreeWidgetItem(...);
newitem->setData(0, Qt::UserRole, QVariant::fromValue<int>(custom_painter_flags::paint_background));
}
void custom_delegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
custom_painter_flags painter_flags = static_cast<painter>(index.data(Qt::UserRole).value<int>());
if(painter_flags & paint_background){
....
}
}
Unfortunately I have not much time right now so this is thrown together pretty quick. Feel free to edit if you find any errors.
You can use qss on the QTreeWidgetItem to change color or background color.
I have done it for a QTableWidget, You must check the value of all your QTreeWidgetItem and set a background color / color.
For example, for my QTableWidget i have done something like this in a loop :
if(good item):
MyQTableItem.setBackground(QtGui.QColor(255,255,255))
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());
I have a QTableView which has some QString based items in its model. I implemented setData in my table model, so editing is working (I can change the data in the cell, setData is called to update the model, and the table is properly updated).
Currently setData is only called when the user is done with editing, e.g. after they hit Enter or click out of the text entry box to finalize the text entry. I want to update other other parts of the table while the user is typing/editing into the text edit control instead of after they're done and the edited contents are finalized.
A simple example of what I want to have is for the next table cell to display a count of how many characters have been entered into the cell being edited, but to do this as the user is typing/editing the cell contents, not just after the edit is finalized and setData is called.
Any pointers to what I should be looking for? Thanks!
You can subclass QStyledItemDelegate and commit the data whenever something changes, and then set that delegate for the view with QAbstractItemView::setItemDelegate.
class MyDelegate : public QStyledItemDelegate {
QSignalMapper *mapper;
public:
MyDelegate(QObject*parent = 0)
: QStyledItemDelegate(parent)
, mapper(new QSignalMapper(this))
{
connect(mapper, SIGNAL(mapped(QWidget*)), SIGNAL(commitData(QWidget*)));
}
QWidget * createEditor(QWidget * parent, const QStyleOptionViewItem & option,
const QModelIndex & index ) const
{
QWidget *editor = QStyledItemDelegate::createEditor(parent, option, index);
if(qobject_cast<QLineEdit*>(editor)) {
connect(editor, SIGNAL(textChanged(QString)), mapper, SLOT(map()));
mapper->setMapping(editor, editor);
}
return editor;
}
};
The answer offered by #alexisdm did not work for me when I needed a persistent
editor enabled by QAbstractTableModel::setPersistentEditor(QModelIndex()).
The following solves this problem:
class Delegate : public QStyledItemDelegate
{
Q_OBJECT
public:
// ... omitted for brevity
QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option,
const QModelIndex &index) const override
{
auto *editor = static_cast<QLineEdit*>(
QStyledItemDelegate::createEditor(parent, option, index));
if (editor) {
connect(editor,
&QLineEdit::textChanged,
[=] (const QString &)
{
const_cast<Delegate*>(this)->commitData(editor);
});
}
return editor;
}
// ... omitted for brevity
};
We simply cast the constness from this and make it commit data for the editor.
Note that in the lambda we capture the editor variable by value [=] because otherwise, capturing with reference would make the value of editor undefined when the function runs out of scope.
I'm using a custom delegate to display QDoubleSpinBoxes in a QTableView. Those spinboxes display their contents with two decimals.
My problem is that I would like the QTableView to also display those numbers with two decimals while they are not being edited (at which point they are not in a QDoubleSpinBox). Or, rather, I would like to be able to specifiy a format for the QTableView's contents.
I tried to subclass QStyledItemDelegate to override displayText, but for a strange reason it crashes. It works correctly if I simply subclass QItemDelegate.
I'm using Qt 4.6.3, on Windows.
I'm not really sure what to make of the exception you are getting. Here is a simple QStyledItemDelegate that we are using without problems. Perhaps there is something different?
#include "model_view/color_combo_delegate.h"
#include <QTimer>
#include "map_elements/common/color_combo_box.h"
ColorComboDelegate::ColorComboDelegate(QObject *parent)
: QStyledItemDelegate(parent) {
}
QWidget *ColorComboDelegate::createEditor(
QWidget *parent,
const QStyleOptionViewItem & /*option*/,
const QModelIndex & /*index*/) const {
ColorComboBox *color_combo_box = new ColorComboBox(parent);
connect(color_combo_box, SIGNAL(currentIndexChanged(int)),
this, SLOT(IndexChanged()));
QTimer::singleShot(0, color_combo_box, SLOT(Popup()));
return color_combo_box;
}
QString ColorComboDelegate::displayText(const QVariant &value,
const QLocale &/*locale*/) const {
Map::Color color = static_cast<Map::Color>(value.toInt());
return Map::color_name(color);
}
void ColorComboDelegate::IndexChanged() {
ColorComboBox *color_combo_box = qobject_cast<ColorComboBox *>(sender());
emit commitData(color_combo_box);
emit closeEditor(color_combo_box);
}
void ColorComboDelegate::setEditorData(QWidget * editor,
const QModelIndex & index) const {
ColorComboBox *color_combo_box = qobject_cast<ColorComboBox *>(editor);
Map::Color color = static_cast<Map::Color>(index.data().toInt());
color_combo_box->set_color(color);
}
void ColorComboDelegate::setModelData(QWidget *editor,
QAbstractItemModel *model,
const QModelIndex &index) const {
ColorComboBox *color_combo_box = qobject_cast<ColorComboBox *>(editor);
model->setData(index, color_combo_box->color());
}
Well, I don't know what happened, but now it no longer crashes. And it now works.
For the record, this is my displayText method:
QString sqxSpinBoxDelegate::displayText(const QVariant &value, const QLocale &locale) const
{
return locale.toString(value.toDouble(), 'f', Decimals);
}