Hi I am creating a dialog for objects with children, a QTreeview with a QlineEdit for filtering. When the dialog comes up initially, it looks like (no.1)
the code looks like below. I've summarised it with just the important bits
#These are global so can be referenced from any function. and all set
#when i initialise my diaglog.
model=QtGui.QStandardItemModel()
treeview=QtGui.QTreeview()
proxymodel=QtGui.QSortFilterProxyModel(treeview)
treeview.setmodel(proxymodel)
proxymodel.setSourceModel(model)
def initialise_treeview(self):
#This builds the treeview with a list of root nodes only
root_nodes=["Apple","Ardvark","Ankle","Bee","Bark","Bar","Carrot"]# a list of root nodes
for objects in root_nodes:
item=QtGui.QStandardItem(objects)
model.appendRow(item)
No.2 shows the treeview being filtered when the user types into the LineEdit text box
#When a user types into the search field the Treeview gets filtered via the proxymodel
QtCore.QObject.connect(LineEdit,QtCore.Signal("TextChanged(QString)")),update_filter)
def update_filter(self,text):
#filter the model based on the text in the qlineedit
proxymodel.setFilterRegExp(LineEdit.text());
Once a user selects an item in the treeview (No. 3, where Bar has been selected). The code should fetch all of the selected items children, add them to the model, and finally expand the selected node to show all the child items (No. 4)
#update_treeview function is called to add children to the selected item
QtCore.QObject.connect(treeview.selectionModel(),QtCore.Signal("currentChanged(QModelIndex,QmodelIndex)")),update_treeview)
def update_treeview(self,currentindex,previousindex):
sourcemodel_index=proxymodel.mapToSource(currentindex)
parent_item=QtGui.QStandardItem()
#get the item from the source model
parent_item=model.itemFromIndex(sourcemodel_index)
for childitem in list_of_children:
file_item=QtGui.QStandardItem(str(childitem))
model.appendRow(file_item)
treeview.expand(currentindex) #this doesn't work when proxymodel has been filtered
So far I have most of this working. Actually all of it. Except the expanding of the treeview, when there has been some filtering.
I have it working when NO filter has been applied, but once the treeview's list is filtered its a bit hit and miss i.e the treeview is not always expanded at the right node.
How can I ensure that the treeview expands at the correct index so that when the folder list has been filtered and files added to the filtered list. How can I ensure that the treeview is expanded to the right location. I'm using python 2.7,Qt 4.8 on windows.
The problem is not that it does not expand, but that it is being affected by the QSortProxyModel filter, the filter should only be applied to topLevels. I have used a new role to not add more children if you already have them, I also think that the clicked signal is the most appropriate for this case.
from PyQt4.QtCore import *
from PyQt4.QtGui import *
HaveChildrenRole = Qt.UserRole
class SortFilterProxyModel(QSortFilterProxyModel):
def filterAcceptsRow(self, source_row, source_parent):
ix = self.sourceModel().index(source_row, 0, source_parent)
if not ix.parent().isValid(): # check if the item has no parent
return QSortFilterProxyModel.filterAcceptsRow(self, source_row, source_parent)
else: # as it has a parent, the filter does not apply
return True
class Widget(QWidget):
def __init__(self, *args, **kwargs):
QWidget.__init__(self, *args, **kwargs)
self.setLayout(QVBoxLayout())
self.treeView = QTreeView(self)
self.le = QLineEdit(self)
self.layout().addWidget(self.treeView)
self.layout().addWidget(self.le)
self.model = QStandardItemModel(self)
self.proxy = SortFilterProxyModel(self)
self.proxy.setSourceModel(self.model)
self.treeView.setModel(self.proxy)
self.initialise_treeview()
self.le.textChanged.connect(self.update_filter)
self.treeView.clicked.connect(self.onClicked)
def initialise_treeview(self):
root_nodes = ["Apple", "Ardvark", "Ankle", "Bee", "Bark", "Bar", "Carrot"] # a list of root nodes
for obj in root_nodes:
item = QStandardItem(obj)
item.setData(False, HaveChildrenRole)
self.model.appendRow(item)
def update_filter(self, text):
self.proxy.setFilterRegExp(text)
def onClicked(self, ix):
s_ix = self.proxy.mapToSource(ix)
it = self.model.itemFromIndex(s_ix)
if not it.data(HaveChildrenRole) and it.parent() is None:
for children in ["A", "B", "C", "D"]:
it.appendRow(QStandardItem(children))
it.setData(True, HaveChildrenRole)
self.treeView.expand(ix)
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
w = Widget()
w.show()
sys.exit(app.exec_())
Explanation:
The filterAcceptsRow
method decides whether the row is displayed or not, in your case that decision should be made to the topLevel as to "Apple", "Bee", etc. So the first thing is to identify those items and the main feature is that they do not have a parent, so we access parent()
and if it is valid it has a parent if it is not a topLevel, then that method must pass the filter and to the others we return True so that they are visible.