Jun-02-2022, 07:07 PM
You can also use QTreeview with column filters.
import sys import re from PyQt5 import QtWidgets, QtGui, QtCore, QtSql TRACE = True MY_TABLE = "items" db = QtSql.QSqlDatabase.addDatabase("QSQLITE") db.setDatabaseName("inventory.db") modelQuery = QtSql.QSqlQueryModel() modelTable = QtSql.QSqlRelationalTableModel() g_hits_count = 0 g_total_count = 0 g_selected_count = 0 def trace(message): if TRACE: print(message) def _human_key(key): parts = re.split(r"(\d*\.\d+|\d+)", key) return tuple( (e.swapcase() if i % 2 == 0 else float(e)) for i, e in enumerate(parts) ) class FilterHeader(QtWidgets.QHeaderView): filterActivated = QtCore.pyqtSignal() def __init__(self, parent): super().__init__(QtCore.Qt.Horizontal, parent) self._editors = [] self._padding = 4 self.setStretchLastSection(True) self.setDefaultAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter) self.setSortIndicatorShown(False) self.sectionResized.connect(self.adjustPositions) parent.horizontalScrollBar().valueChanged.connect(self.adjustPositions) def setFilterBoxes(self, count): while self._editors: editor = self._editors.pop() editor.deleteLater() for index in range(count): editor = QtWidgets.QLineEdit(self.parent()) editor.setPlaceholderText("Filter") editor.setClearButtonEnabled(True) editor.textChanged.connect(self.textChanged) self._editors.append(editor) self.adjustPositions() def textChanged(self): self.filterActivated.emit() def sizeHint(self): size = super().sizeHint() if self._editors: height = self._editors[0].sizeHint().height() size.setHeight(size.height() + height + self._padding) return size def updateGeometries(self): if self._editors: height = self._editors[0].sizeHint().height() self.setViewportMargins(0, 0, 0, height + self._padding) else: self.setViewportMargins(0, 0, 0, 0) super().updateGeometries() self.adjustPositions() def adjustPositions(self): for index, editor in enumerate(self._editors): height = editor.sizeHint().height() editor.move( self.sectionPosition(index) - self.offset() + 2, height + (self._padding // 2), ) editor.resize(self.sectionSize(index), height) def filterText(self, index): if 0 <= index < len(self._editors): return self._editors[index].text() return "" def setFilterText(self, index, text): if 0 <= index < len(self._editors): self._editors[index].setText(text) def clearFilters(self): for editor in self._editors: editor.clear() class HumanProxyModel(QtCore.QSortFilterProxyModel): def lessThan(self, source_left, source_right): data_left = source_left.data() data_right = source_right.data() return True if type(data_left) == type(data_right) == str: return _human_key(data_left) < _human_key(data_right) return super(HumanProxyModel, self).lessThan(source_left, source_right) @property def filters(self): if not hasattr(self, "_filters"): self._filters = [] return self._filters @filters.setter def filters(self, filters): self._filters = filters self.invalidateFilter() global g_hits_count g_hits_count = self.rowCount() def filterAcceptsRow(self, sourceRow, sourceParent): for i, text in self.filters: if 0 <= i < self.sourceModel().columnCount(): ix = self.sourceModel().index(sourceRow, i, sourceParent) data = ix.data() if text not in data.lower(): return False return True class winMain(QtWidgets.QMainWindow): cur_row = -1 row_id = -1 def __init__(self, parent=None): super().__init__(parent) self.setWindowTitle("Music List") self.setupUi() self.setGeometry(0, 0, 900, 800) self.show() def closeEvent(self, event): self.deleteLater() def handleFilterActivated(self): header = self.treeView.header() filters = [] for i in range(header.count()): text = header.filterText(i) if text: filters.append((i, text)) proxy = self.treeView.model() proxy.filters = filters self.updateStatus() self.treeView.setCurrentIndex(self.treeView.model().index(0, 0)) def keyReleaseEvent(self, eventQKeyEvent): key = eventQKeyEvent.key() modifiers = QtWidgets.QApplication.keyboardModifiers() if modifiers == QtCore.Qt.ShiftModifier and key == QtCore.Qt.Key_Escape: self.clear_all_filters() def keyPressEvent(self, event): key = event.key() modifiers = QtWidgets.QApplication.keyboardModifiers() if modifiers != QtCore.Qt.ShiftModifier: focus_obj = self.focusWidget() if key == QtCore.Qt.Key_Return: if isinstance(focus_obj, QtWidgets.QTreeView): self.edit_record(self.treeView.currentIndex()) elif key == QtCore.Qt.Key_Escape: if isinstance(focus_obj, QtWidgets.QLineEdit): focus_obj.clear() def clear_all_filters(self): # clear all inputs of type QLineEdit lineEdits = self.findChildren(QtWidgets.QLineEdit) for lineEdit in lineEdits: lineEdit.clear() def setupUi(self): self.setWindowIcon(QtGui.QIcon.fromTheme("applications-multimedia")) self.centralwidget = QtWidgets.QWidget(self) hBox = QtWidgets.QHBoxLayout(self.centralwidget) self.treeView = QtWidgets.QTreeView(self.centralwidget) self.treeView.setSelectionBehavior(1) self.treeView.setRootIsDecorated(False) self.treeView.setSortingEnabled(True) self.treeView.setAlternatingRowColors(True) self.treeView.setEditTriggers(QtWidgets.QAbstractItemView.DoubleClicked) self.treeView.setSelectionMode(1) self.treeView.header().setStretchLastSection(True) hBox.addWidget(self.treeView) self.setCentralWidget(self.centralwidget) header = FilterHeader(self.treeView) self.treeView.setHeader(header) # StatusBar self.statusBar = self.statusBar() self.setStatusBar(self.statusBar) modelTable.setTable(MY_TABLE) modelTable.setEditStrategy(QtSql.QSqlTableModel.OnFieldChange) self.treeView.setModel(modelTable) while modelTable.canFetchMore(): modelTable.fetchMore() if not TRACE: self.treeView.setColumnHidden(0, True) # enable human sorting proxy = HumanProxyModel(self) proxy.setSourceModel(modelTable) self.treeView.setModel(proxy) # enable filtering header.setFilterBoxes(modelTable.columnCount()) header.filterActivated.connect(self.handleFilterActivated) # update counters global g_total_count, g_hits_count g_total_count = modelTable.rowCount() g_hits_count = g_total_count self.treeView.setColumnWidth(0, 200) self.treeView.setColumnWidth(1, 300) self.treeView.hideColumn(3) self.setWindowTitle(f"Inventory - {str(g_total_count)} Items") if __name__ == "__main__": app = QtWidgets.QApplication(sys.argv) window = winMain() sys.exit(app.exec_())