如何一次使用自定义FilterProxyModel和QtCore.Qt.EditRole?

问题描述

下面是示例代码

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过滤列之前。 问题是:过滤列后,没有将确切的行放入“编辑窗口”。例如,它不是从tableview获取数据库索引行。

下面是参考图像。

enter image description here

如上图,在过滤器之后,我想编辑第4列(“ name4”),但它占用第1列(“ name1”)。过滤后如何编辑必填列。

解决方法

QTableView的currentIndex方法返回与在模型中建立的模型相关联的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()