如何同时使用自定义过滤器代理模型和 QtCore.Qt.EditRole?

How to use custome FilterProxyModel and QtCore.Qt.EditRole at a time?

示例代码如下:

class FilterProxyModel(QtCore.QSortFilterProxyModel):
    def __init__(self, parent=None):
        super().__init__(parent)
        self._filter_value = None

    @property
    def filter_value(self):
        return self._filter_value

    @filter_value.setter
    def filter_value(self, value):
        self._filter_value = value
        self.invalidateFilter()

    def filterAcceptsRow(self, sourceRow, sourceParent):
        if self.filter_value is None:
            return super().filterAcceptsRow(sourceRow, sourceParent)
        if self.filterKeyColumn() >= 0:
            value = (
                self.sourceModel()
                .index(sourceRow, self.filterKeyColumn(), sourceParent)
                .data(self.filterRole())
            )
            return value == self.filter_value

        for column in range(self.columnCount()):
            value = (
                self.sourceModel()
                .index(sourceRow, column, sourceParent)
                .data(self.filterRole())
            )
            if value == self.filter_value:
                return True
        return False
    
    def setFilterRegExp(self, filter):
        self.filter_value = None
        super().setFilterRegExp(filter)



class UI(QMainWindow):
    def __init__(self):
        super(UI, self).__init__()
        uic.loadUi("tableview.ui", self)
        self.show()

        db = QSqlDatabase.addDatabase('QSQLITE')
        db.setDatabaseName('book.db')
        db.open()

        self.model = QtSql.QSqlTableModel(self)
        self.model.setTable("card")
        self.model.select()
        self.tableView.setModel(self.model)
        
        self.proxy = FilterProxyModel(self)
        self.proxy.setSourceModel(self.model)
        
        self.tableView.setModel(self.proxy)
        self.model.select()

        self.edit.clicked.connect(self.edit_items)
        self.refresh.clicked.connect(self.refresh_table)

        column_names = ["Name","Age","Gender"]

        self.comboBox.addItems([x for x in column_names])

        self.horizontalHeader = self.tableView.horizontalHeader()
        self.horizontalHeader.sectionClicked.connect(
            self.tableView_horizontalHeader_sectionClicked
            )
        self.lineEdit.textChanged.connect(self.lineEdit_textChanged)

    def tableView_horizontalHeader_sectionClicked(self, logicalIndex):
        menu = QtWidgets.QMenu(self)

        values = []

        for row in range(self.model.rowCount()):
            value = self.model.index(row, logicalIndex).data(self.proxy.filterRole())
            values.append(value)

        action_all = QtWidgets.QAction("All", self)
        action_all.setData(None) 
        menu.addAction(action_all)
        menu.addSeparator()

        for value in sorted(list(set(values))):
            action = QtWidgets.QAction(str(value), self)
            action.setData(value)
            menu.addAction(action)

        headerPos = self.tableView.mapToGlobal(self.horizontalHeader.pos())
        posY = headerPos.y() + self.horizontalHeader.height()
        posX = headerPos.x() + self.horizontalHeader.sectionPosition(logicalIndex)

        action = menu.exec_(QtCore.QPoint(posX, posY))
        
        if action is not None:
            self.proxy.setFilterKeyColumn(logicalIndex)
            self.proxy.filter_value = action.data()

    def lineEdit_textChanged(self):
        search = QtCore.QRegExp(    self.lineEdit.text(),
                                    QtCore.Qt.CaseInsensitive,
                                    QtCore.QRegExp.RegExp
                                    )
        self.proxy.setFilterKeyColumn(self.comboBox.currentIndex() - 1)
        self.proxy.setFilterRegExp(search)        
            
    def edit_items(self):

        if not self.model.rowCount():
            return
        index = self.tableView.currentIndex()
        if index.isValid():
            row = index.row()
        else:
            row = 0

        dialog = QtWidgets.QDialog()
        dialog.setWindowTitle("Edit Window")
        layout = QtWidgets.QVBoxLayout(dialog)

        formLayout = QtWidgets.QFormLayout()
        layout.addLayout(formLayout)

        name_line = QtWidgets.QLineEdit(self.model.index(row, 0).data())
        formLayout.addRow('Name', name_line)
        name_line.setReadOnly(True)

        age_edit = QtWidgets.QSpinBox()
        formLayout.addRow('Age', age_edit)
        age_edit.setFocus()
        age_edit.setValue(self.model.index(row, 1).data())

        genders = 'M', 'F'
        gender_combo = QtWidgets.QComboBox()
        formLayout.addRow('Gender', gender_combo)
        gender_combo.addItems(genders)
        gender = self.model.index(row, 2).data()
        if gender and gender.upper() in genders:
            gender_combo.setCurrentIndex(genders.index(gender.upper()))
        else:
            gender_combo.setCurrentIndex(-1)

        updateButton = QtWidgets.QPushButton('Update')
        layout.addWidget(updateButton)
        updateButton.clicked.connect(dialog.accept)

        if not dialog.exec_():
            return

        self.model.setData(self.model.index(row, 1), age_edit.value(), 
            QtCore.Qt.EditRole)
        if gender_combo.currentIndex() >= 0:
            self.model.setData(self.model.index(row, 2), 
                gender_combo.currentText(), QtCore.Qt.EditRole)
        # submit all changes to the database
        self.model.submitAll()

    def refresh_table(self):
        print("rsfersh")

这在使用 FilterProxyModel 过滤列之前运行良好。 问题是:在过滤列后,没有将准确的行放入“编辑 Window”。例如,它不是从表视图中获取数据库索引行的行。

