Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
623 views
in Technique[技术] by (71.8m points)

python - PyQt - How to set QComboBox in a table view using QItemDelegate

I am trying to display a combo box in my table, so that I can set the selected index from the table model, as with the other cells in the table. I have pieced this together from other examples but still cannot understand how the interaction works to set the selected index of the QComboBox.

This is the simplest example I can come up with to demonstrate the problem. If anyone can demonstrate how to set the index automatically from model data? Also how to use the 'currentIndexChanged' signal, as this seems to fire almost continuously whenever it is repainted? Thanks.

# The following tells SIP (the system that binds Qt's C++ to Python)
# to return Python native types rather than QString and QVariant
import sip
sip.setapi('QString', 2)
sip.setapi('QVariant', 2)


from PyQt4 import QtCore, QtGui

class TableModel(QtCore.QAbstractTableModel):
    """
    A simple 5x4 table model to demonstrate the delegates
    """
    def rowCount(self, parent=QtCore.QModelIndex()): return 5
    def columnCount(self, parent=QtCore.QModelIndex()): return 4

    def data(self, index, role=QtCore.Qt.DisplayRole):
        if not index.isValid(): return None
        if not role==QtCore.Qt.DisplayRole: return None
        return "{0:02d}".format(index.row())


class ComboDelegate(QtGui.QItemDelegate):
    """
    A delegate that places a fully functioning QComboBox in every
    cell of the column to which it's applied
    """
    def __init__(self, parent):

        QtGui.QItemDelegate.__init__(self, parent)

    def paint(self, painter, option, index):

        self.combo = QtGui.QComboBox(self.parent())
        self.connect(self.combo, QtCore.SIGNAL("currentIndexChanged(int)"), self.parent().currentIndexChanged)

        li = []
        li.append("Zero")
        li.append("One")
        li.append("Two")
        li.append("Three")
        li.append("Four")
        li.append("Five")

        self.combo.addItems(li)

        if not self.parent().indexWidget(index):
            self.parent().setIndexWidget(
                index, 
                self.combo
            )

class TableView(QtGui.QTableView):
    """
    A simple table to demonstrate the QComboBox delegate.
    """
    def __init__(self, *args, **kwargs):
        QtGui.QTableView.__init__(self, *args, **kwargs)

        # Set the delegate for column 0 of our table
        # self.setItemDelegateForColumn(0, ButtonDelegate(self))
        self.setItemDelegateForColumn(0, ComboDelegate(self))

    @QtCore.pyqtSlot()
    def currentIndexChanged(self, ind):
        print "Combo Index changed {0} {1} : {2}".format(ind, self.sender().currentIndex(), self.sender().currentText())

if __name__=="__main__":
    from sys import argv, exit

    class Widget(QtGui.QWidget):
        """
        A simple test widget to contain and own the model and table.
        """
        def __init__(self, parent=None):
            QtGui.QWidget.__init__(self, parent)

            l=QtGui.QVBoxLayout(self)
            self._tm=TableModel(self)
            self._tv=TableView(self)
            self._tv.setModel(self._tm)
            l.addWidget(self._tv)

    a=QtGui.QApplication(argv)
    w=Widget()
    w.show()
    w.raise_()
    exit(a.exec_())
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

You're using paint method incorrectly. It should be used when you want to change displaying behavior of the view. Also creating new widget each time you want to paint it is very expensive. But you want to change editing behavior so you need to change entire logic of your program.

See the fixed code. Below I'll expain the changes.

1. First of all, we need to make the first column editable. You can do it by reimplementing QAbstractItemModel::flags:

def flags(self, index):
    if (index.column() == 0):
        return QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsEnabled
    else:
        return QtCore.Qt.ItemIsEnabled

2. By default the item editor is created when user performs a double click on item. If you want to show all comboboxes by default, you can use openPersistentEditor:

for row in range(0, self._tm.rowCount()):
    self._tv.openPersistentEditor(self._tm.index(row, 0))

Note that you should also open editors for newly created cells (if any).

3. Now back to our delegate. We need to implement createEditor method that will be automatically called by the view when an editor is requested for a cell:

def createEditor(self, parent, option, index):
    combo = QtGui.QComboBox(parent)
    li = []
    li.append("Zero")
    li.append("One")
    li.append("Two")
    li.append("Three")
    li.append("Four")
    li.append("Five")
    combo.addItems(li)
    self.connect(combo, QtCore.SIGNAL("currentIndexChanged(int)"), 
                 self, QtCore.SLOT("currentIndexChanged()"))
    return combo

Note that connect is below appends because we need to avoid currentIndexChanged signals on initialization.

4. Implement setEditorData method that will be called by the view when model data has been changed. Also it will be called once when an editor is initialized.

def setEditorData(self, editor, index):
    editor.blockSignals(True)
    editor.setCurrentIndex(int(index.model().data(index)))
    editor.blockSignals(False)

Again, we want to avoid signals that are not caused by the user, so we use blockSignals.

5. In the slot we simply emit commitData signal that will cause the view to call the setModelData of our delegate:

@QtCore.pyqtSlot()
def currentIndexChanged(self):
    self.commitData.emit(self.sender())

6. Implement setModelData method:

def setModelData(self, editor, model, index):
    model.setData(index, editor.currentIndex())

7. Your model needs to support data changing. So we should implement setData method of the model:

def setData(self, index, value, role=QtCore.Qt.DisplayRole):
    print "setData", index.row(), index.column(), value
    # todo: remember the data

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...