Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. General and Desktop
  4. Continue editing if ItemModel::setData fails
Forum Updated to NodeBB v4.3 + New Features

Continue editing if ItemModel::setData fails

Scheduled Pinned Locked Moved Unsolved General and Desktop
16 Posts 5 Posters 2.6k Views 1 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • N numzero

    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 apparently QValidator::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.

    JonBJ Offline
    JonBJ Offline
    JonB
    wrote on last edited by JonB
    #7

    @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() or eventFilter() are not good enough for you here?

    N 1 Reply Last reply
    0
    • N numzero

      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 apparently QValidator::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.

      JonBJ Offline
      JonBJ Offline
      JonB
      wrote on last edited by JonB
      #8

      @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" :(

      N 1 Reply Last reply
      0
      • JonBJ JonB

        @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() or eventFilter() are not good enough for you here?

        N Offline
        N Offline
        numzero
        wrote on last edited by
        #9

        @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 in QItemDelegate, 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 a commitData and whether that one succeeded”. Doable but...

        Anyway, thanks for all the suggestions. Will try.

        JonBJ 1 Reply Last reply
        0
        • N numzero

          @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 in QItemDelegate, 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 a commitData and whether that one succeeded”. Doable but...

          Anyway, thanks for all the suggestions. Will try.

          JonBJ Offline
          JonBJ Offline
          JonB
          wrote on last edited by
          #10

          @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.... :(

          1 Reply Last reply
          0
          • SGaistS Offline
            SGaistS Offline
            SGaist
            Lifetime Qt Champion
            wrote on last edited by
            #11

            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.

            Interested in AI ? www.idiap.ch
            Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

            JonBJ 1 Reply Last reply
            0
            • SGaistS SGaist

              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.

              JonBJ Offline
              JonBJ Offline
              JonB
              wrote on last edited by JonB
              #12

              @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.

              1 Reply Last reply
              0
              • C Offline
                C Offline
                ChrisW67
                wrote on last edited by
                #13

                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.

                JonBJ 1 Reply Last reply
                0
                • C ChrisW67

                  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.

                  JonBJ Offline
                  JonBJ Offline
                  JonB
                  wrote on last edited by
                  #14

                  @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.

                  1 Reply Last reply
                  0
                  • N Offline
                    N Offline
                    numzero
                    wrote on last edited by
                    #15

                    @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’s validate returns QValidator.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 causes fixup 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.

                    1 Reply Last reply
                    0
                    • JonBJ JonB

                      @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" :(

                      N Offline
                      N Offline
                      numzero
                      wrote on last edited by
                      #16

                      @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-editor

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

                      1 Reply Last reply
                      0

                      • Login

                      • Login or register to search.
                      • First post
                        Last post
                      0
                      • Categories
                      • Recent
                      • Tags
                      • Popular
                      • Users
                      • Groups
                      • Search
                      • Get Qt Extensions
                      • Unsolved