I\'m using QtableView to show my logs and to filter them by column, QSortFilterProxyModel is used. If i filter one column using certain value, and with the filtered data, i
You must create a class that inherits from QSortFilterProxyModel
, and overwrite the filterAcceptsRow
method where False is returned if at least one item is not satisfied and True if all are satisfied.
class SortFilterProxyModel(QSortFilterProxyModel):
def __init__(self, *args, **kwargs):
QSortFilterProxyModel.__init__(self, *args, **kwargs)
self.filters = {}
def setFilterByColumn(self, regex, column):
self.filters[column] = regex
self.invalidateFilter()
def filterAcceptsRow(self, source_row, source_parent):
for key, regex in self.filters.items():
ix = self.sourceModel().index(source_row, key, source_parent)
if ix.isValid():
text = self.sourceModel().data(ix).toString()
if not text.contains(regex):
return False
return True
Example:
def random_word():
letters = "abcdedfg"
word = "".join([choice(letters) for _ in range(randint(4, 7))])
return word
class Widget(QWidget):
def __init__(self, *args, **kwargs):
QWidget.__init__(self, *args, **kwargs)
self.setLayout(QVBoxLayout())
tv1 = QTableView(self)
tv2 = QTableView(self)
model = QStandardItemModel(8, 4, self)
proxy = SortFilterProxyModel(self)
proxy.setSourceModel(model)
tv1.setModel(model)
tv2.setModel(proxy)
self.layout().addWidget(tv1)
self.layout().addWidget(tv2)
for i in range(model.rowCount()):
for j in range(model.columnCount()):
item = QStandardItem()
item.setData(random_word(), Qt.DisplayRole)
model.setItem(i, j, item)
flayout = QFormLayout()
self.layout().addLayout(flayout)
for i in range(model.columnCount()):
le = QLineEdit(self)
flayout.addRow("column: {}".format(i), le)
le.textChanged.connect(lambda text, col=i:
proxy.setFilterByColumn(QRegExp(text, Qt.CaseSensitive, QRegExp.FixedString),
col))
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
w = Widget()
w.show()
sys.exit(app.exec_())
Here is a bit more advanced implementation, it has more convenient clearing and supports different column combination modes: AND (the same as the original, all specified columns must match), OR.
PySide2, Python 3.
import re
from PySide2.QtCore import Qt, QSortFilterProxyModel
class MultiFilterMode:
AND = 0
OR = 1
class MultiFilterProxyModel(QSortFilterProxyModel):
def __init__(self, *args, **kwargs):
QSortFilterProxyModel.__init__(self, *args, **kwargs)
self.filters = {}
self.multi_filter_mode = MultiFilterMode.AND
def setFilterByColumn(self, column, regex):
if isinstance(regex, str):
regex = re.compile(regex)
self.filters[column] = regex
self.invalidateFilter()
def clearFilter(self, column):
del self.filters[column]
self.invalidateFilter()
def clearFilters(self):
self.filters = {}
self.invalidateFilter()
def filterAcceptsRow(self, source_row, source_parent):
if not self.filters:
return True
results = []
for key, regex in self.filters.items():
text = ''
index = self.sourceModel().index(source_row, key, source_parent)
if index.isValid():
text = self.sourceModel().data(index, Qt.DisplayRole)
if text is None:
text = ''
results.append(regex.match(text))
if self.multi_filter_mode == MultiFilterMode.OR:
return any(results)
return all(results)