Как создать фильтры для QTableView в PyQt

Я использую QTableView для отображения данных, полученных из QtSql.QSqlQuery

Я хочу знать, как я могу создать фильтры для него, как в Excel.

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

На приведенном выше изображении мне нужно получить фильтры для всех хедеров (Sh_Code, SH_Seq, Stage). Фильтры будут иметь уникальные значения в том столбце, по которому мы можем фильтровать.

Необходимый результат

Мне нужен заголовок таблицы с раскрывающимся списком, в котором перечислены все уникальные значения в этом столбце, как в Excel ниже. Нет необходимости в верхнем, стандартном фильтре... как показано на рисунке. Нужны только «Все» и уникальные «элементы столбца»

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

Это из моего приложения .NET, загруженного для большей ясности.

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


person Rao    schedule 28.12.2012    source источник


Ответы (3)


Вот пример фильтрации в PyQt с использованием QSortFilterProxyModel, QStandardItemModel и QTableView, его можно легко адаптировать к другим представлениям и моделям:

#!/usr/bin/env python
#-*- coding:utf-8 -*-

from PyQt4 import QtCore, QtGui

class myWindow(QtGui.QMainWindow):
    def __init__(self, parent=None):
        super(myWindow, self).__init__(parent)
        self.centralwidget  = QtGui.QWidget(self)
        self.lineEdit       = QtGui.QLineEdit(self.centralwidget)
        self.view           = QtGui.QTableView(self.centralwidget)
        self.comboBox       = QtGui.QComboBox(self.centralwidget)
        self.label          = QtGui.QLabel(self.centralwidget)

        self.gridLayout = QtGui.QGridLayout(self.centralwidget)
        self.gridLayout.addWidget(self.lineEdit, 0, 1, 1, 1)
        self.gridLayout.addWidget(self.view, 1, 0, 1, 3)
        self.gridLayout.addWidget(self.comboBox, 0, 2, 1, 1)
        self.gridLayout.addWidget(self.label, 0, 0, 1, 1)

        self.setCentralWidget(self.centralwidget)
        self.label.setText("Regex Filter")

        self.model = QtGui.QStandardItemModel(self)

        for rowName in range(3) * 5:
            self.model.invisibleRootItem().appendRow(
                [   QtGui.QStandardItem("row {0} col {1}".format(rowName, column))    
                    for column in range(3)
                    ]
                )

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

        self.view.setModel(self.proxy)
        self.comboBox.addItems(["Column {0}".format(x) for x in range(self.model.columnCount())])

        self.lineEdit.textChanged.connect(self.on_lineEdit_textChanged)
        self.comboBox.currentIndexChanged.connect(self.on_comboBox_currentIndexChanged)

        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     = QtGui.QMenu(self)
        self.signalMapper   = QtCore.QSignalMapper(self)  

        self.comboBox.blockSignals(True)
        self.comboBox.setCurrentIndex(self.logicalIndex)
        self.comboBox.blockSignals(True)

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

        actionAll = QtGui.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 = QtGui.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)

    @QtCore.pyqtSlot(str)
    def on_lineEdit_textChanged(self, text):
        search = QtCore.QRegExp(    text,
                                    QtCore.Qt.CaseInsensitive,
                                    QtCore.QRegExp.RegExp
                                    )

        self.proxy.setFilterRegExp(search)

    @QtCore.pyqtSlot(int)
    def on_comboBox_currentIndexChanged(self, index):
        self.proxy.setFilterKeyColumn(index)


if __name__ == "__main__":
    import sys

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

Чтобы получить требуемые результаты, всплывающее меню запускается щелчком по заголовку и заполняется уникальными значениями для этого столбца. После выбора элемента во всплывающем меню значение передается в self.proxy.setFilterRegExp(filterString), а столбец — в self.proxy.setFilterKeyColumn(filterValue).

изображение

