单列QTreeview搜索过滤器

时间:2017-12-11 03:49:39

标签: python filtering pyside qtreeview qsortfilterproxymodel

我有两个问题:

  1. 我想知道这是否是在单列树视图上进行搜索/过滤的正确方法。我觉得我的很多复制/粘贴可能包含不必要的东西。是QSortFilterProxyModel的子类中的所有代码和search_text_changed方法中的代码都需要吗?我不觉得需要正则表达式,因为我设置filter-proxy来忽略区分大小写。

  2. 如何使用当双击树视图项时,信号会发出一个字符串列表,其中包含所点击的项目的字符串及其所有祖先的递归信息?例如,如果我双击" Birds",它将返回['Birds','Animals'];如果我双击动物"它就会返回['Animals']

  3. enter image description here

    import os, sys
    from PySide import QtCore, QtGui
    
    tags = {
        "Animals": [
            "Birds",
            "Various"
        ],
        "Brick": [
            "Blocks",
            "Special"
        ],
        "Manmade": [
            "Air Conditioners",
            "Audio Equipment"
        ],
        "Food": [
            "Fruit",
            "Grains and Seeds"
        ]
    }
    
    class SearchProxyModel(QtGui.QSortFilterProxyModel):
        def __init__(self, parent=None):
            super(SearchProxyModel, self).__init__(parent)
            self.text = ''
    
        # Recursive search
        def _accept_index(self, idx):
            if idx.isValid():
                text = idx.data(role=QtCore.Qt.DisplayRole).lower()
                condition = text.find(self.text) >= 0
    
                if condition:
                    return True
                for childnum in range(idx.model().rowCount(parent=idx)):
                    if self._accept_index(idx.model().index(childnum, 0, parent=idx)):
                        return True
            return False
    
        def filterAcceptsRow(self, sourceRow, sourceParent):
            # Only first column in model for search
            idx = self.sourceModel().index(sourceRow, 0, sourceParent)
            return self._accept_index(idx)
    
        def lessThan(self, left, right):
            leftData = self.sourceModel().data(left)
            rightData = self.sourceModel().data(right)
            return leftData < rightData
    
    
    class TagsBrowserWidget(QtGui.QWidget):
    
        clickedTag = QtCore.Signal(list)
    
        def __init__(self, parent=None):
            super(TagsBrowserWidget, self).__init__(parent)
            self.resize(300,500)
    
            # controls
            self.ui_search = QtGui.QLineEdit()
            self.ui_search.setPlaceholderText('Search...')
    
            self.tags_model = SearchProxyModel()
            self.tags_model.setSourceModel(QtGui.QStandardItemModel())
            self.tags_model.setDynamicSortFilter(True)
            self.tags_model.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive)
    
            self.ui_tags = QtGui.QTreeView()
            self.ui_tags.setSortingEnabled(True)
            self.ui_tags.sortByColumn(0, QtCore.Qt.AscendingOrder)
            self.ui_tags.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)
            self.ui_tags.setHeaderHidden(True)
            self.ui_tags.setRootIsDecorated(True)
            self.ui_tags.setUniformRowHeights(True)
            self.ui_tags.setModel(self.tags_model)
    
            # layout
            main_layout = QtGui.QVBoxLayout()
            main_layout.addWidget(self.ui_search)
            main_layout.addWidget(self.ui_tags)
            self.setLayout(main_layout)
    
            # signals
            self.ui_tags.doubleClicked.connect(self.tag_double_clicked)
            self.ui_search.textChanged.connect(self.search_text_changed)
    
            # init
            self.create_model()
    
        def create_model(self):
            model = self.ui_tags.model().sourceModel()
            self.populate_tree(tags, model.invisibleRootItem())
            self.ui_tags.sortByColumn(0, QtCore.Qt.AscendingOrder)
    
    
        def populate_tree(self, children, parent):
            for child in sorted(children):
                node = QtGui.QStandardItem(child)
                parent.appendRow(node)
    
                if isinstance(children, dict):
                    self.populate_tree(children[child], node)
    
    
        def tag_double_clicked(self, item):
            text = item.data(role=QtCore.Qt.DisplayRole)
            print [text]
            self.clickedTag.emit([text])
    
    
        def search_text_changed(self, text=None):
            regExp = QtCore.QRegExp(self.ui_search.text(), QtCore.Qt.CaseInsensitive, QtCore.QRegExp.FixedString)
    
            self.tags_model.text = self.ui_search.text().lower()
            self.tags_model.setFilterRegExp(regExp)
    
            if len(self.ui_search.text()) >= 1 and self.tags_model.rowCount() > 0:
                self.ui_tags.expandAll()
            else:
                self.ui_tags.collapseAll()
    
    
    def main():
        app = QtGui.QApplication(sys.argv)
        ex = TagsBrowserWidget()
        ex.show()
        sys.exit(app.exec_())
    
    
    if __name__ == '__main__':
        main()
    

1 个答案:

答案 0 :(得分:1)

在所有中设置filter-proxy 的区分大小写没有意义,因为您通过覆盖filterAcceptsRow来绕过内置过滤。即使你没有这样做,setFilterRegExp无论如何都会忽略当前的情况敏感设置。

我会将filter-proxy简化为:

class SearchProxyModel(QtGui.QSortFilterProxyModel):

    def setFilterRegExp(self, pattern):
        if isinstance(pattern, str):
            pattern = QtCore.QRegExp(
                pattern, QtCore.Qt.CaseInsensitive,
                QtCore.QRegExp.FixedString)
        super(SearchProxyModel, self).setFilterRegExp(pattern)

    def _accept_index(self, idx):
        if idx.isValid():
            text = idx.data(QtCore.Qt.DisplayRole)
            if self.filterRegExp().indexIn(text) >= 0:
                return True
            for row in range(idx.model().rowCount(idx)):
                if self._accept_index(idx.model().index(row, 0, idx)):
                    return True
        return False

    def filterAcceptsRow(self, sourceRow, sourceParent):
        idx = self.sourceModel().index(sourceRow, 0, sourceParent)
        return self._accept_index(idx)

并将搜索方法更改为:

def search_text_changed(self, text=None):
    self.tags_model.setFilterRegExp(self.ui_search.text())

    if len(self.ui_search.text()) >= 1 and self.tags_model.rowCount() > 0:
        self.ui_tags.expandAll()
    else:
        self.ui_tags.collapseAll()

现在,SearchProxyModel对决定如何通过setFilterRegExp方法执行搜索负有唯一的责任。区分大小写是透明处理的,因此无需预处理输入。

获取后代列表的方法可以这样写:

def tag_double_clicked(self, idx):
    text = []
    while idx.isValid():
        text.append(idx.data(QtCore.Qt.DisplayRole))
        idx = idx.parent()
    text.reverse()
    self.clickedTag.emit(text)