How can I efficiently expand an entire subtree of a QTreeView?

人走茶凉 提交于 2020-02-05 13:57:15

问题


EDIT: it turns out that the root performance problem was a size-to-fit function attached to the expanded() signal, so I'm going to accept the first answer and delete this question for being misleading.

Note: I'm asking this question so I can provide an answer for it (and maybe get a better answer). The solution is not intuitive.

MacOS builds of Qt may have a way for the user to expand an entire QTreeView subtree (there was an open bug for it) but non-MacOS builds definitely do not. I am trying to implement the behavior "shift-click on the item decoration expands the entire subtree".

There are two problems. The easier of the two is detecting a shift-click on the decoration. I do this by intercepting the expanded/collapsed signals; they check some "global" state set up by the mousePressEvent:

# similar implementation for _on_expanded
@pyqtSlot(QModelIndex)
def _on_collapsed(self, index):
    if self._in_shift_press:
        self._in_shift_press = False
        _recursive_set_expanded(self, index, False)

def mousePressEvent(self, evt):
    # Make shift-click expand/collapse all
    if int(evt.modifiers() & Qt.ShiftModifier) != 0:
        self._in_shift_press = True
    try:
        QTreeView.mousePressEvent(self, evt)
    finally:
        self._in_shift_press = False

This is a little ugly, but it works well enough.

The harder problem is implementing _recursive_set_expanded(view, root, state). A recursive collapse is very quick. However, the obvious implementation of calling view.setExpanded(True) on all descendents of an index is very very slow -- multiple seconds for ~100 indices. The problem is not an expensive data model, since view.expandAll() is very fast.

Some source diving shows that expandAll() does significantly less work than expand(). However, the Qt API doesn't expose an expandSubtree() method. How can this be made fast, short of digging into the private implementation?


回答1:


Pressing the '*' (asterisk) key expands a node's subnodes, which is just what you want. Have you tried calling keyPressEvent with a fake '*' keypress?




回答2:


Since expandAll() is fast, and collapse(QModelIndex) is fast, my solution is to use only those two methods and avoid calling expand(QModelIndex):

def _recursive_set_expanded(view, root, desired):
    """For |root| and all its descendents, set the 'expanded' state to |desired|.
    It is undefined whether expanded and collapsed signals are emitted."""
    state = {}
    def _recursive_get_state(view, model, index, under_root):
        if index == root:
            under_root = True
        num_children = model.rowCount(index)
        if num_children:
            if under_root:
                state[index] = desired
            else:
                state[index] = view.isExpanded(index)
            for i in xrange(model.rowCount(index)):
                _recursive_get_state(view, model, model.index(i,0, index), under_root)

    _recursive_get_state(view, view.model(), QModelIndex(), False)
    view.expandAll()
    for k,v in state.iteritems():
        if not v:
            view.setExpanded(k, False)



回答3:


void MainWindow::expandNode(const QModelIndex &parentIndex, bool expand) {
  tree->setExpanded(parentIndex, expand);
  for (qint32 rowNum = 0; rowNum < treeModel->rowCount(parentIndex); ++rowNum) {
    QModelIndex childIndex = treeModel->index(rowNum, 0, parentIndex);
    tree->setExpanded(childIndex, expand);
    expandNode(childIndex);
  }
}



回答4:


Since I just had a performance problem while trying to expand subtrees and this question is the first google result, I am going to share my insights, although I am using C++ and Qt5.

TonyK's solution with calling keyPressEvent works. However, this solution felt hacky to me, so I checked the Qt source, and it is indeed hardwired to the Qt::Key_Asterisk key. (Right in QTreeView::keyPressEvent, if you want to see for yourself.)

QTreeView::keyPressEvent just calls expand(), in a depth-first manner, nothing special, no private api calls. And surely, when I just copied the code from keyPressEvent so my function, it worked:

QModelIndex current = currentIndex();
QStack<QModelIndex> parents;
parents.push(current);
QAbstractItemModel * mod = model();
while (!parents.isEmpty()) {
    QModelIndex parent = parents.pop();
    for (int row = 0; row < mod->rowCount(parent); ++row) {
        QModelIndex child = mod->index(row, 0, parent);
        if (!child.isValid())
            break;
        parents.push(child);
        expand(child);
    }
}
expand(current);

However I wanted to create a setExpandedRecursive(QModelIndex index, bool expanded) function, so I checked their difference to my code, and its simply that they call expand() for the subtree's root node last instead of first. So I did that too in my function, and guess what, now collapsing was the problem instead of expanding. So here is my final solution that works for expanding and collapsing subtrees:

void TreeViewClass::setExpandedRecursive(QModelIndex index, bool expanded)
{
    if (!index.isValid())
        return;

    const QAbstractItemModel * model = index.model();
    if (!expanded)
        setExpanded(index, expanded);
    for (int row = 0, count = model->rowCount(index); row < count; ++row)
        setExpandedRecursive(model->index(row, 0, index), expanded);
    if (expanded)
        setExpanded(index, expanded);
}


来源:https://stackoverflow.com/questions/4999536/how-can-i-efficiently-expand-an-entire-subtree-of-a-qtreeview

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!