如何同时使用自定义过滤器代理模型和 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()
示例代码如下:
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()