What is the best way to remove items from a QTreewidget?



  • I am a little bit afraid of deleting items of a QTreeWidget.

    In my program the user will have the ability to select multiple random items from a treewidget and remove them. Many problems arise.

    *1.* Let's say that the selectedItems() contains a parent and its child. What will happen if the parent is deleted first and then I attempt to delete the non-existent child of it?
    
    *2.* Providing that deleting a parent force deletes all its children, wouldn't it better to "clear" the selectedItems() to contain all the parents only and only the children whose parent items have not been selected? Also, consider the following:
    

    !http://i.imgur.com/Gpjsf.png(Problem example)!

    At the above picture you can see that in fact, the selectedItems() has to be "cleared" in such a way that only the parent of all is left. All the other items will be automatically deleted. So, it's not only about searching whether the parent item has been selected, but also the parent of the parent of the parent ... (and so on).

    *3.* How can I detect if all parent items have been selected, and then, instead of deleting them one by one, to call the clear() function?
    
    *4.* After the deletion process, I want another item to be selected. That's because, for example, when there are 3 top level items (without or without children) into the treewidget and the user has selected one of them, then the user should be able to delete all 3 of them by clicking 3 times at the remove button. So, the previous item has to be selected (and the next, in case the index is already 0). As for selecting multiple items, I think that going at the previous item of selectedItems().at(0) would be OK.
    

    Thanks in advance for any answers!



  • Use a very simple trick, and start with deleting the last item in your list of selected items, instead of the first.

    So, in your case, you first delete "SpecialMotionNotify SearchForPixel", then "If 5 NoProcessName process" above it all the way to deleting the rootNode last. You'll see: no problems doing that at all. Before you start optimizing by trying to find out if you could have just cleared the whole tree, see if it really matters in speed. Don't optimize before you know there is a problem. It is called premature optimization.

    Edit:
    As for your question 4: I would keep track of the index of the top item of your selection (row and parent). Then, after deleting, you try to re-select the same index. After the deletion process, that should be either the next item or no item at all. If there is no item there and row is >0, select item with the same parent and row-1. That is the item above the top item that was originally selected.



  • [quote author="Andre" date="1348127894"]Use a very simple trick, and start with deleting the last item in your list of selected items, instead of the first.

    So, in your case, you first delete "SpecialMotionNotify SearchForPixel", then "If 5 NoProcessName process" above it all the way to deleting the rootNode last. You'll see: no problems doing that at all. Before you start optimizing by trying to find out if you could have just cleared the whole tree, see if it really matters in speed. Don't optimize before you know there is a problem. It is called premature optimization.

    Edit:
    As for your question 4: I would keep track of the index of the top item of your selection (row and parent). Then, after deleting, you try to re-select the same index. After the deletion process, that should be either the next item or no item at all. If there is no item there and row is >0, select item with the same parent and row-1. That is the item above the top item that was originally selected.

    [/quote]
    Thanks a lot for your answer! How can I know which is the last item? The items inside selectedItems() are sorted in the row they were selected by the user. This will also help me from my 4th question so as to find the first item's index and try to re-select it.



  • Well, QModelIndex implements operator<(), so you should be able to sort your items. However, I took at look at the implementation of it in the source (4.8.1), and found this:
    @
    inline bool operator<(const QModelIndex &other) const
    {
    if (r < other.r) return true;
    if (r == other.r) {
    if (c < other.c) return true;
    if (c == other.c) {
    if (p < other.p) return true;
    if (p == other.p) return m < other.m;
    }
    }
    return false; }
    @

    That is not correct for trees. I asked at the dev IRC channel, and it seems in Qt 5, this was added to the docs:

    [quote]
    The less than calculation is not directly useful to developers - the way that indexes
    with different parents compare is not defined. This operator only exists so that the
    class can be used with QMap.
    [/quote]
    Also, it seems the opinion is held that there is no correct sorting for trees. I find that idea rediculous for the Qt item models, but that's a different discussion.
    So, you will have to create your own comparison method for sorting. Something like this, I guess, though it's not very efficient for deep trees:

    @
    //we assume we're not interesed in columns
    bool treeIndexSmallerThan(const QModelIndex& left, const QModelIndex& right) {
    if (left == right) return false;

    QModelIndex leftParent(left.parent());
    QModelIndex rightParent(right.parent());
    
    if (!leftParent.isValid() && rightParent.isValid()) return true;
    if (leftParent.isValid() && rightParent.isValid()) {
        if (leftParent == rightParent) {
            return (left.row() < right.row());
        }
    
        // recursive call
        return treeIndexSmallerThan(leftParent, rightParent);
     }
     if (!leftParent.isValid() && !rightParent.isValid()) {
         return (left.row() < right.row());
     }
    
     return false;
    

    }
    @



  • Thanks a lot for your effort! I am trying to figure out how I am going to do it, and I try to sort the items using some kind of bubble sort. The program is crashing every time at
    @QModelIndex leftParent(left.parent());@ (of your function)

    Also, even if I call @if(left.parent().isValid())@ prior to making the left/rightParent objects, it still crashes at this if statement.

    That's the code (indexFromItem is protected but that's not a problem, since I've already subclassed QTreeWidget):

    @QList<QTreeWidgetItem*> selected_items = recording_browser->selectedItems();

    int items_count=selected_items.count();
    
    for(int i=0;i<items_count;i++){
        for(int j=0;j<items_count-i-1;j++){
            if(treeIndexSmallerThan(recording_browser->indexFromItem(selected_items.at(j), 0), recording_browser->indexFromItem(selected_items.at(j+1), 0))){
                //swap
                QTreeWidgetItem *temp = new QTreeWidgetItem;
                temp=selected_items.at(j);
                selected_items[j] = selected_items[j+1];
                selected_items[j+1] = temp;
            }
        }
    }
    
    //now items should be sorted...
    for(int i=0;i<items_count;i++){
        qDebug() << selected_items.at(i)->text(0);
    }@


  • My function was completely untested, and should not be interpretted as working code :-)

    On sorting: please just use qSort. Don't implement your own algorithm unless you know what you are doing.

    On getting the selected items: I would just directly get them from the item selection model, using:
    @
    //get the items
    QModelIndexList selectedIndexes = this->selectionModel()->selectedIndexes();
    //sort them
    qSort(selectedIndexes.begin(), selectedIndex.end(), treeIndexSmallerThan);
    @

    It might be easier to sort using a treeIndexLargerThan function instead though, as that way you can start deleting at the first item in the list instead of iterating backwards.



  • Nice! I managed to make it work with the way you suggested now. The only unsolved problem now is how to select another item after I've deleted the other ones. One suggestion would be to take the first of the selectedIndexes after the sorting and figure out there what would be the previous index of it (or, if it is the last child, then the parent to be selected, etc).

    Should I do an if <-> else as to whether that item has a child or there is a more 'default' way to do it?



  • It doesn't really matter. AFAIK, there are no default ways to do this, so anything (that works efficiently) goes.



  • In case anybody finds this in the future, what I found works well for deleting multiple selected items in a QTreeWidget is to loop on (selectedItems().size() > 0) and delete one of the items each loop. This way, if a parent gets deleted, any selected children will be de-selected so you don't have to worry about them getting double-deleted.



  • In case anybody finds this in the future, what I found works well for deleting multiple selected items in a QTreeWidget is to loop on (selectedItems().size() > 0) and delete one of the items each loop. This way, if a parent gets deleted, any selected children will be de-selected so you don't have to worry about them getting double-deleted.


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.