Issue
I am working on an application in PyQt5 that contains a QTreeView, QStandardItemModel and QSortFilterProxyModel. The TreeView also has a QToolBar on the last column for some rows.
I have made a simplified version for an example :
And here's the source code :
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
class view(QWidget):
def __init__(self):
super(view, self).__init__()
self.tree = QTreeView(self)
layout = QVBoxLayout(self)
layout.addWidget(self.tree)
self.model = QStandardItemModel()
self.model.setHorizontalHeaderLabels(["Col 0", "Col 1", "Col 2", "Toolbar"])
self.tree.header().setDefaultSectionSize(180)
self.tree.setModel(self.model)
self.importData()
self.tree.expandAll()
def importData(self, root=None):
for i in range(3):
parent1 = QStandardItem("Family {}".format(i))
for j in range(3):
self.createRow(parent1, i, j)
def createRow(self, parent, i, j):
child1 = QStandardItem("Child {}".format(i * 3 + j))
child2 = QStandardItem("row: {}, col: {}".format(i, j + 1))
child3 = QStandardItem("row: {}, col: {}".format(i, j + 2))
child4 = QStandardItem("")
parent.appendRow([child1, child2, child3, child4])
self.model.appendRow(parent)
toolbar = QToolBar()
toolbar.addWidget(QLabel("Toolbar Btn: "))
toolbar.addWidget(QPushButton("Btn"))
self.tree.setIndexWidget(child4.index(), toolbar)
if __name__ == "__main__":
app = QApplication(sys.argv)
view = view()
view.setGeometry(300, 100, 600, 300)
view.setWindowTitle("QTreeview Example")
view.show()
sys.exit(app.exec_())
Now I would like to add a filter using a QLineEdit widget and QSortFilterProxyModel, but as you can see below the toolbar gets removed. Can someone explain why and how I could solve this issue ?
Here's my code so far :
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
class view(QWidget):
def __init__(self):
super(view, self).__init__()
self.tree = QTreeView(self)
layout = QVBoxLayout(self)
self.filter = QLineEdit()
self.filter.textChanged.connect(self.onTextChanged)
layout.addWidget(self.filter)
layout.addWidget(self.tree)
self.model = QStandardItemModel()
self.model.setHorizontalHeaderLabels(["Col 0", "Col 1", "Col 2", "Toolbar"])
self.proxyModel = QSortFilterProxyModel(
self.tree, recursiveFilteringEnabled=True
)
self.proxyModel.setSourceModel(self.model)
self.tree.header().setDefaultSectionSize(180)
self.tree.setModel(self.proxyModel)
self.importData()
self.tree.expandAll()
def importData(self, root=None):
for i in range(3):
parent1 = QStandardItem("Family {}".format(i))
for j in range(3):
self.createRow(parent1, i, j)
def createRow(self, parent, i, j):
child1 = QStandardItem("Child {}".format(i * 3 + j))
child2 = QStandardItem("row: {}, col: {}".format(i, j + 1))
child3 = QStandardItem("row: {}, col: {}".format(i, j + 2))
child4 = QStandardItem("")
parent.appendRow([child1, child2, child3, child4])
self.model.appendRow(parent)
toolbar = QToolBar()
toolbar.addWidget(QLabel("Toolbar Btn: "))
toolbar.addWidget(QPushButton("Btn"))
self.tree.setIndexWidget(child4.index(), toolbar)
def onTextChanged(self, text):
self.proxyModel.setFilterRegExp(text)
if __name__ == "__main__":
app = QApplication(sys.argv)
view = view()
view.setGeometry(300, 100, 600, 300)
view.setWindowTitle("QTreeview Example")
view.show()
sys.exit(app.exec_())
Thanks for your help !
Solution
There are three problems in your code.
First of all, the parent shouldn't be added in the createRow()
function, but in importData()
and possibly before adding children.
Qt warns you about this in the output:
StdErr: QStandardItem::insertRows: Ignoring duplicate insertion of item ...
Then, the index used for setIndexWidget()
must be based on the model of the view (which is the proxy), while you're using the source model, so the index is invalid as it belongs to another model.
Finally, when using filtering, the filtered indexes are completely destroyed and their index widget along with them, so when you unset the filter those widget will not be restored.
The solution is to set the widgets when the child indexes are actually added to the model, and this can be done by connecting to the rowsInserted()
signal, after verifying that the parent is valid (otherwise they would be top level items).
Note that QToolBar is not intended for this kind of usage, and a standard QWidget (or QFrame) should be used instead.
class view(QWidget):
def __init__(self):
# ...
self.proxyModel.rowsInserted.connect(self.updateWidgets)
def importData(self, root=None):
for i in range(3):
parent1 = QStandardItem("Family {}".format(i))
self.model.appendRow(parent1)
for j in range(3):
self.createRow(parent1, i, j)
def createRow(self, parent, i, j):
child1 = QStandardItem("Child {}".format(i * 3 + j))
child2 = QStandardItem("row: {}, col: {}".format(i, j + 1))
child3 = QStandardItem("row: {}, col: {}".format(i, j + 2))
child4 = QStandardItem("")
parent.appendRow([child1, child2, child3, child4])
def updateWidgets(self, parent, first, last):
if not parent.isValid():
return
for row in range(first, last + 1):
toolbar = QWidget()
layout = QHBoxLayout(toolbar)
layout.setContentsMargins(0, 0, 0, 0)
layout.addWidget(QLabel("Toolbar Btn: "))
layout.addWidget(QPushButton("Btn"))
childIndex = self.proxyModel.index(row, 3, parent)
self.tree.setIndexWidget(childIndex, toolbar)
Note: classes and constants should always have capitalized names.
Answered By - musicamante
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.