首页 > 解决方案 > 如何在pyQt5中根据两个QcomboBoxes中选择的项目过滤行

问题描述

有四列,我想让搜索栏根据两个 QComboBox 中选择的值一起过滤行。目前,我一次只能过滤一列中的行。

也可以为两个列启用搜索栏。我试图将某种 AND 运算符应用于组合框,但失败了。

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

from PyQt5 import QtCore, QtGui, QtWidgets
import pandas as pd


class PandasModel(QtCore.QAbstractTableModel):
    def __init__(self, df=pd.DataFrame(), parent=None):
        QtCore.QAbstractTableModel.__init__(self, parent=parent)
        self._df = df.copy()
        self.bolds = dict()

    def toDataFrame(self):
        return self._df.copy()

    def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
        if orientation == QtCore.Qt.Horizontal:
            if role == QtCore.Qt.DisplayRole:
                try:
                    return self._df.columns.tolist()[section]
                except (IndexError,):
                    return QtCore.QVariant()
            elif role == QtCore.Qt.FontRole:
                return self.bolds.get(section, QtCore.QVariant())
        elif orientation == QtCore.Qt.Vertical:
            if role == QtCore.Qt.DisplayRole:
                try:
                    # return self.df.index.tolist()
                    return self._df.index.tolist()[section]
                except (IndexError,):
                    return QtCore.QVariant()
        return QtCore.QVariant()

    def setFont(self, section, font):
        self.bolds[section] = font
        self.headerDataChanged.emit(QtCore.Qt.Horizontal, 0, self.columnCount())

    def data(self, index, role=QtCore.Qt.DisplayRole):
        if role != QtCore.Qt.DisplayRole:
            return QtCore.QVariant()

        if not index.isValid():
            return QtCore.QVariant()

        return QtCore.QVariant(str(self._df.iloc[index.row(), index.column()]))

    def setData(self, index, value, role):
        row = self._df.index[index.row()]
        col = self._df.columns[index.column()]
        if hasattr(value, "toPyObject"):
            # PyQt4 gets a QVariant
            value = value.toPyObject()
        else:
            # PySide gets an unicode
            dtype = self._df[col].dtype
            if dtype != object:
                value = None if value == "" else dtype.type(value)
        self._df.set_value(row, col, value)
        return True

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

    def columnCount(self, parent=QtCore.QModelIndex()):
        return len(self._df.columns)

    def sort(self, column, order):
        colname = self._df.columns.tolist()[column]
        self.layoutAboutToBeChanged.emit()
        self._df.sort_values(
            colname, ascending=order == QtCore.Qt.AscendingOrder, inplace=True
        )
        self._df.reset_index(inplace=True, drop=True)
        self.layoutChanged.emit()


class CheckablePandasModel(PandasModel):
    def __init__(self, df=pd.DataFrame(), parent=None):
        super().__init__(df, parent)
        self.checkable_values = set()
        self._checkable_column = -1

    @property
    def checkable_column(self):
        return self._checkable_column

    @checkable_column.setter
    def checkable_column(self, column):
        if self.checkable_column == column:
            return
        last_column = self.checkable_column
        self._checkable_column = column

        if last_column == -1:
            self.beginInsertColumns(
                QtCore.QModelIndex(), self.checkable_column, self.checkable_column
            )
            self.endInsertColumns()

        elif self.checkable_column == -1:
            self.beginRemoveColumns(QtCore.QModelIndex(), last_column, last_column)
            self.endRemoveColumns()
        for c in (last_column, column):
            if c > 0:
                self.dataChanged.emit(
                    self.index(0, c), self.index(self.columnCount() - 1, c)
                )

    def columnCount(self, parent=QtCore.QModelIndex()):
        return super().columnCount(parent) + (1 if self.checkable_column != -1 else 0)

    def data(self, index, role=QtCore.Qt.DisplayRole):
        if self.checkable_column != -1:
            row, col = index.row(), index.column()
            if col == self.checkable_column:
                if role == QtCore.Qt.CheckStateRole:
                    return (
                        QtCore.Qt.Checked
                        if row in self.checkable_values
                        else QtCore.Qt.Unchecked
                    )
                return QtCore.QVariant()
            if col > self.checkable_column:
                index = index.sibling(index.row(), col - 1)
        return super().data(index, role)

    def setData(self, index, value, role):
        if self.checkable_column != -1:
            row, col = index.row(), index.column()
            if col == self.checkable_column:
                if role == QtCore.Qt.CheckStateRole:
                    if row in self.checkable_values:
                        self.checkable_values.discard(row)
                    else:
                        self.checkable_values.add(row)
                    self.dataChanged.emit(index, index, (role,))
                    return True
                return False
            if col > self.checkable_column:
                index = index.sibling(index.row(), col - 1)
        return super().setData(index, value, role)

    def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
        if self.checkable_column != -1:
            if section == self.checkable_column and orientation == QtCore.Qt.Horizontal:
                return QtCore.QVariant()
            if section > self.checkable_column and orientation == QtCore.Qt.Horizontal:
                section -= 1
        return super().headerData(section, orientation, role)

    def flags(self, index):
        if self.checkable_column != -1:
            col = index.column()
            if col == self.checkable_column:
                return QtCore.Qt.ItemIsUserCheckable | QtCore.Qt.ItemIsEnabled
            if col > self.checkable_column:
                index = index.sibling(index.row(), col - 1)
        return super().flags(index)


