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.4k 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.
  • JonBJ JonB

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

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

    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 2 Replies Last reply
    0
    • N numzero

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

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

      @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 its editingFinished() 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....

      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
        #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