Within frame should I use panels or not to contain items?

I am trying to figure out the basics here and totally stuck.

I am trying to make a gui program with three distinct areas see below where MyFrame is the frame and Pannel1 through Panel 3 are placed as shown. The reason is that I want to put nav and action buttons in panel 1, A display element in panel2, and 2 display elements in panel 3. or is there an easier way to segment the frame? I will do the leg work, but I cannot break through what is probably a very basic task. HELP!!! And thanks for any assistance.

MyPanel

1 Like

Uhm… this is both a matter of layout technique and of code design.
In principle, the “right” way to go is with panels… lots of them.
Panels handle tab navigation for you, but the main reason to adopt a panel-driven layout is because they help you separate the various aspects/concerns/functionalities of your program. You should think of a panel as a separate, autonomous “unit of work”, complete with widgets and event binding and callbacks, ready to be “plugged in” the rest of your program/interface. By subclassing wx.Panel and writing multiple panel classes, you keep your classes short and you archieve separation of concerns. In general, the panel is the basic building brick of a good wxPython OOP.
Finally, when you need to put all your panels together inside the main window, it’s important not to place them directly on the window, but instead to give the window a “root” panel on its own and then put the other panels on the root panel. This will help you to manage tab navigation across the panels.

For instance, this is how I would probably deal with your layout:

class Panel_1(wx.Panel): # beware: always give a meaningful name!
    def __init__(self, parent):
        wx.Panel.__init__(self, parent)
        self.SetBackgroundColour(wx.RED)
        # here all the widgets, event bindings, callbacks
        # and all the "business logic" needed to make
        # this panel work

class Panel_2(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent)
        self.SetBackgroundColour(wx.YELLOW)
        # etc. etc.

class Panel_3(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent)
        self.SetBackgroundColour(wx.GREEN)
        # etc. etc. 


class MainFrame(wx.Frame):
    def __init__(self, *a, **k):
        wx.Frame.__init__(self, *a, **k)
        root = wx.Panel(self) # a "root" panel to begin with
        panel_1 = Panel_1(root) # all panels are children of the root
        panel_2 = Panel_2(root)
        panel_3 = Panel_3(root)

        s = wx.BoxSizer(wx.VERTICAL)
        s.Add(panel_2, 1, wx.EXPAND)
        s.Add(panel_3, 1, wx.EXPAND)

        root_sizer = wx.BoxSizer(wx.HORIZONTAL)
        root_sizer.Add(panel_1, 1, wx.EXPAND)
        root_sizer.Add(s, 3, wx.EXPAND)
        root.SetSizer(root_sizer)
        # and very often you won't need any more code here!
        # the main window class can unburden itself 
        # from tons of spaghetti code, since all the 
        # functionality is already coded in the separate panels

There is an important caveat, however. Comminication between sibling panels can be difficult, due to how events propagate up the hierarchy in wxPython. For instance, if you have a button on the left panel, and you want something to happen in the right panel when the button is clicked… then you may have a problem.
This panel-driven design is better suited when each panel is really autonomous, and includes all the widgets it needs for its own functionality - basically, a dialog without a dialog window. The more your panels are interwined, the more you should think in terms of some other kind of design.

Sometimes it’s just better to give up. In your case, if you see that your 3 panels will end up sharing too much functionality, then by definition they are no more 3 separate panels. So, let it be just one panel instead and use sizers to organize the layout.
The next step (I’m still referring to your case) is to give up the left panel which, in any case, has no functionality on its own and it’s just a helper for the other two. Instead, place your left buttons directly on the root panel, and let the root (or even the main window) wire up and manage the events:

class Panel_root(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent)
        self.SetBackgroundColour(wx.RED)
        b = wx.Button(self, -1, 'clic')
        self.panel_2 = Panel_2(self)
        self.panel_3 = Panel_3(self)

        s = wx.BoxSizer(wx.VERTICAL)
        s.Add(self.panel_2, 1, wx.EXPAND)
        s.Add(self.panel_3, 1, wx.EXPAND)

        root_sizer = wx.BoxSizer(wx.HORIZONTAL)
        root_sizer.Add(b, 1, wx.ALL, 5)
        root_sizer.Add(s, 3, wx.EXPAND)
        self.SetSizer(root_sizer)

        b.Bind(wx.EVT_BUTTON, self.onclic)

    def onclic(self, e):
        self.panel_2.text.SetValue('hello') # this is VERY ugly

