3

What I have done so far:

I'm implementing an custom QAbstractTableModel (used in a QTableView-Widget) that contains editable cells. The properties of these cells are specified in my flags() method that looks like this:

def flags(self, index):  # Qt was imported from PyQt4.QtCore
    if index.column() < 2:
        return Qt.ItemIsEditable | Qt.ItemIsEnabled | \
               Qt.ItemIsSelectable
    else:
        return Qt.ItemIsEnabled | Qt.ItemIsSelectable

Cells in the first two columns are marked as editable just like I want.

What I want to do:

However when double clicking the cell to trigger the editing the contained text is deleted and an empty field is shown.

Example of editing a cell

But I don't want to delete & replace the contained text because the contained text could be very long and shouldn't be retyped. I just want to edit what is already contained. When double-clicked the cell should be editable with the previously contained text inside (maybe already selected).

The Question:

How can I achieve this behavior? Do I need to edit my flags method and specify different properties for these cells?

2 Answers2

4

You have a few options.

No data is appearing in the cells during editing because you likely haven't set any data on the Qt.EditRole for each item in your model. The QTableWidget's do this be default.

Another way of doing this is by using a QItemDelegate. This will allow you to manually create the editor widget and initialize it before it appears in the QTableView. You can use the display role text if the edit text hasn't been populated.

class MyDelegate(QtGui.QItemDelegate):

    def createEditor(self, parent, option, index):
        if index.column() == 2:
            return super(MyDelegate, self).createEditor(parent, option, index)
        return None

    def setEditorData(self, editor, index):
        if index.column() == 2:
            # Gets display text if edit data hasn't been set.
            text = index.data(Qt.EditRole) or index.data(Qt.DisplayRole)
            editor.setText(text)         

delegate = MyDelegate()
tableview.setItemDelegate(delegate)
Brendan Abel
  • 35,343
  • 14
  • 88
  • 118
  • Thank you. I tried your second approach with `QItemDelegate` and it works just as intended. If I wanted to set the `Qt.EditRole` without using the class how would I do that? Would I have to define a new method in my custom `TableModel`? –  Jun 07 '16 at 18:39
  • 1
    I don't think you should have to do anything in your model. Though, it's questionable why you *need* to create a custom model. Does `QStandardItemModel` not work? If you just want to control flags, you can set those on the items as you create them. It doesn't have to be a dynamic function on the model. – Brendan Abel Jun 07 '16 at 18:42
  • Well, I'm just figuring out how to do anything with PyQt and didn't even knew there was something like a standard class. I'll look it up. –  Jun 07 '16 at 18:53
  • 1
    Also, unless you know you need a model the item widget classes -- `QTreeWidget`, `QTableWidget`, etc will usually suffice, and will usually be faster to code and create, and usually simpler code as well. – Brendan Abel Jun 07 '16 at 18:57
  • hey , i want exactly the opposite, i have a custom QListWidget, and when i click over the item to rename it, i just have the old name and cant delete it even if i start to typeit stays there, when i hit enter it disappears and gets the new one, but is annoying, i want to hit enter and behaves as it should do it ! heeelp please – Nestor Colt Oct 13 '17 at 23:28
  • @NestorColt Welcome! You should submit a new question that describes that problem. Comments aren't really a good place to answer new questions. – Brendan Abel Oct 16 '17 at 19:03
  • @BrendanAbel, where did you learn about things like QStandardItemModel and QTreeWidget? The books I have found so far on PyQt only cover things very superficially. Thanks. – user118967 Feb 18 '21 at 05:59
  • 1
    @user118967 The Qt and PySide docs are pretty good and detail much of this. Also, if you download PySide or Qt with Creator or Designer, they include a bunch of examples for all these widget types. – Brendan Abel Feb 18 '21 at 16:59
2

As Brendan Abel said,

No data is appearing in the cells during editing because you likely haven't set any data on the Qt.EditRole for each item in your model

In this case, to show previous data in the edit box after you double click the cell, it requires to access data() as EditRole, so you should implement the data() method in the inherited model class as something like this:

def data(self, index, role=None):
    ...
    if role == QtCore.Qt.DisplayRole or role == QtCore.Qt.EditRole:
        item = index.internalPointer()
        return item.data[index.column()]
    ...

It is also mentioned in the official document of model view programming

Claude C
  • 31
  • 4
  • index.internalPointer() returns None in PyQt5 – Ciasto piekarz Jan 28 '21 at 20:07
  • @Ciastopiekarz The [doc](https://doc.qt.io/qtforpython-5/PySide2/QtCore/QModelIndex.html#PySide2.QtCore.PySide2.QtCore.QModelIndex.internalPointer) says it returns a pointer to the internal data, and in my app it does return a data object, not None, as long as the index is valid. – Claude C Jan 29 '21 at 07:42
  • I appreciate the information on the need to include the condition for EditRole. However this answer suggests to get the data QTableView's `index` object. This seems to contradict the whole model-view concept, which is for the data (model) to be separate from the view. The returned value could have been computed or read from any custom data structure the model happens to use to store its data, not necessarily from index.internalPointer. In fact, the OP probably already had an implementation for `data` doing just that and just needed to add the condition `role == QtCore.Qt.EditRole`. – user118967 Feb 18 '21 at 06:21
  • 1
    @user118967 Thank you for your advice. I simply followed the official model view example [source code](https://github.com/pyqt/examples/blob/_/src/pyqt-official/itemviews/editabletreemodel/editabletreemodel.py). In the code, `data(..)` (ln143) calls `getItem(..)`(ln159) finally calls `index.internalPointer()` (ln161). IMO it should be harmless cuz the `index` is a model index object produced by `index(..)` in the model class, not by the view. – Claude C Feb 20 '21 at 03:51