Unsolved Continue editing if ItemModel::setData fails
-
I have a
QTreeView
with an editable model (customQAbstractItemModel
subclass). When the user finishes editing an item, model’ssetData
validates the input. If the input is not valid, it does nothing, returnsfalse
and editing is aborted. Is it possible to keep the editor open instead to let the user fix the input in place? -
Write your own QStyledItemDelegate and override setModelData()
-
@Christian-Ehrlicher What should I put inside? It’s not the function that hides the editor and it doesn’t even return anything, so I don’t see how could it help. I’ve tried several hacks though:
- editor.setVisible(true) — did nothing
- view.edit(index) — failed, logged “edit: editing failed”
- view.openPersistentIndex(index) — kinda worked but causes a lot of behavior I don’t want
I guess I’ll have to subclass the tree view itself...
-
@numzero
I understand your issue, not sure yet whether editing framework supports this. To start with, have you put some kind of validator onto your editing widget(s), can you detect many of your "set data will fail" cases there? -
I hadn’t but that’s the way to go. Just found in the source (
QAbstractItemDelegatePrivate::editorEventFilter
) that it doesn’t close the editor only if the validator tells the input is not valid. That should be technically enough, just double validation for one case and some ugly workaround in the other. And apparentlyQValidator::fixup
is only called when the user finished editing so I might even be able to show a message if the input is not valid. -
@numzero
This thread https://www.qtcentre.org/threads/41893-Model-View-framework-delegate-don-t-close-editor-while-it-contains-invalid-value wants your desired behaviour, and it doesn't look good :(If there turns out to be nothing better, I am thinking aloud: say you have a
QLineEdit
. I am guessing the item editing framework awaits itseditingFinished()
signal to know that it is to be closed and its data submitted? So you would need to put in your validation so that it did not emit that signal if the data was not going to be acceptable? I agree that sounds like per editing widget requirement and what you would like should be simpler and provided by the editing item delegate, but unless we can find that.... -
@numzero said in Continue editing if ItemModel::setData fails:
QAbstractItemDelegatePrivate::editorEventFilter
Your post crossed with my last one.
I was indeed going to say I'd probably go look at what their framework source code is doing to decide when to end the editing in order to decide who best to proceed.
Looks like that is what we are looking for, but not private? :(editorEvent()
oreventFilter()
are not good enough for you here? -
@numzero
void QAbstractItemView::closeEditor(QWidget *editor, QAbstractItemDelegate::EndEditHint hint)That's a
virtual protected slot
. Wonder what happens if you override that, check validity, and not call base? Does that keep it open, and are you still in an acceptable state??P.S.
Just found https://stackoverflow.com/questions/54623332/qtableview-prevent-departure-from-cell-and-closure-of-delegate-editor, confirms difficulty of all this.
Have run out of ideas, it is what it is from what I have seen, no obvious support for "keep editing on data submit failure" :( -
@JonB
eventFilter
is apparently suitable:bool QStyledItemDelegate::eventFilter(QObject *object, QEvent *event) { Q_D(QStyledItemDelegate); return d->editorEventFilter(object, event); }
and the only other place
QAbstractItemDelegatePrivate::editorEventFilter
is ever called from is inQItemDelegate
, thus irrelevant.void QAbstractItemView::closeEditor(QWidget *editor, QAbstractItemDelegate::EndEditHint hint)
<...> override that, check validity, and not call base?
Possible too but the check would be complicated: it’s not “check validity” but “check whether this
closeEditor
is immediately following acommitData
and whether that one succeeded”. Doable but...Anyway, thanks for all the suggestions. Will try.
-
@numzero
OK, so you are saying the fundamental order is close-editor followed by commit-data, and not the other way around? Then no wonder it is not supported/difficult! We wanted set-model-data to be happening before closing editor.... :( -
Hi,
To the best of my knowledge, the validation should happen before you send anything to the model like QIntValidator does for a QLineEdit.
Can you share what your validation looks like ? Depending on it you could extract it from the model and reuse in your editor.
-
@SGaist
Nonetheless, there are "validations" which, for whatever reason, might only be detected at the time the value is sent to the model. If, for whatever reason set-model-data returns false there ought be a way of staying within the created editor widget to allow the user to try again or cancel. It is a "shame" the Qt architecture does not allow for that. -
This might cause some sort of circular disaster but...
Can you give your delegate a signal:
void reopenEdit(const QModelIndex &index);
that you connect to the view's slot
void edit(const QModelIndex &index)
when setting the delegate.
Then in your delegate setModelData(), if the model returns false, emit the signal with the relevant model index. You noted before that calling view.edit(index) directly failed, so you may need to trigger the signal on a zero-length timer.Your editor will lose its state (selection, cursor position, etc.) but should be open and ready for acceptable input.
-
@ChrisW67 said in Continue editing if ItemModel::setData fails:
Your editor will lose its state (selection, cursor position, etc.) but should be open and ready for acceptable input.
Indeed. But the user will see the editor close and re-open, which was what (I believe) the OP was trying to avoid. Not that there is a perfect solution here, just saying.
-
@ChrisW67 said in Continue editing if ItemModel::setData fails:
Your editor will lose its state (selection, cursor position, etc.)
...and content, which is exactly what I’m trying to avoid.
@JonB said in Continue editing if ItemModel::setData fails:
OK, so you are saying the fundamental order is close-editor followed by commit-data, and not the other way around?
I’ve said “following”, not “followed by”. Which to my knowledge is the other way around.
So, I tried to use a validator. Added a function to my model:
def getValidator(self, parent: QWidget, index: QModelIndex) -> QValidator
and to the delegate:
def createEditor(self, parent: QWidget, option: QStyleOptionViewItem, index: QModelIndex) -> QWidget: editor = super().createEditor(parent, option, index) match editor: # --snip-- case QLineEdit(): editor.setValidator(index.model().getValidator(editor, index)) return editor
(I’m using PySide2)
This way, I can tailor the validator to the specific cell being edited. This kinda works: if validator’svalidate
returnsQValidator.Intermediate
, attempting to commit data does nothing, including not touching the editor.However, using this approach for user interaction is infeasible:
validate
is called way too often, on every change.fixup
is called both on committing and on cancelling editing (indirectly in the latter case).
Moreover, if I show a dialog from e.g.
fixup
it of course has to process events and of course one of them causesfixup
to be called again (cf. https://forum.qt.io/topic/136758/qmessagebox-warning-replacement-that-wont-process-the-event-loop/35). While I can protect it from reentrancy this is just getting too hairy.As I do want user interaction, will try another approach; likely overriding
closeEditor
as @JonB proposed. -
@JonB said in Continue editing if ItemModel::setData fails:
@numzero
void QAbstractItemView::closeEditor(QWidget *editor, QAbstractItemDelegate::EndEditHint hint)That's a
virtual protected slot
. Wonder what happens if you override that, check validity, and not call base? Does that keep it open, and are you still in an acceptable state??
<...>
Just found https://stackoverflow.com/questions/54623332/qtableview-prevent-departure-from-cell-and-closure-of-delegate-editorThat does work, mostly. Here is a sample:
class MyView(QTreeView): _allow_close_editor = True def closeEditor(self, editor: QWidget, hint: QAbstractItemDelegate.EndEditHint): if not self._allow_close_editor: return super().closeEditor(editor, hint) self._allow_close_editor = True def commitData(self, editor: QWidget): self._allow_close_editor = False super().commitData(editor) def dataChanged(self, topLeft: QModelIndex, bottomRight: QModelIndex, roles: [int] = []): super().dataChanged(topLeft, bottomRight, roles) self._allow_close_editor = True
However, it behaves poorly if the user tries to select another item while his input for the current one is not valid: the focus changes, but the editor remains open.
editor.setFocus()
doesn’t help.So here is a more advanced version, based on the SO answer:
class MyItemDelegate(QStyledItemDelegate): def setModelData(self, editor: QWidget, model: QAbstractItemModel, index: QModelIndex) -> None: n = editor.metaObject().userProperty().name() if not n: n = d.editorFactory().valuePropertyName(model.data(index, Qt.EditRole).userType()) if n: commit_result = model.setData(index, editor.property(n), Qt.EditRole) if not commit_result: QMessageBox.warning(editor, 'Tree editor', 'Failed to update the value', QMessageBox.Ok) editor.setFocus() parent = editor while parent: if isinstance(parent, QAbstractItemView): parent.setCurrentIndex(index) break parent = parent.parent() editor.setProperty(MyView.COMMIT_RESULT_PROPERTY_NAME, commit_result) class MyView(QTreeView): COMMIT_RESULT_PROPERTY_NAME = '_myview_commit_result' def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.setItemDelegate(MyItemDelegate(parent=self)) def closeEditor(self, editor: QWidget, hint: QAbstractItemDelegate.EndEditHint): commit_result = editor.property(self.COMMIT_RESULT_PROPERTY_NAME) editor.setProperty(self.COMMIT_RESULT_PROPERTY_NAME, None) if commit_result == False: return super().closeEditor(editor, hint)
This almost works. Showing UI works, keeping editor state works. Still, has some weird behavior, like visual and actual focus not matching.
Anyway, I suppose doing what I’m trying to do may not be exactly good idea anyway; it may be more confusing than helpful. And e.g. Dolphin does it differently: it lets the user continue editing in the dialog it shows, rather than in the view itself after showing the dialog. (Technically, it’s KIO what shows the dialog IIUC. And technically, Dolphin doesn’t use QTreeView either despite looking like one).