Binding Events From a Tree Control

Hi all,

I figured out the solution to the problem described below, but I don’t understand why the solution works, so thought I would ask for some explanation, as I seem to be missing something about binding events in general.

I have a Panel (called self) with a child tree control (wx.TreeCtrl) called self.tree. This tree is embedded in a pane of an AUI Manager which manages self. I don’t think the AUI manager aspect is the issue here, but anyway, I bound the event wx.EVT_TREE_SEL_CHANGED to an event handler within self.

When I bound the event to the tree like this:

self.tree.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnTreeSelection)

if I then triggered any of these events while the frame was open by selecting tree items, when I then closed the top level frame, I would get an error upon close “RuntimeError: wrapped C/C++ object of type TreeCtrl has been deleted”, triggered within the event handler statement created above. Somehow binding the event to the tree results in the event handler getting called after the tree has been deleted. However, if I bound the event this way directly to the panel:

self.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnTreeSelection, self.tree)

everything works fine.

Why is it necessary to bind a tree event handler to the parent panel/frame rather than to the tree itself? In the second statement, I still specified self.tree as the source of the event, but by binding it to the Panel (self), the event handler functions properly. This confuses me because I have often bound event handlers for a button directly to the button (wx.Button.Bind(wx.EVT…, event handler)) without any trouble. Not sure why you can’t do the same with a tree control.

any help :face_with_hand_over_mouth:

That’s a very helpful article (I had read it before), but I don’t think it answers my question. If I understand correctly, a wx.TreeEvent is a command event, so even if I bind a tree event using self.tree.Bind(…), the event will propagate from self.tree to its parent (self) and find the event handler in self. This is what I observed - when I did self.tree.Bind(), the event handler did work (it ran when I selected items in the tree), but I got an error when closing the program indicating the event handler was called after the tree had been destroyed.

You don’t specify if in self.OnTreeSelection you are calling Skip() or not.
Try it without, which should prevent the event being passed up the event chain to the panel and hopefully stop it falling over.

You don’t specify if in self.OnTreeSelection you are calling Skip() or not.

Same behavior with or without Skip(). To clarify, the event is never falling over - it succesfully executes whether I bind it to the tree or to the panel. The event handler self.OnTreeSelection is in the panel self which is the parent of self.tree. The tree itself self.tree does not have an event handler, so when I bound as self.tree.Bind(), the event is first delivered to self.tree, finds no event handler, and by default is passed up to the panel where it then finds self.OnTreeSelection and executes. Putting a Skip() statement here would just enable the event to continue passing up to any parent windows of self, none of which have an event handler for EVT_TREE_SEL_CHANGED in my case.

Again, the question is, when I bind as self.tree.Bind() why do I get an error upon closing the frame which indicates that the event handler self.OnTreeSelection was called after self.tree was destroyed. Note that this error will only occur if, while the program was running, I trigger at least one call to self.OnTreeSelection.

I tried creating a simple script to see if I could replicate the problem using Python 3.10.12 + wxPython 4.2.1 gtk3 (phoenix) wxWidgets 3.2.2.1 on Linux Mint 21.3.

However, when I run it using either of the Bind() calls I’m not getting any RuntimeError exceptions when I close the frame.

Perhaps my example is not close enough to your code, or the problem is platform dependent?

import wx

class MyPanel(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent)
        self.tree = wx.TreeCtrl(self)
        self.root = self.tree.AddRoot("Root")
        self.tree.SetItemData(self.root, None)
        for x in range(8):
            self.tree.AppendItem(self.root, f"Item {x}")
        self.tree.Expand(self.root)

        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.tree, 1, wx.EXPAND, 0)
        self.SetSizer(sizer)

        # Comment/Uncomment to change the binding...
        self.tree.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnTreeSelection)
        # self.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnTreeSelection, self.tree)

    def OnTreeSelection(self, evt):
        print(f"Selected: '{self.tree.GetItemText(evt.GetItem())}'")


class MyFrame(wx.Frame):
    def __init__(self, parent):
        wx.Frame.__init__(self, parent)
        self.panel = MyPanel(self)
        self.Bind(wx.EVT_CLOSE, self.OnClose)

    def OnClose(self, _evt):
        self.Destroy()


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

I’m on Windows. This is very similar in structure to my code, so should be the same principle at work. Did you select the tree item before attempting the close? Note that I only had the error occur if I selected the tree item (triggering the event and the print statement which you made) and then closed the frame.

If that doesn’t reproduce the error for you, I can try providing my code (didn’t do it initially because I will have to pare it down considerably).

Yes, I selected at least one tree item before closing the frame.

I’m on Windows too. I tried Richard’s code but couldn’t reproduce the runtime error.
(wxPython 4.2.1 and Python 3.11)

I have no idea but if you still get “RuntimeError: wrapped C/C++ object of type TreeCtrl has been deleted”, the following might be a workaround:

    def OnTreeSelection(self, evt):
        if self.tree:
            ...

well, as @RichardT example shows that if the hierarchies (parent - child) are kept properly your situation doesn’t happen (and if, it would pull down most of wx stuff, I think)

so you’ll have to pin down who is destroying your tree :cry: