PyQt - QCombobox in QTableview

自作多情 提交于 2019-12-11 15:16:23

问题


I am displaying data from an SQLite database in a QTableView using a QSqlTableModel. Letting the user edit this data works fine. However, for some columns I want to use QComboboxes instead of free text cells, to restrict the list of possible answers.

I have found this SO answer and am trying to implement it on my model/view setting, but I'm running into problems (so this is a follow-up).

Here's a full mini-example:

#!/usr/bin/python3
# -*- coding: utf-8 -*-

from PyQt5 import QtSql
from PyQt5.QtWidgets import (QWidget, QTableView, QApplication, QHBoxLayout,
                             QItemDelegate, QComboBox)
from PyQt5.QtCore import pyqtSlot
import sys

class ComboDelegate(QItemDelegate):
    """
    A delegate that places a fully functioning QComboBox in every
    cell of the column to which it's applied

    source: https://gist.github.com/Riateche/5984815
    """
    def __init__(self, parent, items):
        self.items = items
        QItemDelegate.__init__(self, parent)

    def createEditor(self, parent, option, index):
        combo = QComboBox(parent)
        li = []
        for item in self.items:
            li.append(item)
        combo.addItems(li)
        combo.currentIndexChanged.connect(self.currentIndexChanged)
        return combo

    def setEditorData(self, editor, index):
        editor.blockSignals(True)
#         editor.setCurrentIndex(int(index.model().data(index))) #from original code
        editor.setCurrentIndex(index.row()) # replacement
        editor.blockSignals(False)

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

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


class Example(QWidget):
    def __init__(self):
        super().__init__()
        self.resize(400, 150)

        self.createConnection()
        self.fillTable() # comment out to skip re-creating the SQL table
        self.createModel()
        self.initUI()

    def createConnection(self):
        self.db = QtSql.QSqlDatabase.addDatabase("QSQLITE")
        self.db.setDatabaseName("test.db")
        if not self.db.open():
            print("Cannot establish a database connection")
            return False

    def fillTable(self):
        self.db.transaction()
        q = QtSql.QSqlQuery()

        q.exec_("DROP TABLE IF EXISTS Cars;")
        q.exec_("CREATE TABLE Cars (Company TEXT, Model TEXT, Year NUMBER);")
        q.exec_("INSERT INTO Cars VALUES ('Honda', 'Civic', 2009);")
        q.exec_("INSERT INTO Cars VALUES ('VW', 'Golf', 2013);")
        q.exec_("INSERT INTO Cars VALUES ('VW', 'Polo', 1999);")

        self.db.commit()

    def createModel(self):
        self.model = QtSql.QSqlTableModel()
        self.model.setTable("Cars")
        self.model.select()

    def initUI(self):
        layout = QHBoxLayout()
        self.setLayout(layout)

        view = QTableView()
        layout.addWidget(view)

        view.setModel(self.model)

        view.setItemDelegateForColumn(0, ComboDelegate(self, ["VW", "Honda"]))
        for row in range(0, self.model.rowCount()):
            view.openPersistentEditor(self.model.index(row, 0))

    def closeEvent(self, e):
        for row in range(self.model.rowCount()):
            print("row {}: company = {}".format(row, self.model.data(self.model.index(row, 0))))
        if (self.db.open()):
            self.db.close()


def main():
    app = QApplication(sys.argv)
    ex = Example()
    ex.show()
    sys.exit(app.exec_())


if __name__ == '__main__':
    main()

In this case, I want to use a QCombobox on the "Company" column. It should be displayed all the time, so I'm calling openPersistentEditor.

Problem 1: default values I would expect that this shows the non-edited field's content when not edited (i.e. the company as it is listed in the model), but instead it apparently shows the ith element of the combobox's choices. How can I make each combobox show the model's actual content for this field by default?

Problem 2: editing When you comment out "self.fill_table()" you can check whether the edits arrive in the SQL database. I would expect that choosing any field in the dropdown list would replace the original value. But (a) I have to make every choice twice (the first time, the value displayed in the cell remains the same), and (b) the data appears in the model weirdly (changing the first column to 'VW', 'Honda', 'Honda' results in ('1', 'VW', '1' in the model). I think this is because the code uses editor.currentIndex() in the delegate's setModelData, but I have not found a way to use the editor's content instead. How can I make the code report the user's choices correctly back to the model? (And how do I make this work on first click, instead of needing 2 clicks?)

Any help greatly appreciated. (I have read the documentation on QAbstractItemDelegate, but I don't find it particularly helpful.)


回答1:


Found the solution with the help of the book Rapid GUI Programming with Python and Qt:

createEditor and setEditorData do not work as I expected (I was misguided because the example code looked like it was using the text content but instead was dealing with index numbers). Instead, they should look like this:

def setEditorData(self, editor, index):
    editor.blockSignals(True)
    text = index.model().data(index, Qt.DisplayRole)
    try:
        i = self.items.index(text)
    except ValueError:
        i = 0
    editor.setCurrentIndex(i)
    editor.blockSignals(False)

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

I hope this helps someone down the line.



来源:https://stackoverflow.com/questions/51945016/pyqt-qcombobox-in-qtableview

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!