下面是参考图片。

如上图过滤后,我想编辑第 4 列(“name4”)但它正在使用第 1 列(“name1”)。如何在过滤后编辑所需的列。

QTableView的currentIndex方法returns一个QModelIndex与模型建立关联的模型,在此它是“self.proxy”而不是相对于“self.model”。另一方面,在这些情况下,使用 QDataWidgetMapper 简化了映射列的任务:

import sys
from PyQt5 import QtCore, QtWidgets, QtSql, uic


class FilterProxyModel(QtCore.QSortFilterProxyModel):
    def __init__(self, parent=None):
        super().__init__(parent)
        self._filter_value = None

    @property
    def filter_value(self):
        return self._filter_value

    @filter_value.setter
    def filter_value(self, value):
        self._filter_value = value
        self.invalidateFilter()

    def filterAcceptsRow(self, sourceRow, sourceParent):
        if self.filter_value is None:
            return super().filterAcceptsRow(sourceRow, sourceParent)
        if self.filterKeyColumn() >= 0:
            value = (
                self.sourceModel()
                .index(sourceRow, self.filterKeyColumn(), sourceParent)
                .data(self.filterRole())
            )
            return value == self.filter_value

        for column in range(self.columnCount()):
            value = (
                self.sourceModel()
                .index(sourceRow, column, sourceParent)
                .data(self.filterRole())
            )
            if value == self.filter_value:
                return True
        return False

    def setFilterRegExp(self, filter):
        self.filter_value = None
        super().setFilterRegExp(filter)


class UI(QtWidgets.QMainWindow):
    def __init__(self):
        super(UI, self).__init__()
        uic.loadUi("tableview.ui", self)
        self.tableView.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)

        db = QtSql.QSqlDatabase.addDatabase("QSQLITE")
        db.setDatabaseName("book.db")
        db.open()

        self.model = QtSql.QSqlTableModel(self)
        self.model.setTable("card")
        self.model.select()

        self.proxy = FilterProxyModel(self)
        self.proxy.setSourceModel(self.model)

        self.tableView.setModel(self.proxy)
        self.model.select()

        self.edit.clicked.connect(self.edit_items)
        self.refresh.clicked.connect(self.refresh_table)

        r = self.model.record()
        column_names = [r.field(i).name().title() for i in range(r.count())]

        self.comboBox.addItems([x for x in column_names])

        self.horizontalHeader = self.tableView.horizontalHeader()
        self.horizontalHeader.sectionClicked.connect(
            self.tableView_horizontalHeader_sectionClicked
        )
        self.lineEdit.textChanged.connect(self.lineEdit_textChanged)

    def tableView_horizontalHeader_sectionClicked(self, logicalIndex):
        menu = QtWidgets.QMenu(self)

        values = []

        for row in range(self.model.rowCount()):
            value = self.model.index(row, logicalIndex).data(self.proxy.filterRole())
            values.append(value)

        action_all = QtWidgets.QAction("All", self)
        action_all.setData(None)
        menu.addAction(action_all)
        menu.addSeparator()

        for value in sorted(list(set(values))):
            action = QtWidgets.QAction(str(value), self)
            action.setData(value)
            menu.addAction(action)

        headerPos = self.tableView.mapToGlobal(self.horizontalHeader.pos())
        posY = headerPos.y() + self.horizontalHeader.height()
        posX = headerPos.x() + self.horizontalHeader.sectionPosition(logicalIndex)

        action = menu.exec_(QtCore.QPoint(posX, posY))

        if action is not None:
            self.proxy.setFilterKeyColumn(logicalIndex)
            self.proxy.filter_value = action.data()

    def lineEdit_textChanged(self):
        search = QtCore.QRegExp(
            self.lineEdit.text(), QtCore.Qt.CaseInsensitive, QtCore.QRegExp.RegExp
        )
        self.proxy.setFilterKeyColumn(self.comboBox.currentIndex())
        self.proxy.setFilterRegExp(search)

    def edit_items(self):

        if not self.model.rowCount():
            return
        index = self.tableView.currentIndex()
        if index.isValid():
            row = index.row()
        else:
            row = 0

        name_line = QtWidgets.QLineEdit(readOnly=True)
        age_edit = QtWidgets.QSpinBox()
        gender_combo = QtWidgets.QComboBox()
        genders = "M", "F"
        gender_combo.addItems(genders)
        updateButton = QtWidgets.QPushButton("Update")

        mapper = QtWidgets.QDataWidgetMapper()
        mapper.setSubmitPolicy(QtWidgets.QDataWidgetMapper.ManualSubmit)
        mapper.setModel(self.tableView.model())
        mapper.addMapping(name_line, 0)
        mapper.addMapping(age_edit, 1)
        mapper.addMapping(gender_combo, 2)
        mapper.setCurrentIndex(row)

        dialog = QtWidgets.QDialog()
        dialog.setWindowTitle("Edit Window")

        layout = QtWidgets.QVBoxLayout(dialog)

        formLayout = QtWidgets.QFormLayout()
        layout.addLayout(formLayout)
        formLayout.addRow("Name", name_line)
        formLayout.addRow("Age", age_edit)
        formLayout.addRow("Gender", gender_combo)
        layout.addWidget(updateButton)
        updateButton.clicked.connect(dialog.accept)

        if dialog.exec_():
            mapper.submit()

    def refresh_table(self):
        print("refresh")


def main():
    app = QtWidgets.QApplication(sys.argv)
    w = UI()
    w.show()
    sys.exit(app.exec_())


if __name__ == "__main__":
    main()