I'm working with TableViews in a Stacked Layout. The parent widget contains form controls (a lineEdit and a master checkbox) which manipulate the filters of the tableviews.
The master checkbox needs to be able to hide every checked item in the visible model.
How do I filter based on the CheckStateRole instead of the DisplayRole? In the code below, the third line from the bottom you will see a "what do I do here?", which is where I've lost the ability to understand any more of the documentation without concrete examples.
Current code:
class ShippingWindow(QWidget, Ui_shippingWidget):
def __init__(self, saveData, objectData, parent=None):
super().__init__(parent)
self.setupUi(self)
self.saveData = saveData
self.objectData = objectData
# set up our stacked layout. shipWidget, polyWidget and monoWidget will hold our tables.
self.stackedLayout = QStackedLayout()
self.shipWidget = QTableView()
self.shipModel = QStandardItemModel()
self.stackedLayout.addWidget(self.shipWidget)
self.stackWidget = QWidget()
self.stackWidget.setLayout(self.stackedLayout)
self.stackedLayout.setCurrentWidget(self.shipWidget)
self.verticalLayout.addWidget(self.stackWidget)
# setup our contents
self.populateShipping(saveData, objectData)
# Attach the line edit to the Filter.
self.filterTable.textChanged.connect(self.shippingFilterProxy.setFilterRegularExpression)
# radio button logic for swapping currentWidget
self.radioShip.toggled.connect(self.toggleShip)
self.radioPoly.toggled.connect(self.togglePoly)
self.radioMono.toggled.connect(self.toggleMono)
# hide shipped checkbox logic
self.hideShipped.stateChanged.connect(self.toggleShippedItems)
def populateShipping(self, saveData, objectData):
shippingBox = self.shipWidget
excludedCats = ["Moved", "Tax-Exempt"]
shippables = list(filter(None, [value if "SendOnly" in value and "FullShipment" in value["SendOnly"] and value["SendOnly"]["FullShipment"] and value["Category"] not in excludedCats else '' for value in objectData]))
# sort by name
sorted_shippables = sorted(shippables, key=lambda d: d["Name"])
self.shipModel = QStandardItemModel(len(sorted_shippables), 5)
self.shipModel.setHorizontalHeaderLabels(["Item", "Category", "Price", "Sources", "Developer"])
shippingBox.verticalHeader().hide()
# self.shipModel.setRowCount(len(shippables))
row = 0
for item in sorted_shippables:
itemObj = SVObject(**item)
nameCell = QStandardItem(itemObj.Name)
nameCell.setFlags(QtCore.Qt.ItemFlag.ItemIsUserCheckable | QtCore.Qt.ItemFlag.ItemIsEnabled)
if itemObj.ID["objects"] in saveData["basicShipped"]:
nameCell.setCheckState(QtCore.Qt.CheckState.Checked)
else:
nameCell.setCheckState(QtCore.Qt.CheckState.Unchecked)
priceCell = QStandardItem()
priceCell.setData(itemObj.Price, QtCore.Qt.ItemDataRole.DisplayRole)
itemSources = list(item["Sources"].keys())
sourceString = ", ".join(itemSources)
sourceCell = QStandardItem(sourceString)
self.shipModel.setItem(row, 0, nameCell)
self.shipModel.setItem(row, 1, QStandardItem(itemObj.Category))
self.shipModel.setItem(row, 2, priceCell)
self.shipModel.setItem(row, 3, sourceCell)
self.shipModel.setItem(row, 4, QStandardItem(itemObj.ModName))
row += 1
shippingBox.setSortingEnabled(True)
# set up the filter proxy model
self.shippingFilterProxy = QtCore.QSortFilterProxyModel()
self.shippingFilterProxy.setSourceModel(self.shipModel)
self.shippingFilterProxy.setFilterKeyColumn(0)
self.shippingFilterProxy.setFilterRole(QtCore.Qt.ItemDataRole.DisplayRole)
self.shippingFilterProxy.setFilterCaseSensitivity(QtCore.Qt.CaseSensitivity.CaseInsensitive)
self.shipWidget.setModel(self.shippingFilterProxy)
shippingBox.resizeColumnsToContents()
def toggleShippedItems(self, s):
if s == 2:
if self.stackedLayout.currentIndex() == 0:
# change the role to checkstate for a moment
self.shippingFilterProxy.setFilterRole(QtCore.Qt.ItemDataRole.CheckStateRole)
self.shippingFilterProxy.setFilterFixedString(2) # what do I do here?
# change the role back so the text filter works again?
# self.shippingFilterProxy.setFilterRole(QtCore.Qt.ItemDataRole.DisplayRole)
Where I'm stuck is in toggleShippedItems at the very end. I've tried looping the models, they are non-iterable. I need to be able hide the checked items, keep them hidden, and still have the lineEdit search remain in effect. I'm much more comfortable with TableWidget, but since I need the text filter, I'm floundering out of my depth with TableView.
For those searching in the future, here is how I resolved the question on my own. It's cobbled together from PyQt4 and PyQt5 snippets updated to PyQt6, and C++ versions run through an automated C++ to Python translator.
New populateShipping method:
def populateShipping(self, saveData, objectData):
shippingBox = self.shipWidget
shippingBox.setStyleSheet("border: 5px ridge rgb(91, 43, 42);")
excludedCats = ["Moved", "Tax-Exempt"]
shippables = list(filter(None, [value if "SendOnly" in value and "FullShipment" in value["SendOnly"] and value["SendOnly"]["FullShipment"] and value["Category"] not in excludedCats else '' for value in objectData]))
# sort by name
sorted_shippables = sorted(shippables, key=lambda d: d["Name"])
dataTable = []
for item in sorted_shippables:
dataRow = []
# populate the dataTable here, snip snip
dataTable.append(dataRow)
labels = ["Name", "Category", "Price", "Sources", "Developer"]
# set up the model and proxies
self.shippingmodel = ShippablesTableModel(dataTable, labels)
# check the boxes for any shipped items
checkCol = 0
for r in range(self.shippingmodel.rowCount()):
itemID = sorted_shippables[r]["ID"]["objects"]
checkIdx = self.shippingmodel.index(r, checkCol)
if str(itemID) in self.saveData["basicShipped"]:
self.shippingmodel.setData(checkIdx, QtCore.Qt.CheckState.Checked, QtCore.Qt.ItemDataRole.CheckStateRole)
# checkbox proxy filter
self.shippedProxyModel = ShippingCheckProxy()
self.shippedProxyModel.setSourceModel(self.shippingmodel)
self.shippedProxyModel.setFilterFixedString("All")
# search box proxy filter nests checkbox proxy filter
self.shippeditemNameProxyModel = QtCore.QSortFilterProxyModel()
self.shippeditemNameProxyModel.setSourceModel(self.shippedProxyModel)
self.shippeditemNameProxyModel.setFilterKeyColumn(0)
self.shippeditemNameProxyModel.setFilterCaseSensitivity(QtCore.Qt.CaseSensitivity.CaseInsensitive)
# populate and design the actual table.
shippingBox.setSortingEnabled(True)
shippingBox.setModel(self.shippeditemNameProxyModel)
shippingBox.verticalHeader().hide()
shippingBox.horizontalHeader().setStyleSheet("border: none;")
shippingBox.resizeColumnsToContents()
Custom Table Model:
class ShippablesTableModel(QtCore.QAbstractTableModel):
def __init__(self, shippingData, headerNames, parent=None):
super(ShippablesTableModel, self).__init__(parent)
self.tableData = shippingData
self.columnNames = headerNames
self.checks = {}
def columnCount(self, *args):
return len(self.columnNames)
def rowCount(self, *args):
return len(self.tableData)
def checkState(self, index):
if index in self.checks.keys():
return self.checks[index]
else:
return QtCore.Qt.CheckState.Unchecked
def data(self, index, role=QtCore.Qt.ItemDataRole.DisplayRole):
row = index.row()
col = index.column()
if role == QtCore.Qt.ItemDataRole.DisplayRole:
value = self.tableData[row][col]
return value
elif role == QtCore.Qt.ItemDataRole.CheckStateRole and col == 0:
return self.checkState(QtCore.QPersistentModelIndex(index))
return None
def setData(self, index, value, role=QtCore.Qt.ItemDataRole.EditRole):
if not index.isValid():
return False
if role == QtCore.Qt.ItemDataRole.CheckStateRole:
self.checks[QtCore.QPersistentModelIndex(index)] = value
self.dataChanged.emit(index, index)
return True
return False
def flags(self, index):
fl = QtCore.QAbstractTableModel.flags(self, index)
if index.column() == 0:
fl = QtCore.Qt.ItemFlag.ItemIsUserCheckable | QtCore.Qt.ItemFlag.ItemIsEnabled
return fl
def headerData(self, col, orientation, role=QtCore.Qt.ItemDataRole.DisplayRole):
if role == QtCore.Qt.ItemDataRole.DisplayRole and orientation == QtCore.Qt.Orientation.Horizontal:
return self.columnNames[col]
return None
Custom QSortFilterProxyModel:
class ShippingCheckProxy(QtCore.QSortFilterProxyModel):
def __init__(self, parent=None):
super(ShippingCheckProxy, self).__init__(parent)
def filterAcceptsRow(self, row_num, parent):
model = self.sourceModel()
shippedIdx = model.index(row_num, 0, parent)
thisState = shippedIdx.data(QtCore.Qt.ItemDataRole.CheckStateRole)
# we can only pass a regex through, but we only want the actual text
# (pattern), not a parsed regex.
regex = self.filterRegularExpression()
pattern = regex.pattern()
# If the 0 pattern is submitted, only show rows with unchecked boxes.
if pattern == "0":
if thisState == QtCore.Qt.CheckState.Unchecked or thisState == 0:
return True
else:
return False
# otherwise show everything.
return True