class CustomProxyModel(QtCore.QSortFilterProxyModel):
    def __init__(self, parent=None):
        super().__init__(parent)
        self._filters = dict()

    @property
    def filters(self):
        return self._filters

    def setFilter(self, expresion, column):
        if expresion:
            self.filters[column] = expresion
        elif column in self.filters:
            del self.filters[column]
        self.invalidateFilter()

    def filterAcceptsRow(self, source_row, source_parent):
        for column, expresion in self.filters.items():
            text = self.sourceModel().index(source_row, column, source_parent).data()
            regex = QtCore.QRegExp(
                expresion, QtCore.Qt.CaseInsensitive, QtCore.QRegExp.RegExp
            )
            if regex.indexIn(text) == -1:
                return False
        return True


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

        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.comboBox_2, 0, 0, 1, 1)

        self.setCentralWidget(self.centralwidget)

        self.load_sites()
        self.comboBox.addItems(["{0}".format(col) for col in self.model._df['Facility'][:]])
        self.comboBox_2.addItems(["{0}".format(col) for col in self.model._df['Date'][:]])
        self.lineEdit.textChanged.connect(self.on_lineEdit_textChanged)

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

    def load_sites(self):
        df = pd.DataFrame(
            {
                "Date": ["1_April", "2_April", "7_May", "10_July","11_April", "22_April", "17_May", "1_July"],
                "status": ["open", "open", "open", "closed","open", "open", "open", "closed"],
                "Facility": ["PA ", "PB", "south PA", "east PB","PC ", "PC North", "West PA", "South PB"],
                "data_quality": ["poor", "moderate", "high", "high","high", "high","poor", "moderate"],
            }
        )

        # df = pd.read_csv('HMIS.csv')

        self.model = CheckablePandasModel(df)
        self.model.checkable_column = 0
        self.proxy = CustomProxyModel(self)
        self.proxy.setSourceModel(self.model)
        self.view.setModel(self.proxy)
        self.view.resizeColumnsToContents()

    @QtCore.pyqtSlot(int)
    def on_view_horizontalHeader_sectionClicked(self, logicalIndex):
        if logicalIndex == self.model.checkable_column:
            return

        self.menuValues = QtWidgets.QMenu(self)
        self.comboBox.blockSignals(True)
        self.comboBox.setCurrentIndex(
            logicalIndex - 1
            if logicalIndex > self.model.checkable_column
            else logicalIndex
        )
        self.comboBox.blockSignals(True)

        valuesUnique = set(
            self.proxy.index(i, logicalIndex).data()
            for i in range(self.proxy.rowCount())
        )

        actionAll = QtWidgets.QAction("All", self)
        self.menuValues.addAction(actionAll)
        self.menuValues.addSeparator()
        for i, name in enumerate(valuesUnique):
            action = QtWidgets.QAction(name, self)
            action.setData(i)
            self.menuValues.addAction(action)

        headerPos = self.view.mapToGlobal(self.horizontalHeader.pos())
        pos = headerPos + QtCore.QPoint(
            self.horizontalHeader.sectionPosition(logicalIndex),
            self.horizontalHeader.height(),
        )
        action = self.menuValues.exec_(pos)
        if action is not None:
            font = QtGui.QFont()
            if action.data() is None:  # all
                self.proxy.setFilter("", logicalIndex)
            else:
                font.setBold(True)
                self.proxy.setFilter(action.text(), logicalIndex)
            self.model.setFont(logicalIndex - 1, font)

    @QtCore.pyqtSlot(str)
    def on_lineEdit_textChanged(self, text):
        self.proxy.setFilter(text, self.comboBox.currentIndex() + 1)


if __name__ == "__main__":
    import sys

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

标签: python-3.xpyqt5

解决方案


推荐阅读