CustomTreeCtrl - unchecking a single child item doesn't uncheck its parent(s)

If a CustomTreeCtrl is configured to use checkboxes and the TR_AUTO_CHECK_PARENT style is specified, then, if you check all the child items under a parent item, the parent item (and its parent item, etc, if appropriate) is/are automatically checked. This appears to work correctly.

However, if you then uncheck one of the child items, the parent item is not unchecked. This can be demonstrated using the following app:

import wx
import wx.lib.agw.customtreectrl as CT

class MyFrame(wx.Frame):
    def __init__(self, parent):
        wx.Frame.__init__(self, parent, -1, "CustomTreeCtrl")
        agw_style = (wx.TR_DEFAULT_STYLE |
                     CT.TR_HAS_BUTTONS |
                     CT.TR_AUTO_CHECK_CHILD |
                     CT.TR_AUTO_CHECK_PARENT)
        tree = CT.CustomTreeCtrl(self, agwStyle=agw_style)
        root = tree.AddRoot("Root", ct_type=1)
        for i in range(1, 3):
            child = tree.AppendItem(root, "Folder %d" % i, ct_type=1)
            for j in range(1, 4):
                tree.AppendItem(child, "File %d-%d" % (i, j), ct_type=1)
        tree.ExpandAll()


if __name__ == '__main__':
    app = wx.App(0)
    frame = MyFrame(None)
    frame.Show()
    app.MainLoop()

Is it just me, or do others find this behaviour illogical?

The automatic checking of the parent items is handled by the CustomTreeCtrl.AutoCheckParent() method. That method is called both when a child item is checked and when it is unchecked. It contains a while loop which compares each child of the parent with the value of the checked parameter. If any of the child items doesn’t have the same checked state, the method returns without doing anything.

That is fine when the checked parameter is True, but when it is False, the method will return if any of the child items’ checked state is True so that the parent item will not be unchecked unless all the child items are unchecked. That does not seem correct to me as the tree then appears to be in an inconsistent state.

In the app below I derived a subclass of CustomTreeCtrl that overrides the AutoCheckParent() method to only execute the while loop if the checked parameter is True. I prefer this behaviour compared to the original.

Does anyone have any comments on this suggestion?

import wx
import wx.lib.agw.customtreectrl as CT

class MyCustomTreeCtrl(CT.CustomTreeCtrl):
    def AutoCheckParent(self, item, checked):
        """
        Traverses up the tree and checks/unchecks parent items.

        :param `item`: an instance of :class:`GenericTreeItem`;
        :param bool `checked`: ``True`` to check an item, ``False`` to uncheck it.

        :note: This method is meaningful only for checkbox-like and radiobutton-like items.
        """

        parent = item.GetParent()
        if not parent or parent.GetType() != 1:
            return

        if checked:
            (child, cookie) = self.GetFirstChild(parent)
            while child:
                if child.GetType() == 1 and child.IsEnabled():
                    if checked != child.IsChecked():
                        return
                (child, cookie) = self.GetNextChild(parent, cookie)

        self.CheckItem2(parent, checked, torefresh=True)
        self.AutoCheckParent(parent, checked)


class MyFrame(wx.Frame):
    def __init__(self, parent):
        wx.Frame.__init__(self, parent, -1, "CustomTreeCtrl")
        agw_style = (wx.TR_DEFAULT_STYLE |
                     CT.TR_HAS_BUTTONS |
                     CT.TR_AUTO_CHECK_CHILD |
                     CT.TR_AUTO_CHECK_PARENT)
        tree = MyCustomTreeCtrl(self, agwStyle=agw_style)
        root = tree.AddRoot("Root", ct_type=1)
        for i in range(1, 3):
            child = tree.AppendItem(root, "Folder %d" % i, ct_type=1)
            for j in range(1, 4):
                tree.AppendItem(child, "File %d-%d" % (i, j), ct_type=1)
        tree.ExpandAll()


if __name__ == '__main__':
    app = wx.App(0)
    frame = MyFrame(None)
    frame.Show()
    app.MainLoop()

total confusion :stuck_out_tongue_closed_eyes:
a folder is not a file & vice versa, so having the same marking :scream_cat:
at best there may be something for checking all files but never the same check box
on top of that showing both checks, header & detail, is blatant redundancy and thus error-prone, in the coding as well as for the user, I think…

basically I stick firmly to what I have said, especially in commercial data processing :cowboy_hat_face:

however, @RichardT, you prefer the parent tick only if all kids are checked

but I suppose the original idea was that the parent tick indicates that some child is selected

that version you can see by asking in your modification

  • if not checked:

Thanks for your replies, Georg.

The problem with that idea is that if you run my first example and only check ‘File 1-1’, then some child is shown as checked, but ‘Folder 1’ isn’t.

It’s only when you go from all child items being checked to not all child items being checked, that you get the state of parent being checked indicating that some child is selected.

That’s why I think it’s being inconsistent - it depends on the order of how items were checked/unchecked and not just on which child items are currently checked.

I would prefer it if the control gave a consistent indication of the current state.

I did wonder if setting the folders to 3-state would help.

When an item’s 3-state is set to wx.CHK_UNDETERMINED, it is shown as a dash in the checkbox:

However, when I modified my first example to do this, I started getting AssertionError in the CustomTreeCtrl.Check() method. It doesn’t seem to work automatically with the TR_AUTO_CHECK_CHILD and/or TR_AUTO_CHECK_PARENT styles.

Perhaps you would need to subclass CustomTreeCtrl and add your own functionality to make it work in a meaningful way?

@RichardT, somehow you have lost me

in your subclass of CustomTreeCtrl if (line 19) if checked: is replaced by if not checked: then the behaviour, at least on my windows, is consistent some child :sunglasses:

1 Like

Ah! I finally see what you are saying. I think I must have lost a few more brain cells recently, sorry for being so slow. I agree, that works better, thank you for the suggestion, Georg.

I did have a look to see if I could find any applications that use 3-state checkboxes in a tree and noticed that Google Earth does this for selecting the layers to be displayed. Where all the child items are selected, the parent shows a tick mark; where some child items are selected, the parent shows a grey square and where no child items are selected, the parent shows an empty checkbox.

google_earth_crop

I had a go at creating a sub-class of CustomTreeCtrl that handles 3-state checkboxes for folders in a similar way to how GoogleEarth handles the selection of layers:

vokoscreenNG-2023-04-20_13-13-12

I need to do more testing to make sure I haven’t broken anything, but it looks promising.

2 Likes