2

I'm using a QTableView and sub-classed a QAbstractTableModel. When editing a cell I noticed that QAbstractTableModel.setData only goes through the last selected cell. Is there a way to get setData to work with multiple (selected) cells?

As an example of usage:

  • Select four cells from the table.
  • Begin edit on one cell.
  • Input a value and hit enter to commit.
  • Have value modify all four cells (currently it just modifies the cell that is selected last)

I tried sub-classing closeEditor, then looping through selectedIndexes to call them with setData, but I don't know how to get the user's input value from closeEditor.

Here's my attempt, almost got it, just need the input value.

def closeEditor(self, editor, hint):
    is_cancelled = (hint == QtGui.QAbstractItemDelegate.RevertModelCache)

    if not is_cancelled:
        for index in self.selectedIndexes():
            if index == self.currentIndex():
                continue

            input_value = ??? # Don't know how to get this from here!

            self.model().setData(index, input_value, QtCore.Qt.EditRole)

    return QtGui.QTableWidget.closeEditor(self, editor, hint)
Green Cell
  • 4,677
  • 2
  • 18
  • 49

2 Answers2

2

The editor in the closeEditor signal is the widget that was used for editing.

If that is, for example a QLineEdit you can read the text of it through its text property.

One way to retrieve the property value is through QObject's property API

value = editor.property("text")
Kevin Krammer
  • 5,159
  • 2
  • 9
  • 22
  • The editor is a `QWidget`, so there's no text property. – Green Cell Nov 28 '16 at 01:51
  • As I wrote: if the widget is an `QLineEdit` then it has a text property. I.e. it depends on which widget is being used as the editor. The default delegate used line edits for text, spin boxes for numbers and so on – Kevin Krammer Nov 28 '16 at 17:13
  • Which one does it use then? What's the classname of `editor`? I.e. the value of `editor.metaObject().className()`? – Kevin Krammer Nov 29 '16 at 09:51
  • `editor.metaObject().className()` is `QExpandingLineEdit`. Though `type(editor)` is `QWidget`, so I can't just call `editor.text` like you were suggesting as it doesn't exist. Is the value stuffed inside `editor().metaObject()` somewhere? I checked its documentation but can't really find a way. – Green Cell Nov 29 '16 at 11:07
  • @GreenCell. The answer given here is technically correct. A `QExpandingLineEdit` is an internal Qt class which is a subclass of `QLineEdit`. There seems to be a bug in PySide, because it should pass an instance of `QLineEdit` to `closeEditor`, rather than a `QWidget`. The same code works correctly in PyQt4 (i.e. it passes a `QLineEdit`). – ekhumoro Nov 29 '16 at 20:08
  • I guess there's no solution with PySide then? I did use PySide as a tag instead of PyQt ;) – Green Cell Nov 30 '16 at 00:23
  • @GreenCell I had assumed that Python would either access the property through its "duck typing" or at least have a way to cast. But even if Python can't do casts and can't access properties like that, then it should still be possible to call `QObject::property()` – Kevin Krammer Nov 30 '16 at 17:30
  • @KevinKrammer Sorry for being snarky earlier, it's just when I see your answer my immediate response is... duh. It seems like a bug with PySide, maybe missing something to collect the proper editor. I'm able to get the user's input inside `closeEditor` using `editor.property("text")`. Could you add this to your answer for PySide users? Then I'll accept it, thank you! – Green Cell Dec 01 '16 at 01:51
  • @KevinKrammer. It's PySide's job to cast `QWidget* editor` to a `QlineEdit*` before passing a wrapper object to Python. It cannot simply cast to `QExpandingLineEdit*`, because that is an internal Qt class which PySide cannot wrap. So some special handling would be required to cast to the right base-class (i.e. it won't just happen automatically). – ekhumoro Dec 01 '16 at 19:19
0

Edit: I was using this as a workaround as there was a bug in PySide where editor was only returning a QWidget. Please look at Kevin Krammer's answer!

Though it's a slight work around, the solution I have so far seems to be working fine. Please let me know if there's a better way, this is a bit long-winded.

In closeEditor I make every cell pass through setData with None as the value.

Then in QAbstractTableModel I have a variable there _input_value that will store the user's input for the rest of the cells to grab.

class TableView(QtGui.QTableView):

    def __init__(self, parent=None):
        super(TableView, self).__init__(parent)

    def closeEditor(self, editor, hint):
        is_cancelled = (hint == QtGui.QAbstractItemDelegate.RevertModelCache)

        if not is_cancelled:
            for index in self.selectedIndexes():
                if index == self.currentIndex():
                    continue

                # Supply None as the value
                self.model().setData(index, None, QtCore.Qt.EditRole)

        # Reset value for next input
        if self.model()._input_value is not None:
            self.model()._input_value = None

        return QtGui.QTableWidget.closeEditor(self, editor, hint)


class TableModel(QtCore.QAbstractTableModel):

    def __init__(self, parent=None):
        super(TableModel, self).__init__(parent)

        self.main_widget = parent

        self._input_value = None

    def setData(self, index, value, role):
        # Triggers if user cancelled input
        if value is None and self._input_value is None:
            return False

        if self._input_value is None:
            # The last selected cell will pass through here to store the value.
            self._input_value = value
        else:
            # All other cells will pass None, so just grab our stored value.
            value = self._input_value

        # Do whatever you want with value now

        return True
Green Cell
  • 4,677
  • 2
  • 18
  • 49