Search code examples
c++qtqt5qtablewidgetqstyleditemdelegate

QStyledItemDelegate: differentiate the cause for closeEditor() or setModelData()


I'm using a QTableWidget and need some custom handling of the editing, so I set a QStyledItemDelegate on it.

When the user finishes editing, the signal closeEditor() is emitted, which I connect to to process the entered data. This signal is both emitted when Enter/Return is pressed, and when the user clicks somewhere else (outside the QTableWidgetItem to be edited).

My question is: Is is possible to differntiate if the user presses Return/Enter or clicks somewhere else? I'd like to handle the "click outside" just like an ESC press (no data is changed and the original value of the QTableWidgetItem is restored). By now, both cases change the data.

QAbstractItemDelegate::EndEditHint (which is emitted with closeEditor()) does not give me that information.

Thanks for all help!

Edit:

To process data before it's written back to the model, one can implement setModelData(), but still, there seems to be no way if this function was called by pressing Enter/Return or by clicking somewhere else …


Solution

  • I recently made a sample to become (more) familiar with QStyledItemDelegate concerning inline editing of cells:

    In this sample, I did the update of underlying data model overloading QStyledItemDelegate::setModelData().

    After having read this question, I just tested what happens if input is finished (or strictly speaking: aborted) by ESC. This is what I observed:

    1. The underlying data is kept unchanged.

    2. Digging deeper (i.e. setting a break-point into my overloaded setModelData()), I observed setModelData() is even not called in this case.

    May be, the OPs issue can be solved easily by a little re-design of his delegate.

    Finally, my derived class (declaration) I used to achieve a "full-featured" delegate to edit table cells inline:

    class ValueNameDelegate: public QStyledItemDelegate {
    
      // methods:
      public:
        /// @name Construction & Destruction
        //@{
    
        /// constructor.
        ValueNameDelegate();
    
        /// destructor.
        virtual ~ValueNameDelegate() = default;
    
        // disabled:
        ValueNameDelegate(const ValueNameDelegate&) = delete;
        ValueNameDelegate& operator=(const ValueNameDelegate&) = delete;
    
        //@}
      protected:
        /// @name Overloaded Event Handlers
        //@{
    
        // inserts editor in table (by setParent(pQParent)) and
        // returns the editor widget to edit cell.
        virtual QWidget* createEditor(
          QWidget *pQParent, const QStyleOptionViewItem &qOption,
          const QModelIndex &qMIndex) const override;
    
        // removes editor from table (by setParent(nullptr)).
        virtual void destroyEditor(
          QWidget *pQEditor, const QModelIndex &qMIndex) const override;
    
        // reads data from table model and updates editor.
        virtual void setEditorData(
          QWidget *pQEditor, const QModelIndex &qMIndex) const override;
    
        // reads data from editor and updates table model.
        virtual void setModelData(
          QWidget *pQEditor, QAbstractItemModel *pQModel,
          const QModelIndex &qMIndex) const override;
    
        //@}
    };
    

    Note:

    In my case, there is only one editor widget created before-hand. It is re-used for every cell editing (instead of creating for each edit a new one).

    This is a little bit different from the Qt Spin Box Delegate Example but works as expected.

    I cannot imagine how this shouldn't make any difference concerning the OPs problem.


    According to feed-back, OP wants to handle the focus-lose event like abort of input. This is in opposition how this is handled by default in QLineEdit.

    So, my solution is to provide an overloaded version of QLineEdit. Instead of changing the behavior of QLineEdit, it simply tracks in a member bool _confirmed whether Enter was pressed. The most tricky part was to identify another suitable event or signal to reset the member. Finally, I decided that focusOutEvent() is just called at the right time and hence suitable for this task.

    testQLineEdit-Finished.cc:

    #include <QtWidgets>
    
    class LineEdit: public QLineEdit {
      private:
        // flag: true ... last finished editing was confirmed
        bool _confirmed;
      public:
        // Construction & Destruction
        explicit LineEdit(
          const QString &contents = QString(), QWidget *pQParent = nullptr):
          QLineEdit(contents, pQParent),
          _confirmed(false)
        {
          QObject::connect(this, &QLineEdit::returnPressed,
            [this](){ onSigReturnPressed(); });
        }
        LineEdit(QWidget *pQParent): LineEdit(QString(), pQParent) { }
        virtual ~LineEdit() = default;
        LineEdit(const LineEdit&) = delete;
        LineEdit& operator=(const LineEdit&) = delete;
    
      public:
        // returns whether last finished editing was confirmed.
        bool isConfirmed() { return _confirmed; }
      protected:
        virtual void focusOutEvent(QFocusEvent *pQEvent) override
        {
          _confirmed = false;
          QLineEdit::focusOutEvent(pQEvent);
        }
      private:
        void onSigReturnPressed() { _confirmed = true; }
    };
    
    int main(int argc, char **argv)
    {
      qDebug() << "Qt Version:" << QT_VERSION_STR;
      QApplication app(argc, argv);
      // setup GUI
      LineEdit qEdit(QString::fromUtf8("Hello World"));
      qEdit.show();
      // install signal handlers
      QObject::connect(&qEdit, &LineEdit::editingFinished,
        [&]() {
          qDebug() << "Edit confirmed:" << qEdit.isConfirmed();
        });
      // runtime loop
      return app.exec();
    }
    

    Snapshot of testQLineEdit-Finished

    The output of Edit confirmed: true was achieved by pressing Enter, the false lines by clicking around.

    This is done rather simple and might be done more sophisticated. However, it shows the principle achieved with a rather low number of code lines.