如何使用 QAbstractTableModel 而不是 QSortFilterProxyModel 进行排序和过滤

Posted

技术标签:

【中文标题】如何使用 QAbstractTableModel 而不是 QSortFilterProxyModel 进行排序和过滤【英文标题】:How to sort and filter using QAbstractTableModel instead of QSortFilterProxyModel 【发布时间】:2015-08-22 06:01:42 【问题描述】:

我尝试通过将这个属性与当前显示在 QComboBox 中的文本进行比较,从 QAbstractTableModel 的 data() 方法内部通过 self.category 属性过滤 self.items 的对象。但是,代码无法正常运行。

不应该将 QAbstractTableModel 的 data() 方法用作“代理模型的accepts row() 方法的替代品吗?

不使用QSortFilterProxyModel是否可以实现过滤?如果我们必须使用代理来过滤模型项,那么最 Pythonic 的方式是什么?

from PySide import QtGui, QtCore

class Item(object):
    def __init__(self):
        self.ID=None
        self.name=None
        self.category=None

class TableModel(QtCore.QAbstractTableModel):
    def __init__(self, parent=None):
        QtCore.QAbstractTableModel.__init__(self, parent)
        self.items = []
        self.filterCategory = None

    def rowCount(self, parent=QtCore.QModelIndex()):
        return len( [item for item in self.items if item.category==self.filterCategory] )

    def columnCount(self, parent=QtCore.QModelIndex()):
        return 1

    def data(self, index, role):
        if not index.isValid(): return 
        row=index.row()

        item=self.items[row]
        if item.category!=self.filterCategory:
            return  

        if role == QtCore.Qt.DisplayRole:
            return self.items[row].name

        if role == QtCore.Qt.UserRole:
            return self.items[row]

    def insertRows(self, row, item, column=1, index=QtCore.QModelIndex()):
        self.beginInsertRows(QtCore.QModelIndex(), row, row+1)
        self.items.append(item)
        self.endInsertRows()

    def setFilter(self, comboText):
        self.filterCategory = comboText
        self.layoutChanged.emit()

    def filterAcceptsRow(self, row, proc):
        index=self.sourceModel().index(row, 0, proc)
        item=self.sourceModel().data(index, QtCore.Qt.UserRole)
        if not item: return True

        resourceType=item.category
        if self.filters.get(category)==False:
            return False       

        if self.searchText and len(self.searchText)>0 and item.searchString(self.searchText)==False:
            return False
        return True

class MyWindow(QtGui.QWidget):
    def __init__(self, *args):
        QtGui.QWidget.__init__(self, *args)
        vLayout=QtGui.QVBoxLayout(self)
        self.setLayout(vLayout)

        self.tableModel = TableModel()     

        self.ViewA=QtGui.QTableView(self)
        self.ViewA.clicked.connect(self.viewClicked)
        vLayout.addWidget(self.ViewA)

        for row in range(5):
            item=Item()
            item.ID=row
            if item.ID%2: item.category='Pet'
            else: item.category='Birds'
            item.name='%s_%s'%(item.category, row)

            self.tableModel.insertRows(row, item)

        self.ViewA.setModel(self.tableModel)

        self.combo=QtGui.QComboBox()
        self.combo.addItems(['Pet','Birds'])
        self.combo.activated.connect(self.comboActivated)
        vLayout.addWidget(self.combo)

        currentComboCategory=self.combo.currentText()
        self.tableModel.setFilter(currentComboCategory)


    def viewClicked(self, indexClicked):
        print('indexClicked() row: %s  column: %s'%(indexClicked.row(), indexClicked.column() ))

    def comboActivated(self, arg=None):
        comboText=self.combo.currentText()
        self.tableModel.setFilter(comboText)

if __name__ == "__main__":
    app = QtGui.QApplication(sys.argv)
    w = MyWindow()
    w.show()
    sys.exit(app.exec_())

【问题讨论】:

【参考方案1】:

下面的“no-more-proxy”代码展示了如何从QAbstractTableModel内部而不是代理内部进行排序和过滤:

import sys, os
from PyQt import QtGui, QtCore

class Item(object):
    def __init__(self,ID=None,name=None,category=None,area=None):
        self.ID=ID
        self.name=name
        self.category=category
        self.area='South'

class TableModel(QtCore.QAbstractTableModel):
    def __init__(self, parent=None):
        QtCore.QAbstractTableModel.__init__(self, parent)
        self.currentItems=[]
        self.items = []
        self.filterCategory = None
        self.searchField = None

        self.mainColumn=0
        self.order=QtCore.Qt.SortOrder.DescendingOrder

    def rowCount(self, parent=QtCore.QModelIndex()):
        return len( self.currentItems )

    def columnCount(self, parent=QtCore.QModelIndex()):
        return 5

    def data(self, index, role):
        if not index.isValid(): return 
        row=index.row()
        column=index.column()

        item=self.currentItems[row]

        if role == QtCore.Qt.DisplayRole:
            if column==0: return item.ID
            elif column==1: return item.name
            elif column==2: return item.category
            elif column==4 or column==5: return item.area

        if role == QtCore.Qt.UserRole:
            return item

    def insertRows(self, row, item, column=1, index=QtCore.QModelIndex()):
        self.beginInsertRows(QtCore.QModelIndex(), row, row+1)
        self.items.append(item)
        self.endInsertRows()

    def setFilter(self, comboText=None, searchText=None, mainColumn=None, order=None):
        if comboText: self.filterCategory=comboText
        if searchText: self.searchText=searchText
        if mainColumn!=None: self.mainColumn=mainColumn
        self.order=order

        self.currentItems=[item for item in self.items if item.category==self.filterCategory]
        if searchText:
            self.currentItems=[item for item in self.currentItems if searchText in '%s%s%s'%(item.ID, item.name, item.category)]

        values=[]
        if self.mainColumn==0: values=[[item.ID, item, False] for item in self.currentItems]
        elif self.mainColumn==1: values=[[item.name, item, False] for item in self.currentItems]
        elif self.mainColumn==2: values=[[item.category, item, False] for item in self.currentItems]
        elif self.mainColumn==3 or self.mainColumn==4: values=[[item.area, item, False] for item in self.currentItems]  

        keys=sorted([value[0] for value in values if isinstance(value, list)])
        if self.order==QtCore.Qt.AscendingOrder: keys=list(reversed(keys))

        filtered=[]
        for key in keys:
            for each in values:
                if each[0]!=key: continue
                if each[2]==True: continue
                item=each[1]

                filtered.append(item)
                each[2]=True

        if filtered: self.currentItems=filtered

        self.layoutChanged.emit()

