简体   繁体   中英

How to implement a filterable List in PyQt5/PySide2?

How can I implement a simple filterable List in PyQt5/PySide2?

Test code:

import sys

from PySide2.QtCore import QAbstractListModel
from PySide2.QtGui import Qt
from PySide2.QtWidgets import QApplication, QWidget, QVBoxLayout, QLineEdit, QListView


class MyModel(QAbstractListModel):
    def __init__(self):
        QAbstractListModel.__init__(self)
        self.test_data = ['Test','abc', 'Test2', 'aaa', 'bbb']
        self.filtered_data = self.test_data
        self.filter = ''
        self.refresh()

    def refresh(self):
        if self.filter:
            self.filtered_data  = [x for x in self.test_data if self.filter in x]
        else:
            self.filtered_data = self.test_data

    def data(self, index, role):
        if role == Qt.DisplayRole:
            return self.filtered_data[index.row()]

    def rowCount(self, index):
        return len(self.filtered_data)


def text_changed():
    model.filter = le.text()
    model.refresh()


if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = QWidget()

    le = QLineEdit()
    le.textChanged.connect(text_changed)
    model = MyModel()
    lv = QListView(model=model)

    layout = QVBoxLayout()
    layout.addWidget(le)
    layout.addWidget(lv)

    window.setLayout(layout)
    window.show()
    app.exec_()


The Filter itself works, but the ListView is not refreshed properly. When I change the filter, the list is not updated, until I move with the mouse to a entry.

That you change the information of an internal list does not notify in sight that something has changed, for each type of change certain methods must be invoked before or after the change.

In this case, beginResetModel and endResetModel are enough

def refresh(self):
    self.beginResetModel()
    if self.filter:
        self.filtered_data  = [x for x in self.test_data if self.filter in x]
    else:
        self.filtered_data = self.test_data
    self.endResetModel()

Instead another more elegant solution is to use a QSortFilterProxyModel:

class MyModel(QAbstractListModel):
    def __init__(self):
        QAbstractListModel.__init__(self)
        self.test_data = ['Test','abc', 'Test2', 'aaa', 'bbb']
        self.filtered_data = self.test_data

    def data(self, index, role):
        if role == Qt.DisplayRole:
            return self.filtered_data[index.row()]

    def rowCount(self, index):
        return len(self.filtered_data)


def text_changed():
    proxy.filter = le.text()


class SortFilterProxyModel(QSortFilterProxyModel):
    def __init__(self, parent=None):
        super().__init__(parent)
        self._filter = ""
    def filterAcceptsRow(self, sourceRow, sourceParent):
        index = self.sourceModel().index(sourceRow, 0, sourceParent)
        if self.filter:
            return self.filter in index.data()
        return True

    @property
    def filter(self):
        return self._filter
    
    @filter.setter
    def filter(self, f):
        self._filter = f
        self.invalidateFilter()


if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = QWidget()

    le = QLineEdit()
    le.textChanged.connect(text_changed)
    model = MyModel()
    proxy = SortFilterProxyModel()
    proxy.setSourceModel(model)
    lv = QListView(model=proxy)

    layout = QVBoxLayout()
    layout.addWidget(le)
    layout.addWidget(lv)

    window.setLayout(layout)
    window.show()
    app.exec_()

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM