1

I'm trying to make a help menu in wxPython that has a tree control on the left and an html viewer on the right; the treecontrol acts as a sort of navigation menu, and on clicking an item in the treecontrol, the appropriate local .html file should load in the left panel.

However, when I try to implement this, I'm unable to bind any events to the different nodes in the tree control. This is what I have so far -- could someone show me where I'm going wrong?

Code:

import wx
import wx.html


class HelpMenu(wx.Frame):
    def __init__(self, parent):
        tstr = "Need help?"
        super(HelpMenu, self).__init__(
            parent, -1, title=tstr, size=wx.Size(800, 500), style=wx.CAPTION |
            wx.CLOSE_BOX | wx.SYSTEM_MENU | wx.RESIZE_BORDER)

        splitter = wx.SplitterWindow(self)
        window1 = NavBar(splitter)
        window2 = htmlPanel(splitter)
        splitter.SplitVertically(window1, window2)
        splitter.SetSashGravity(0.5)
        # splitter.SetMinimumPaneSize(20)

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


class htmlPanel(wx.Panel):
    def __init__(self, parent):
        super(htmlPanel, self).__init__(parent)

        html = wx.html.HtmlWindow(self)
        if 'gtk2' in wx.PlatformInfo:
            html.SetStandardFonts()

        # htmlszr = wx.BoxSizer(wx.VERTICAL)
        # htmlszr.Add(html, 0, wx.EXPAND)
        html.LoadPage('.\\index.html')
        # self.SetSizer(htmlszr)


class NavTree(wx.TreeCtrl):
    def __init__(self, parent, id, pos, size, style):
        super(NavTree, self).__init__(parent, id, pos, size, style)


class NavBar(wx.Panel):
    def __init__(self, parent):
        super(NavBar, self).__init__(parent)

        self.tree = NavTree(
            self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TR_HAS_BUTTONS)
        self.root = self.tree.AddRoot('Help Contents')
        self.welcome = self.tree.AppendItem(self.root, 'Start Page')
        self.background = self.tree.AppendItem(self.root, 'Background')
        self.method = self.tree.AppendItem(self.root, 'Calculation Method')
        self.using = self.tree.AppendItem(self.root, 'Using the Calculator')
        self.start = self.tree.AppendItem(self.using, 'Selecting Data Type')
        self.params = self.tree.AppendItem(self.using, 'Setting Parameters')
        self.results = self.tree.AppendItem(self.using, 'Viewing Results')
        self.interp = self.tree.AppendItem(self.root, 'Interpreting Results')
        # insert more children of interp here for various issues concerning
        # interpretation
        self.welcome.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self._welcome)
        self.background.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self._background)
        self.method.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self._method)
        self.using.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self._using)
        self.start.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self._start)
        self.params.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self._params)
        self.results.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self._results)
        self.interp.Bind(wx.EVT_TREE_ITEM_ACTIVATE, self._interp)

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

        def _welcome(self, event):
            htmlPanel.html.LoadPage('.\\Help\\index.html')

        def _background(self, event):
            htmlPanel.html.LoadPage('.\\Help\\background.html')

        def _method(self, event):
            htmlPanel.html.LoadPage('.\\Help\\method.html')

        def _using(self, event):
            htmlPanel.html.LoadPage('.\\Help\\use.html')

        def _start(self, event):
            htmlPanel.html.LoadPage('.\\Help\\start.html')

        def _params(self, event):
            htmlPanel.html.LoadPage('\\Help\\params.html')

        def _results(self, event):
            htmlPanel.html.LoadPage('\\Help\\results.html')

        def _interp(self, event):
            htmlPanel.html.LoadPage('\\Help\\interpretation.html')


if __name__ == "__main__":
    app = wx.App(False)
    frame = HelpMenu(None)
    frame.Show()
    app.MainLoop()

Output:

>>> Traceback (most recent call last):
  File "c:\Python\testing\splitterwindow.py", line 100, in <module>
    frame = HelpMenu(None)
  File "c:\Python\testing\splitterwindow.py", line 13, in
__init__
    window1 = NavBar(splitter)
  File "c:\Python\testing\splitterwindow.py", line 60, in __init__
    self.welcome.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self._welcome)
AttributeError: 'TreeItemId' object has no attribute 'Bind'
>>> C:\Python\testing>

I have a sneaking suspicion that self.welcome isn't referring to the node itself, but rather to something else, and this is the source of the error. I'm just not quite sure how to refer to the actual node/text in the treecontrol to be able to bind an event handler to it.

EDIT Rolf has me 99% of the way there, except for this weird behavior where only the root populates -- none of the children. See screenshot below.navbar fail

W. MacTurk
  • 130
  • 1
  • 15
  • 1
    `Bind` to `self.tree` then interpret the event but you have more than 1 issue with that code. – Rolf of Saxony Nov 01 '21 at 17:14
  • How will the event handler know which node was selected/activated? I saw some mention of people monitoring the cursor and checking what the x,y position was on left click, checking to see which (if any) node was under the cursor, and calling the appropriate event handler if so... but that seems really hacky and complicated for something that should be quite simple, no? – W. MacTurk Nov 01 '21 at 18:04
  • 1
    See the answer I've posted. It too might be considered a bit hacky but it works. – Rolf of Saxony Nov 01 '21 at 19:56

1 Answers1

2

Here's a mock-up of your code, which I've adapted to use the labels in the treectrl to decide what to display.

import wx
import wx.html


class HelpMenu(wx.Frame):
    def __init__(self, parent):
        tstr = "Need help?"
        super(HelpMenu, self).__init__(
            parent, -1, title=tstr, size=wx.Size(800, 500), style=wx.CAPTION |
            wx.CLOSE_BOX | wx.SYSTEM_MENU | wx.RESIZE_BORDER)

        splitter = wx.SplitterWindow(self)
        window1 = NavBar(splitter)
        window2 = htmlPanel(splitter)
        splitter.SplitVertically(window1, window2)
        splitter.SetSashGravity(0.5)
        # splitter.SetMinimumPaneSize(20)

        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(splitter, 1, wx.EXPAND)
        self.SetSizer(sizer)
        self.Show()

class htmlPanel(wx.Panel):
    def __init__(self, parent):
        super(htmlPanel, self).__init__(parent)

        self.html = wx.html.HtmlWindow(self)
        if 'gtk2' in wx.PlatformInfo:
            self.html.SetStandardFonts()

        htmlszr = wx.BoxSizer(wx.VERTICAL)
        htmlszr.Add(self.html, 1, wx.EXPAND)
        self.html.LoadPage('index.html')
        self.SetSizer(htmlszr)


class NavTree(wx.TreeCtrl):
    def __init__(self, parent, id, pos, size, style):
        super(NavTree, self).__init__(parent, id, pos, size, style)


class NavBar(wx.Panel):
    def __init__(self, parent):
        super(NavBar, self).__init__(parent)
        self.tree = NavTree(
            self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TR_HAS_BUTTONS)
        self.parent = parent
        self.root = self.tree.AddRoot('Help Contents')
        self.welcome = self.tree.AppendItem(self.root, 'Start Page')
        self.background = self.tree.AppendItem(self.root, 'Background')
        self.method = self.tree.AppendItem(self.root, 'Calculation Method')
        self.using = self.tree.AppendItem(self.root, 'Using the Calculator')
        self.start = self.tree.AppendItem(self.using, 'Selecting Data Type')
        self.params = self.tree.AppendItem(self.using, 'Setting Parameters')
        self.results = self.tree.AppendItem(self.using, 'Viewing Results')
        self.interp = self.tree.AppendItem(self.root, 'Interpreting Results')
        # insert more children of interp here for various issues concerning
        # interpretation
        self.tree.ExpandAll()
        self.tree.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self._click)

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

    def _click(self, event):
        item = event.GetItem()
        label = self.tree.GetItemText(item)
        print(label)
        if label == 'Help Contents':
            self._welcome()
        elif label == 'Background':
            self._background()
        elif label == 'Calculation Method':
            self._method()
        elif label == 'Start Page':
            self._start()
        elif label == 'Selecting Data Type':
            self._using()
        elif label == 'Setting Parameters':
            self._params()                                            
        elif label == 'Viewing Results':
            self._results()        
        elif label == 'Interpreting Results':
            self._interp()

    def _welcome(self):
        self.parent.GetWindow2().html.LoadPage('index.html')

    def _background(self):
        self.parent.GetWindow2().html.LoadPage('background.html')

    def _method(self):
        self.parent.GetWindow2().html.LoadPage('method.html')

    def _using(self):
        self.parent.GetWindow2().html.LoadPage('use.html')

    def _start(self):
        self.parent.GetWindow2().html.LoadPage('index.html')

    def _params(self):
        self.parent.GetWindow2().html.LoadPage('params.html')

    def _results(self):
        self.parent.GetWindow2().html.LoadPage('results.html')

    def _interp(self):
        self.parent.GetWindow2().html.LoadPage('interp.html')

if __name__ == "__main__":
    app = wx.App(False)
    frame = HelpMenu(None)
    frame.Show()
    app.MainLoop()

enter image description here

Here's a variant of the NavBar class using lists for the text and related html files, linked simply using the index position.

class NavBar(wx.Panel):
    def __init__(self, parent):
        super(NavBar, self).__init__(parent)
        self.labs = ['Help Contents', 'Start Page', 'Background', 'Calculation Method', 'Using the Calculator',\
                     'Selecting Data Type', 'Setting Parameters', 'Viewing Results', 'Interpreting Results']
        self.pages = ['index.html','index.html','background.html','method.html',None,'use.html',\
                      'params.html','results.html','interp.html']
        self.tree = NavTree(
            self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.TR_HAS_BUTTONS)
        self.parent = parent
        self.root = self.tree.AddRoot(self.labs[0])
        self.tree.AppendItem(self.root, self.labs[1])
        self.tree.AppendItem(self.root, self.labs[2])
        self.tree.AppendItem(self.root, self.labs[3])
        self.using = self.tree.AppendItem(self.root, self.labs[4])
        self.tree.AppendItem(self.using, self.labs[5])
        self.tree.AppendItem(self.using, self.labs[6])
        self.tree.AppendItem(self.using, self.labs[7])
        self.tree.AppendItem(self.root, self.labs[8])
        # insert more children of interp here for various issues concerning
        # interpretation
        self.tree.ExpandAll()
        self.tree.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self._click)

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

    def _click(self, event):
        item = event.GetItem()
        label = self.tree.GetItemText(item)
        if label:
            pass
        else:
            return
        index = self.labs.index(label)
        if self.pages[index]:
            self.parent.GetWindow2().html.LoadPage(self.pages[index])
Rolf of Saxony
  • 21,661
  • 5
  • 39
  • 60
  • Ah, yep, definitely caught a few dumb mistakes in my code. You're always saving my skin, Rolf! Although, running the code, the treectrl doesn't populate for me. I get the root node to display, index.html loads correctly, and no errors get thrown, but none of the children populate in the tree. I added a screenshot to the original post to show what I get. Note: I tweaked your code a bit, then got the error, but even when copy/pasting directly from your post (and moving the .html files to the proper directories), the behavior is identical. – W. MacTurk Nov 02 '21 at 03:20
  • 1
    See the edited answer. I simply added `self.tree.ExpandAll()`. You can expand items, nodes or everything. – Rolf of Saxony Nov 02 '21 at 07:41
  • Ah, I thought the default behavior was to have the children expanded. Rolf saves the day again! Cheers! – W. MacTurk Nov 02 '21 at 14:55