class Panel_2(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent)
        self.SetBackgroundColour(wx.YELLOW)
        self.text = wx.TextCtrl(self, pos=(10, 10))

class Panel_3(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent)
        self.SetBackgroundColour(wx.GREEN)


class MainFrame(wx.Frame):
    def __init__(self, *a, **k):
        wx.Frame.__init__(self, *a, **k)
        Panel_root(self)

I usually don’t like this solution very much. However, even if you adopt it, it’s very important that you don’t allow a class/panel to access directly an internal attribute/widget of another class/panel, like in the previous example. This is very bad OOP, always. You should set up an API of some kind instead, to shield from direct access, like in the following example.
The next step is to have all the 3 panels in place, to take advantage of the upward propagation of the events in wxPython, and let the root panel (or even the main window) receive the events from the left panel and wire them back down to the right panels as appropriate.

class Panel_root(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent)
        self.panel_1 = Panel_1(self)
        self.panel_2 = Panel_2(self)
        self.panel_3 = Panel_3(self)

        s = wx.BoxSizer(wx.VERTICAL)
        s.Add(self.panel_2, 1, wx.EXPAND)
        s.Add(self.panel_3, 1, wx.EXPAND)

        root_sizer = wx.BoxSizer(wx.HORIZONTAL)
        root_sizer.Add(self.panel_1, 1, wx.EXPAND)
        root_sizer.Add(s, 3, wx.EXPAND)
        self.SetSizer(root_sizer)

        self.Bind(wx.EVT_BUTTON, self.onclic)

    def onclic(self, e):
        origin = e.GetEventObject().GetName()
        if origin == 'first button':
            self.panel_2.update('hello') # note that we use an API...
        elif origin == 'second button':
            self.panel_3.update('hello') # ...to avoid direct access

class Panel_1(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent)
        self.SetBackgroundColour(wx.RED)
        b1 = wx.Button(self, -1, 'first', pos=(10, 10), name='first button')
        b2 = wx.Button(self, -1, 'second', pos=(10, 50), name='second button')

class Panel_2(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent)
        self.SetBackgroundColour(wx.YELLOW)
        self.text = wx.TextCtrl(self, pos=(10, 10))

    def update(self, value): # we provide a public API to update this panel
        self.text.SetValue(value)

class Panel_3(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent)
        self.SetBackgroundColour(wx.GREEN)
        self.text = wx.TextCtrl(self, pos=(10, 10))

    def update(self, value):
        self.text.SetValue(value)

class MainFrame(wx.Frame):
    def __init__(self, *a, **k):
        wx.Frame.__init__(self, *a, **k)
        Panel_root(self)

I quite like this one, usually. It allows me to keep my panels separate, while leveraging the upwards-only event propagation to wire the inter-panel communications at the window (or root panel) level. However, please note that this approach requires some knowledge of the wxPython mechanics to work. Here, for instance, we use the “name” API of the widgets to help us identify the button that has been clicked. I use this trick quite often, but it’s not the only one possible.

The next step… well, if you find yourself in a situation where the inter-panel (or inter-window) communication is too much complicated to be handled by the normal wxPython events… you stil have options (like EVT_UPDATE_UI, EVT_IDLE, EventManager, or a Pub/Sub protocol, etc.)… but this will be for another time.

1 Like

Thank you for the excellent reply! I am just beginning with Python and this is exactly what I needed to kick start my understanding.

Oh I like these pure thoughts but usually at the first practical encounter I’m having a break down…

1 Like

“No battle plan survives contact with the enemy” - Helmuth von Moltke, and dozens of OOP programmers ever since :slight_smile:

Wasn’t he one doing in hardware?