person Community    schedule 28.12.2012
comment
Когда я набираю строку 0 col 0 в поле фильтра, ничего не фильтруется. я добавил операторы печати в on_lineEdit_textChanged on_comboBox_currentIndexChanged, но они никогда не выполняются. Я работаю над Python 2.6.4. - person Rao; 02.01.2013
comment
@PBLNarasimhaRao Забыл подключить слоты! Я обновил код, теперь он должен работать - person ; 02.01.2013
comment
он работал, соединяя сигналы self.comboBox.currentIndexChanged.connect(self.on_comboBox_currentIndexChanged) и self.lineEdit.textChanged.connect(self.on_lineEdit_textChanged) Но мне нужно, чтобы фильтры отображались в заголовках столбцов, откуда я могу их выбрать (например, как в excel). Я обновил вопрос с помощью снимка Excel, который требуется. - person Rao; 02.01.2013
comment
@PBLNarasimhaRao Проверьте мой обновленный ответ, я добавил некоторую информацию о том, как отображать всплывающее меню фильтра при нажатии на заголовок - person ; 02.01.2013
comment
можете ли вы отредактировать свой код для того, что вы предложили. Я не смог найти событие клика по заголовку. - person Rao; 02.01.2013
comment
@PBLNarasimhaRao Сигнал, который вы ищете, это sectionClicked ( int logicalIndex ). - person ; 02.01.2013
comment
я пробовал self.view.horizontalHeader.sectionClicked (self.headerclick) но получаю ошибку AttributeError: 'builtin_function_or_method' object has no attribute 'sectionClicked' - person Rao; 02.01.2013
comment
давайте продолжим это обсуждение в чате - person Rao; 02.01.2013
comment
@PBLNarasimhaRao Попробуйте это вместо этого self.view.horizontalHeader().sectionClicked (self.headerclick) - person ; 02.01.2013
comment
этот self.view.horizontalHeader().sectionClicked.connect(self.headerclick) работал, но как получить из него индекс столбца....? - person Rao; 02.01.2013
comment
@PBLNarasimhaRao sectionClicked возвращает число, которое является номером столбца, пожалуйста, создайте новый пост для любых дальнейших вопросов, чтобы не расширять эту тему - person ; 02.01.2013
comment
вопросы, которые я задаю только об этом посте ... Ваш ответ не полностью завершен. так что я получаю сомнения. Только по этой причине я попросил вас обновить свой ответ в соответствии с вашим предложением. так как я нуб в QTableview - person Rao; 02.01.2013
comment
@PBLNarasimhaRao Обратите внимание, что мой ответ полностью охватывает ваш исходный вопрос и что мне не следовало редактировать свой ответ, чтобы он соответствовал вашим новым требованиям, вместо этого нужно было создать новый пост, что-то вроде Как сортировать в QTableView, например, в excel< /я>. Я обновлю свой код, чтобы отразить изменения в вашем вопросе, хотя вы должны быть в состоянии написать код, который вам нужен, самостоятельно с помощью указателей, которые я вам дал, пожалуйста, не ожидайте, что вас будут обманывать, и учтите, что это имеет значение в вашем будущем вопросы в СО - person ; 02.01.2013
comment
@PBLNarasimhaRao Проверьте мой обновленный ответ, я отредактировал код, чтобы включить сортировку в QTableView, как в Excel - person ; 02.01.2013
comment
Спасибо за помощь. я начну новый пост для моих дальнейших сомнений. На данный момент, если я отфильтрую столбец 1 с помощью row 0 col 0 и попытаюсь отфильтровать столбец 2, он должен отображать только видимые уникальные значения столбца 2 (возможно, он должен отображать только row 0 col 1), но теперь он показывает все элементы столбца 2 (row 0 col 1, row 1 col 1 , row 2 col 1) - person Rao; 03.01.2013

Я попытался обновить ответ, приведенный выше, для PyQt5

from PyQt5 import QtCore, QtGui, QtWidgets

class myWindow(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super(myWindow, self).__init__(parent)
        self.centralwidget  = QtWidgets.QWidget(self)
        self.lineEdit       = QtWidgets.QLineEdit(self.centralwidget)
        self.view           = QtWidgets.QTableView(self.centralwidget)
        self.comboBox       = QtWidgets.QComboBox(self.centralwidget)
        self.label          = QtWidgets.QLabel(self.centralwidget)

        self.gridLayout = QtWidgets.QGridLayout(self.centralwidget)
        self.gridLayout.addWidget(self.lineEdit, 0, 1, 1, 1)
        self.gridLayout.addWidget(self.view, 1, 0, 1, 3)
        self.gridLayout.addWidget(self.comboBox, 0, 2, 1, 1)
        self.gridLayout.addWidget(self.label, 0, 0, 1, 1)

        self.setCentralWidget(self.centralwidget)
        self.label.setText("Regex Filter")

        self.model = QtGui.QStandardItemModel(self)

        for rowName in range(3*5):
            self.model.invisibleRootItem().appendRow(
                [   QtGui.QStandardItem("row {0} col {1}".format(rowName, column))    
                    for column in range(3)
                    ]
                )

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

        self.view.setModel(self.proxy)
        self.comboBox.addItems(["Column {0}".format(x) for x in range(self.model.columnCount())])

        self.lineEdit.textChanged.connect(self.on_lineEdit_textChanged)
        self.comboBox.currentIndexChanged.connect(self.on_comboBox_currentIndexChanged)

        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)  

        self.comboBox.blockSignals(True)
        self.comboBox.setCurrentIndex(self.logicalIndex)
        self.comboBox.blockSignals(True)

        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)

    @QtCore.pyqtSlot(str)
    def on_lineEdit_textChanged(self, text):
        search = QtCore.QRegExp(    text,
                                    QtCore.Qt.CaseInsensitive,
                                    QtCore.QRegExp.RegExp
                                    )

        self.proxy.setFilterRegExp(search)

    @QtCore.pyqtSlot(int)
    def on_comboBox_currentIndexChanged(self, index):
        self.proxy.setFilterKeyColumn(index)


if __name__ == "__main__":
    import sys

    app  = QtWidgets.QApplication(sys.argv)
    main = myWindow()
    main.show()
    main.resize(400, 600)
    sys.exit(app.exec_())
person Behzad Jamali    schedule 25.02.2020

Согласно ответу от @user1006989 и @Behzad Jamali: позиция меню для фильтрации по заголовку не будет всплывать в точной позиции, если в таблице больше столбцов, чем текущий порт просмотра.

Чтобы иметь правильное позиционирование всплывающего меню, используйте эту строку

posX = headerPos.x() + self.horizontalHeader.sectionViewportPosition(index)
person hbkvikas    schedule 16.04.2020