Solved QStyledItemDelegate::createEditor() not getting called
-
In my scenario,
QStyledItemDelegate::createEditor()
is not getting called when I double click on certain items in myQTableView
. I am looking for a hint as to why not, please....Life is not simple, and nor is my scenario. I describe it in abstract, code would be large to reproduce. I am going to be brief as what I need is a clue as to where the issue lies.
I have a proxy model derived from
QIdentityProxyModel
. It is a "semi-identity" model! Some columns are mapped to the underlying source model, but some columns are "internal" to my proxy model.I have done all the necessary overrides etc. Overridden methods include:
columnCount(); rowCount(); data(); setData(); flags(); mapFromSource(); mapToSource(); index();
I have a
QTreeView
whose model is the proxy model. It has asetItemDelegate()
to my delegate derived fromQStyledItemDelegate
. The delegate is for editing with spin boxes. It is taken from the Qt example for this. Overridden methods include:createEditor(); setEditorData(); setModelData(); updateEditorGeometry();
Now, everything works. All my mappings etc, correctly pick out indexes from the underlying source model or internal to the proxy model, as appropriate.
data()
picks out the right data,setData()
sets the right data. And so on.If I double-click to edit a cell in a column mapped to the underlying source model it goes into spinbox edit mode on the correct thing, reads/writes it correctly, etc.
Except only.... My problem is that if I double-click to edit a cell in a column mapped to the internal proxy model's data, nothing happens :( The subclassed
QStyledItemDelegate::createEditor()
simply does not get called, and that is where I am stuck.Now, you will immediately say, "Ah ha, that's because your flags must be wrong, it doesn't know it's editable". But for my proxy-internal editable columns I have:
return (Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsEnabled);
This is what the flags for underlying source model return, I checked that. Note the
Qt::ItemIsEditable
. That code is indeed hit, and it shows the column in non-edit mode as not grayed for read-only, which it would if I removed that.So I need a clue: given that
flags()
is returningQt::ItemIsEditable
for an item, what else could be wrong/am I missing which would causeQStyledItemDeletegate::createEditor()
not to be called?? -
For the sake of completeness, I present as the solution a complete, standalone, working code for this situation. For simplicity, I have removed any use of a
QStyledItemDelegate
forQTreeView::setItemDelegate()
as this is not required.It shows the minimal needed to allow for a
QAbstractProxyModel
, derived fromQIdentityProxyModel
, where you want to combine data from an underlying genuine source model with additional data held in, or external to, the proxy model layer.I am marking this as the solution. Thanks to @Christian-Ehrlicher for all his hard work/solutions!
#include <QApplication> #include <QDebug> #include <QIdentityProxyModel> #include <QStandardItemModel> #include <QTreeView> class MyProxyModel : public QIdentityProxyModel { int proxyData[5] = { 5, 6, 7, 8, 9 }; virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override { Q_ASSERT(!parent.isValid()); return 5; } virtual int columnCount(const QModelIndex &parent = QModelIndex()) const override { Q_ASSERT(!parent.isValid()); return 2; } virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override { if (role == Qt::DisplayRole) { if (orientation == Qt::Horizontal) return QString("Column #%1 (%2)").arg(section).arg(section == 0 ? "source model" : "proxy model"); } return QIdentityProxyModel::headerData(section, orientation, role); } virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override { if (!index.isValid()) return QVariant(); if (index.column() == 1) { if (role == Qt::DisplayRole || role == Qt::EditRole) return proxyData[index.row()]; return QVariant(); } return QIdentityProxyModel::data(index, role); } virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override { if (index.column() == 1) { if (role == Qt::DisplayRole || role == Qt::EditRole) { proxyData[index.row()] = value.toInt(); return true; } return false; } return QIdentityProxyModel::setData(index, value, role); } virtual Qt::ItemFlags flags(const QModelIndex &index) const override { if (!index.isValid()) return Qt::NoItemFlags; if (index.column() == 1) return (Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsEnabled); return QIdentityProxyModel::flags(index); } virtual QModelIndex mapFromSource(const QModelIndex &sourceIndex) const override { if (!sourceIndex.isValid()) return QModelIndex(); Q_ASSERT(sourceIndex.column() == 0); return createIndex(sourceIndex.row(), 0); } virtual QModelIndex mapToSource(const QModelIndex &proxyIndex) const override { if (!proxyIndex.isValid()) return QModelIndex(); if (proxyIndex.column() == 1) return QModelIndex(); Q_ASSERT(proxyIndex.column() == 0); return sourceModel()->index(proxyIndex.row(), 0); } virtual QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override { Q_ASSERT(!parent.isValid()); QModelIndex proxyIndex = createIndex(row, column); if (proxyIndex.column() == 1) return proxyIndex; return mapFromSource(mapToSource(proxyIndex)); } virtual QModelIndex sibling(int row, int column, const QModelIndex &idx) const override { if (!idx.isValid()) return QModelIndex(); Q_ASSERT(!idx.parent().isValid()); if (column == 1) return index(row, column); return mapFromSource(sourceModel()->sibling(row, column, mapToSource(idx))); } virtual QModelIndex buddy(const QModelIndex &index) const override { return index; } }; int main(int argc, char *argv[]) { QApplication a(argc, argv); QStandardItemModel *model; MyProxyModel *proxy; QTreeView *tv; model = new QStandardItemModel(5, 1); for (int row = 0; row < model->rowCount(); row++) model->setData(model->index(row, 0), row); proxy = new MyProxyModel; proxy->setSourceModel(model); tv = new QTreeView; tv->setModel(proxy); tv->show(); return a.exec(); }
-
Try to override QStyledItemDelegate::paint() to see if your delegate is really used or maybe replaced by something else later on.
-
@Christian-Ehrlicher
Hi Christian. I'll do that if you like, but I already know from breakpoints and other code in the delegate that it's being called all the time. In particular, remember that if the cell/item happens to be one mapped to the underlying source model rather than one that is mapped to the proxy's own data,createEditor()
is called, is that not enough to prove?The delegate is set for the widget as a whole, via
this->tableView->setItemDelegate(new MyDelegate(this));
And it has an
initStyleOption
in which I alteroption->text
(for$
or%
characters), and they show up fine on the proxy cells when in non-edit mode. So again that shows it's called for these proxy cells?I have just seen there is a
QStyledItemDelegate::editorEvent
, andQAbstractItemDelegate::editorEvent
:When editing of an item starts, this function is called with the event that triggered the editing, the model, the index of the item, and the option used for rendering the item.
The base implementation returns false (indicating that it has not handled the event).
Maybe I should start looking there? I'm trying to understand what happens when it decides whether or not to call
createEditor()
? -
@JonB said in QStyledItemDelegate::createEditor() not getting called:
when it decides whether or not to call createEditor()?
See QAIVP::editor() - so when this function gets called it should use your delegate...
-
@Christian-Ehrlicher
Brill, thanks, I have stuff to look through over weekend!
I have just had a haircut --- yay!!!!! [ When people read this in many years' time they will wonder how miraculous that was at this date :) ] -
Hi,
One thing you can also do if you did not already is to add the override keyword to your overload declaration just in case there's a spelling or parameter issue.
-
@SGaist
Thanks, they all have (virtual
and)override
on them, I like being thorough :) I annotated when pasting here. -
@Christian-Ehrlicher said in QStyledItemDelegate::createEditor() not getting called:
Try to override QStyledItemDelegate::paint() to see if your delegate is really used or maybe replaced by something else later on.
FWIW, I have now had time to test this, and confirm it is indeed called, both on those elements which can be edited successfully and my problem ones which cannot.
-
Right! I have now had a chance to override my delegate's
editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index)
.-
When double clicking on a cell mapped through the proxy to the underlying source model, which does go into edit mode, I see mouse up/down/click events with
index
being the correct, valid index into my proxy model. (Note:index
has row/column/model being the proxy model, not the underlying source model.) -
When double clicking on a cell mapped into the proxy's own data, which does not go into edit mode, I see the same mouse events but with
index
being invalid ---row == -1, column == -1, model == QObject(0x0)
.
As I said, the
mapFromSource()
,mapToSource()
&index()
methods are all overridden to provide the correct mapping behaviour, e.g. in the non-edit case the correct data is displayed, taken from the underlying source model or from the proxy's own data as appropriate. I now need to understand what is different here, to cause the invalid index, which is obviously why it refuses to go into edit mode.... -
-
I have spent some time examining code, trying stuff out, etc., but to no avail. I have looked at the woboq source code of
QIdentityProxyModel
&QTransposeProxyModel
, each of which has elements applicable to my model situation. I have come to the conclusion that there is simply not enough information, or I do not not understand enough, to reliably find a way forward. It may be that the necessary logic is there, or I need to override more methods, but I think I will just flounder, unable to identify the precise problem or progress.Therefore, sadly, I think I must completely scrap my attempt to build a model based on
QIdentity/TransposeProxyModel
for my situation. It seems a shame as everything is working with the sole exception of the ability to makeQStyledItemDeletegate::createEditor()
work for interactive editing, but unless an expert can miraculously come up with the missing link I have little choice.So I intend to give up on my model requiring some items to map to an underlying source model while other items have their data stored internally in the proxy model. Instead I will use a
QStandardItemModel
for the whole thing, which will give me model storage for non-source-model data, and overridedata()
&setData()
to access data local toQStandardItemModel
for local items but calldata()
/setData()
explicitly on the original source model for non-local data. And explicitly deal with model data change signals etc. At least I (should) know how to do that without issue.... -
Maybe you can strip it down so we can debug it.
-
@Christian-Ehrlicher
Dear Christian,You asked me to provide an example. I have slaved all morning to do so, so I hope you will very kindly care to examine it and provide feedback! :)
The code can be copied, compiled & run as-is. I have spent much time cutting it down to a minimum for pasting. there is much missing from my actual case, don't be surprised. I may have made some mistake/difference which is not present in my real, I don't know.
The intention is that the view/proxy model has 5 rows & 2 columns. Column #0 maps to column #0 of the
QStandardItemModel
source model. Column #1 maps to the internalproxy[]
data array held "locally" in the proxy model.The code behaves the same as real example, in that clicking column #0 does go into editor but clicking column #1 does not. That is my question. However, somewhere it is different from real case, but I don't know where. In my real example you would see clicking on column #1 does cause
MyProxySpinDelegate::editorEvent()
to be called, but with in invalidQIndex
. In this cut down version it does not get called at all on column #1 click.We will have to deal with that if we get as far as you suggesting how to make this one work to edit column #1 as well as column #0.
The code, all in
main.cpp
:#include <QApplication> #include <QDebug> #include <QIdentityProxyModel> #include <QSpinBox> #include <QStandardItemModel> #include <QStyledItemDelegate> #include <QTreeView> class MyProxyModel : public QIdentityProxyModel { int proxyData[5] = { 5, 6, 7, 8, 9 }; virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override { Q_ASSERT(!parent.isValid()); return 5; } virtual int columnCount(const QModelIndex &parent = QModelIndex()) const override { Q_ASSERT(!parent.isValid()); return 2; } virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override { if (role == Qt::DisplayRole) { if (orientation == Qt::Horizontal) return QString("Column #%1 (%2)").arg(section).arg(section == 0 ? "source model" : "proxy model"); } return QIdentityProxyModel::headerData(section, orientation, role); } virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override { if (!index.isValid()) return QVariant(); if (index.column() == 1) { if (role == Qt::DisplayRole || role == Qt::EditRole) return proxyData[index.row()]; return QVariant(); } return QIdentityProxyModel::data(index, role); } virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override { if (index.column() == 1) { if (role == Qt::DisplayRole || role == Qt::EditRole) { proxyData[index.row()] = value.toInt(); return true; } return false; } return QIdentityProxyModel::setData(index, value, role); } virtual Qt::ItemFlags flags(const QModelIndex &index) const override { if (!index.isValid()) return Qt::NoItemFlags; if (index.column() == 1) return (Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | Qt::ItemIsEnabled); return QIdentityProxyModel::flags(index); } virtual QModelIndex mapFromSource(const QModelIndex &sourceIndex) const override { if (!sourceIndex.isValid()) return QModelIndex(); Q_ASSERT(sourceIndex.column() == 0); return createIndex(sourceIndex.row(), 0); } virtual QModelIndex mapToSource(const QModelIndex &proxyIndex) const override { if (!proxyIndex.isValid()) return QModelIndex(); // I HAVE A FEELING NEXT 2 LINES MIGHT BE THE ROOT OF THE PROBLEM? // For proxy model colum #1, there is no index in the source model if (proxyIndex.column() == 1) return QModelIndex(); Q_ASSERT(proxyIndex.column() == 0); return sourceModel()->index(proxyIndex.row(), 0); } virtual QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override { Q_ASSERT(!parent.isValid()); QModelIndex proxyIndex = createIndex(row, column); if (proxyIndex.column() == 1) return proxyIndex; return mapFromSource(mapToSource(proxyIndex)); } }; virtual QModelIndex sibling(int row, int column, const QModelIndex &idx) const override { if (!idx.isValid()) return QModelIndex(); Q_ASSERT(!idx.parent().isValid()); if (column == 1) return index(row, column); return mapFromSource(sourceModel()->sibling(row, column, mapToSource(idx))); } class MyProxySpinDelegate : public QStyledItemDelegate { virtual bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index) override { qDebug() << "editorEvent" << event->type() << model << option << index; return QStyledItemDelegate::editorEvent(event, model, option, index); } virtual QWidget *createEditor(QWidget* parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override { Q_UNUSED(option); Q_UNUSED(index); QSpinBox *spin = new QSpinBox(parent); return spin; } }; int main(int argc, char *argv[]) { QApplication a(argc, argv); QStandardItemModel *model; MyProxyModel *proxy; QTreeView *tv; model = new QStandardItemModel(5, 1); for (int row = 0; row < model->rowCount(); row++) model->setData(model->index(row, 0), row); proxy = new MyProxyModel; proxy->setSourceModel(model); tv = new QTreeView; tv->setModel(proxy); tv->setItemDelegate(new MyProxySpinDelegate); tv->show(); return a.exec(); }
-
Looks like the default sibling() implementation is the culprit:
QModelIndex QIdentityProxyModel::sibling(int row, int column, const QModelIndex &idx) const { Q_D(const QIdentityProxyModel); return mapFromSource(d->model->sibling(row, column, mapToSource(idx))); }
Since you don't have a col1 in your source model it will return an invalid index I would guess.
-
@Christian-Ehrlicher
I'm not certain if you're saying I can correct this with my code?I see that
sibling()
usesmapFromSource()
&mapToSource()
. I worry about whether my requirement can work.Remember, what I have is a genuine source model with some genuine data columns for my special proxy model, but I also have columns of data internally in the proxy model.
In the case of Qt's
QIdentityProxyModel
&QTransposeProxyModel
, both of them always map to/from data rows/columns in their source model.In my case, I'm not worried about my
mapFromSource(const QModelIndex &sourceIndex)
. An index in the source model can always be mapped to an index in my proxy (or invalid if not a mapped column).But I am worried about
mapToSource(const QModelIndex &proxyIndex)
. If the proxy index is column #0 I'm OK and can map to the source model. But if it's column #1, its own internal column, there is no source model index. And I think that is where there is a real problem? I can only return theproxyIndex
parameter as-is, but that's an index in the proxy and I think may have caused infinite recursion error?, orQModelIndex()
invalid. And I'm worried that is a problem either way round for the Qt code?I have edited that fragment in my code above to read:
// I HAVE A FEELING NEXT 2 LINES MIGHT BE THE ROOT OF THE PROBLEM? // For proxy model colum #1, there is no index in the source model if (proxyIndex.column() == 1) return QModelIndex();
-
@JonB said in QStyledItemDelegate::createEditor() not getting called:
And I think that is where there is a real problem? I
correct, reimplement sibling and implement a workaround for your special columns.
-
correct, reimplement sibling and implement a workaround for your special columns.
But I don't think I know how to! (As in, what the code should do?)
When
idx
is in the proxy model and refers to colum#1, there is no equivalentmapToSource(idx)
. Hmm, I wantmapFromSource()
of that .... Maybe in this case I need to just look uprow
,column
in proxy model...? -
QModelIndex MyProxyModel::sibling(int row, int column, const QModelIndex &idx) const { if (column != 1) return mapFromSource(d->model->sibling(row, column, mapToSource(idx))); return <yourindexfor X,1> }
-
@Christian-Ehrlicher
Yep, I get it now! The penny clicked as I typed it :) I will certainly try this tomorrow and report back!Thanks so much :)
-
@Christian-Ehrlicher
Dear Christian,Thank you for your suggestion. It does indeed make sense, but it still does not alter/solve the behaviour, either in the test code or in my real code :(
I have incorporated your
sibling
override suggestion:virtual QModelIndex sibling(int row, int column, const QModelIndex &idx) const override { if (!idx.isValid()) return QModelIndex(); Q_ASSERT(!idx.parent().isValid()); if (column == 1) return index(row, column); return mapFromSource(sourceModel()->sibling(row, column, mapToSource(idx))); }
I have also corrected a slip in my test code:
virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override
(the default for
role
was wrong), and ineditorEvent()
qDebug() << "editorEvent" << event->type() << model << option << index;
(prints
event->type()
).I have edited my sample code above to include these three changes.
I have verified that
sibling() override
is indeed called.The good news is that with the new
sibling()
definition the test code now acts like the real code, in thatMyProxySpinDelegate::editorEvent()
is called when clicking on the proxy-only column.However, the bad news is that in both test & real it still does not go into edit mode :( The debug from
editorEvent()
shows:- When clicking on a source-mapped column, which does go into edit mode, I see a bunch of lines starting:
editorEvent QEvent::MouseButtonPress QIdentityProxyModel(0x5555557c2ef0) QModelIndex(0,0,0x0,QIdentityProxyModel(0x5555557c2ef0))
- But on clicking the proxy-only column, which does not go into edit mode, I see the same bunch but with:
editorEvent QEvent::MouseButtonPress QIdentityProxyModel(0x5555557c2ef0) QModelIndex(-1,-1,0x0,QObject(0x0))
This invalid
QModelIndex()
must be the issue preventing going into edit mode successfully.Even with the
sibling()
change you should be finding the same as me, viz. it does not go into edit mode on proxy-only column? I don't know just how you came up with your observation, but can you not take it as far as actually making the test go into edit mode? -
The problem seems to be the QAIV::buddy() function (or better the reimplementation in QSFPM). Simply replace the implementation by
QModelIndex buddy(const QModelIndex &index) const override { return index; }
and the editing works as expected.