class ItemDelegate(QtGui.QItemDelegate):
    def __init__(self, parent):
        QtGui.QItemDelegate.__init__(self, parent)

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

    def createEditor(self, parent, option, index):
        tableView=parent.parent()
        model=tableView.model()

        item=model.data(index, QtCore.Qt.UserRole)

        combo=QtGui.QComboBox(parent)
        combo.addItems(['South','West','North','East'])
        combo.currentIndexChanged.connect(self.comboIndexChanged)    

        if item.area:
            comboIndex=combo.findText(item.area)
            if comboIndex>=0:
                combo.setCurrentIndex(comboIndex)
        else: combo.setCurrentIndex(0)

        return combo

    def comboIndexChanged(self):
        self.commitData.emit(self.sender())

    def setModelData(self, combo, model, index): 
        item=model.data(index, QtCore.Qt.UserRole)
        comboText=combo.currentText()
        item.area=comboText

class MyWindow(QtGui.QWidget):
    def __init__(self, *args):
        QtGui.QWidget.__init__(self, *args)
        vLayout=QtGui.QVBoxLayout(self)
        self.setLayout(vLayout)

        self.tableModel = TableModel()   

        self.searchLine=QtGui.QLineEdit()
        vLayout.addWidget(self.searchLine)

        self.searchLine.textEdited.connect(self.searchLineEditied)
        self.searchLine.returnPressed.connect(self.searchLineEditied)

        self.tableView=QtGui.QTableView(self)
        self.tableView.setSortingEnabled(True)
        self.tableView.horizontalHeader().setResizeMode(QtGui.QHeaderView.Stretch) 
        self.tableView.setShowGrid(False) 
        self.tableView.setSelectionBehavior(QtGui.QTableView.SelectRows)
        self.tableView.setAlternatingRowColors(True)

        self.delegate=ItemDelegate(self.tableView)
        self.tableView.setItemDelegate(self.delegate)

        self.tableView.clicked.connect(self.viewClicked)
        vLayout.addWidget(self.tableView)

        for row in range(15):
            if row%2:  category='Pet' 
            else: category='Birds'
            item=Item(category=category, ID=row, name='%s_%s'%(category,row)) 
            self.tableModel.insertRows(row, item)

        self.tableView.setModel(self.tableModel)

        self.combo=QtGui.QComboBox()
        self.combo.addItems(['Pet','Birds'])
        self.combo.activated.connect(self.comboActivated)
        vLayout.addWidget(self.combo)

        currentComboCategory=self.combo.currentText()
        self.tableModel.setFilter(currentComboCategory)

        self.horizontalHeader=self.tableView.horizontalHeader()
        self.horizontalHeader.sortIndicatorChanged.connect(self.headerTriggered)
        self.addComboDelegates()

    def headerTriggered(self, mainColumn=None, order=None):
        self.tableModel.setFilter(mainColumn=mainColumn, order=order)
        self.deleteComboDelegates()
        self.addComboDelegates() 

    def comboActivated(self, comboIndex=None):
        self.deleteComboDelegates()
        comboText=self.combo.currentText()
        self.tableModel.setFilter(comboText=comboText)
        self.addComboDelegates()
        self.tableModel.layoutChanged.emit()

    def searchLineEditied(self, searchText=None):
        self.tableModel.setFilter(searchText=searchText)

    def viewClicked(self, indexClicked):
        item=self.tableModel.data(indexClicked, QtCore.Qt.UserRole)
        print 'ID: %s, name: %s, category: %s'%(item.ID,item.name,item.category)

    def deleteComboDelegates(self):
        for row in range(self.tableModel.rowCount()):
            index=self.tableModel.index(row, 3, QtCore.QModelIndex())
            self.tableView.closePersistentEditor(index)

    def addComboDelegates(self):
        for row in range(self.tableModel.rowCount()):
            index=self.tableModel.index(row, 3, QtCore.QModelIndex())
            self.tableView.openPersistentEditor(index)


if __name__ == "__main__":
    app = QtGui.QApplication(sys.argv)
    w = MyWindow()
    w.show()
    sys.exit(app.exec_())

【讨论】:

以上是关于如何使用 QAbstractTableModel 而不是 QSortFilterProxyModel 进行排序和过滤的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 beginMoveRows 在 QTableView (QAbstractTableModel) 中移动一行?

使用 QAbstractTableModel(模型/视图)时如何将“选择一个...”添加到 QComboBox?

QAbstractTableModel 和 QSortFilterProxyModel - 如何清除数据和更新视图

使用排序的 QAbstractTableModel 时如何将“选择项目...”添加到 QComboBox?

如何使用不同的行数更新 QAbstractTableModel 中的数据

如何使用pyQt在qml中的python文件中显示QAbstractTableModel