Как создать строки фильтра для Qtableview с помощью QSqlTableModel?

Можно ли создать фильтр строк для Qtableview с помощью QSqlTableModel. Я пою QSqlTableModel, чтобы показать данные о Qtableview из SQLite, которые работают. Но я пытаюсь фильтровать строки. Когда я выполняю код, который я получаю ниже, ошибка

строка 44, для строки в диапазоне (self.model.rowCount()) AttributeError: объект «QSqlTableModel» не имеет атрибута «элемент»

Я пробовал, но не получил никакого решения. Можно ли это сделать с помощью QSqlTableModel? я хочу показывать отфильтрованные элементы из таблицы, когда я нажимаю на любой из столбцов Qtableview. Я использую Qtableview с QSqlTableModel.

Я хочу сделать так, как показано на изображении

введите здесь описание изображения

from PyQt5 import QtCore, QtGui, QtWidgets, QtSql
from PyQt5.QtSql import *


class myWindow(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super(myWindow, self).__init__(parent)
        self.centralwidget  = QtWidgets.QWidget(self)
        self.view           = QtWidgets.QTableView(self.centralwidget)

        self.gridLayout = QtWidgets.QGridLayout(self.centralwidget)

        self.gridLayout.addWidget(self.view, 1, 0, 1, 3)

        self.setCentralWidget(self.centralwidget)

       # self.model = QtGui.QStandardItemModel(self)

        db = QSqlDatabase.addDatabase("QSQLITE")
        db.setDatabaseName("model.db")
        db.open()

        self.model = QSqlTableModel(self)
        self.model.setTable("sheet")
        self.model.select()
        self.view.setModel(self.model)

        self.proxy = QtCore.QSortFilterProxyModel(self)
        self.proxy.setSourceModel(self.model)

        self.view.setModel(self.proxy)


        self.horizontalHeader = self.view.horizontalHeader()
        self.horizontalHeader.sectionClicked.connect(self.on_view_horizontalHeader_sectionClicked)

    @QtCore.pyqtSlot(int)
    def on_view_horizontalHeader_sectionClicked(self, logicalIndex):
        self.logicalIndex   = logicalIndex
        self.menuValues     = QtWidgets.QMenu(self)
        self.signalMapper   = QtCore.QSignalMapper(self)  

        valuesUnique = [    self.model.item(row, self.logicalIndex).text()
                            for row in range(self.model.rowCount())
                            ]

        actionAll = QtWidgets.QAction("All", self)
        actionAll.triggered.connect(self.on_actionAll_triggered)
        self.menuValues.addAction(actionAll)
        self.menuValues.addSeparator()


        for actionNumber, actionName in enumerate(sorted(list(set(valuesUnique)))):              
            action = QtWidgets.QAction(actionName, self)
            self.signalMapper.setMapping(action, actionNumber)  
            action.triggered.connect(self.signalMapper.map)  
            self.menuValues.addAction(action)

        self.signalMapper.mapped.connect(self.on_signalMapper_mapped)  

        headerPos = self.view.mapToGlobal(self.horizontalHeader.pos())        

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

        self.menuValues.exec_(QtCore.QPoint(posX, posY))

    @QtCore.pyqtSlot()
    def on_actionAll_triggered(self):
        filterColumn = self.logicalIndex
        filterString = QtCore.QRegExp(  "",
                                        QtCore.Qt.CaseInsensitive,
                                        QtCore.QRegExp.RegExp
                                        )

        self.proxy.setFilterRegExp(filterString)
        self.proxy.setFilterKeyColumn(filterColumn)

    @QtCore.pyqtSlot(int)
    def on_signalMapper_mapped(self, i):
        stringAction = self.signalMapper.mapping(i).text()
        filterColumn = self.logicalIndex
        filterString = QtCore.QRegExp(  stringAction,
                                        QtCore.Qt.CaseSensitive,
                                        QtCore.QRegExp.FixedString
                                        )

        self.proxy.setFilterRegExp(filterString)
        self.proxy.setFilterKeyColumn(filterColumn)


if __name__ == "__main__":
    import sys

    app  = QtWidgets.QApplication(sys.argv)
    main = myWindow()
    main.show()
    main.resize(400, 600)
    sys.exit(app.exec_())

person user3030327    schedule 25.07.2020    source источник


Ответы (1)


QSortFilterProxyModel реализован для сравнения строк, но в вашем случае у вас есть разные типы значений, поэтому необходимо будет реализовать собственный фильтр.

Для обработки различных типов данных, связанных с каждой опцией, в QAction значение сохраняется и доступно с помощью setData() и data() соответственно.

С другой стороны, я считаю ненужным использовать QSignalMapper или сигналы, поскольку метод exec_() возвращает QAction, если он выбран, или None в противном случае.

Чтобы получить доступ к значению каждого элемента, необходимо использовать метод data() модели:

from PyQt5 import QtCore, QtGui, QtWidgets, QtSql


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 True
        value = (
            self.sourceModel()
            .index(sourceRow, self.filterKeyColumn(), sourceParent)
            .data(self.filterRole())
        )
        return value == self.filter_value


class myWindow(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super(myWindow, self).__init__(parent)
        self.centralwidget = QtWidgets.QWidget(self)
        self.view = QtWidgets.QTableView(self.centralwidget)

        self.gridLayout = QtWidgets.QGridLayout(self.centralwidget)

        self.gridLayout.addWidget(self.view, 1, 0, 1, 3)

        self.setCentralWidget(self.centralwidget)

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

        self.model = QtSql.QSqlTableModel(self)
        self.model.setTable("sheet")
        self.model.select()
        self.view.setModel(self.model)

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

        self.view.setModel(self.proxy)

        self.horizontalHeader = self.view.horizontalHeader()
        self.horizontalHeader.sectionClicked.connect(
            self.on_view_horizontalHeader_sectionClicked
        )

    @QtCore.pyqtSlot(int)
    def on_view_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.view.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()


if __name__ == "__main__":
    import sys

    app = QtWidgets.QApplication(sys.argv)
    main = myWindow()
    main.show()
    main.resize(400, 600)
    sys.exit(app.exec_())
person eyllanesc    schedule 02.08.2020
comment
Во втором столбце только числа. Получение ошибки. action = QtWidgets.QAction(actionName, self) TypeError: аргументы не соответствуют ни одному перегруженному вызову: QAction(parent: QObject = None): аргумент 1 имеет неожиданный тип 'int' QAction(str, parent: QObject = None): аргумент 1 имеет неожиданный тип 'int' QAction (QIcon, str, parent: QObject = None): аргумент 1 имеет неожиданный тип 'int' - person user3030327; 02.08.2020
comment
@user3030327 user3030327 изображение, которое вы показываете, не отражает то, на что вы указываете, вы можете поделиться файлом model.db - person eyllanesc; 02.08.2020
comment
да. Я не поделился содержимым базы данных, это моя ошибка. На самом деле я, хотя это был пример. На самом деле в моем содержимом базы данных есть текст некоторых столбцов, а также некоторые столбцы также имеют только числовые значения. - person user3030327; 02.08.2020
comment
@user3030327 попробуйте еще раз - person eyllanesc; 02.08.2020
comment
@user3030327 user3030327 Изображения не помогают, попробуйте мое новое решение, если оно работает, вам не нужно делиться .db, если это не работает, мне понадобится ваш .db, поэтому, если вы можете поделиться им через диск, drobox, и т. д. или другие подобные средства. - person eyllanesc; 02.08.2020
comment
Ваше новое решение работает, и это очень ценное решение. - person user3030327; 02.08.2020
comment
Давайте продолжим обсуждение в чате. - person user3030327; 02.08.2020