How to Create a panel with two frames? And i could select anyone i wanted

How to Create a panel with two frames? And i could select anyone i wanted.

I can only create a top window. But how to create a panel with two or more frames?

In wxPython a frame is a top level window. A panel is a window on which controls are placed, it is usually placed within a frame.

An application can have more than one frame. For example, you could have a main frame which has one or more child frames that can be displayed or hidden as required.

Another technique is to have a single frame that contains several panels, each with its own set of controls. Putting the panels in a sizer allows you to hide or display any of the panels, as required.

Which of these techniques matches what you are trying to do?

Many thanks for your reply. I prefer the first one.
but could u share example of both of them. I like to try it.

Nice of u!

Here is a simple example of the first technique:

import wx

class Frame1(wx.Frame):
    def __init__(self, *args, **kwds):
        wx.Frame.__init__(self, *args, **kwds)
        self.SetSize((400, 300))
        self.SetTitle("Frame 1")
        self.panel = wx.Panel(self, wx.ID_ANY)
        main_sizer = wx.BoxSizer(wx.VERTICAL)
        self.text_ctrl = wx.TextCtrl(self.panel, wx.ID_ANY, "Here is some text", style=wx.TE_MULTILINE)
        self.text_ctrl.SetInsertionPointEnd()
        main_sizer.Add(self.text_ctrl, 1, wx.EXPAND|wx.ALL, 4)
        button_sizer = wx.BoxSizer(wx.HORIZONTAL)
        self.close_button = wx.Button(self.panel, wx.ID_ANY, "Close")
        button_sizer.Add(self.close_button, 0, 0, 0)
        main_sizer.Add(button_sizer, 0, wx.ALIGN_CENTRE|wx.BOTTOM, 4)
        self.panel.SetSizer(main_sizer)
        self.Layout()
        self.Bind(wx.EVT_CLOSE, self.OnClose)
        self.Bind(wx.EVT_BUTTON, self.OnClose, self.close_button)

    def OnClose(self, _event):
        self.Hide()

class Frame2(wx.Frame):
    def __init__(self, *args, **kwds):
        wx.Frame.__init__(self, *args, **kwds)
        self.SetSize((400, 300))
        self.SetTitle("Frame 2")
        self.panel = wx.Panel(self, wx.ID_ANY)
        main_sizer = wx.BoxSizer(wx.VERTICAL)
        self.list_ctrl = wx.ListCtrl(self.panel, wx.ID_ANY, style=wx.LC_REPORT)
        self.list_ctrl.AppendColumn("Name")
        self.list_ctrl.AppendColumn("Date")
        main_sizer.Add(self.list_ctrl, 1, wx.EXPAND|wx.ALL, 4)
        button_sizer = wx.BoxSizer(wx.HORIZONTAL)
        self.close_button = wx.Button(self.panel, wx.ID_ANY, "Close")
        button_sizer.Add(self.close_button, 0, 0, 0)
        main_sizer.Add(button_sizer, 0, wx.ALIGN_CENTRE|wx.BOTTOM, 4)
        self.panel.SetSizer(main_sizer)
        self.Layout()
        self.Bind(wx.EVT_CLOSE, self.OnClose)
        self.Bind(wx.EVT_BUTTON, self.OnClose, self.close_button)

    def OnClose(self, _event):
        self.Hide()

class MainFrame(wx.Frame):
    def __init__(self, *args, **kwds):
        wx.Frame.__init__(self, *args, **kwds)
        self.SetSize((240, 80))
        self.SetTitle("Main Frame")
        self.panel = wx.Panel(self, wx.ID_ANY)
        main_sizer = wx.BoxSizer(wx.VERTICAL)
        button_sizer = wx.BoxSizer(wx.HORIZONTAL)
        self.frame_1_button = wx.Button(self.panel, wx.ID_ANY, "Frame 1")
        button_sizer.Add(self.frame_1_button, 0, wx.RIGHT, 24)
        self.frame_2_button = wx.Button(self.panel, wx.ID_ANY, "Frame 2")
        button_sizer.Add(self.frame_2_button, 0, 0, 0)
        main_sizer.Add(button_sizer, 0, wx.ALIGN_CENTRE|wx.TOP|wx.BOTTOM, 8)
        self.panel.SetSizer(main_sizer)
        self.Layout()

        self.Bind(wx.EVT_BUTTON, self.OnFrame1, self.frame_1_button)
        self.Bind(wx.EVT_BUTTON, self.OnFrame2, self.frame_2_button)

        self.frame_1 = Frame1(self, pos=(300, 0))
        self.frame_2 = Frame2(self, pos=(310, 0))

    def OnFrame1(self, _event):
        self.frame_2.Hide()
        self.frame_1.Show()

    def OnFrame2(self, _event):
        self.frame_1.Hide()
        self.frame_2.Show()

class MyApp(wx.App):
    def OnInit(self):
        self.frame = MainFrame(None)
        self.SetTopWindow(self.frame)
        self.frame.Show()
        return True

if __name__ == "__main__":
    app = MyApp(0)
    app.MainLoop()

Tested using wxPython 4.2.1 gtk3 (phoenix) wxWidgets 3.2.4 + Python 3.12.3 + Linux Mint 22

I think I have an example of the second somewhere, may take a while to find…

EDIT: I just realised there was a bug in this example. If you closed either of the child frames using the button on its window decoration then the frame would be destroyed and couldn’t be shown again. I have modified the code above to bind EVT_CLOSE in the child frames to fix the issue.

1 Like

Below is an example that demonstrates the second technique. When you click on the arrow buttons on the toolbar, it shows/hides a couple of sidebars to the left and right of the TextCtrl.

import wx

ID_LEFT = wx.NewIdRef()
ID_RIGHT = wx.NewIdRef()

class MyFrame(wx.Frame):
    def __init__(self, *args, **kwds):
        wx.Frame.__init__(self, *args, **kwds)
        self.SetSize((600, 500))
        self.SetTitle("Sidebars")
        self.frame_statusbar = self.CreateStatusBar(1)
        self.frame_statusbar.SetStatusWidths([-1])
        self.toolbar = wx.ToolBar(self, -1)
        self.toolbar.AddTool(ID_LEFT, "Left sidebar", wx.ArtProvider.GetBitmap(wx.ART_GOTO_FIRST, wx.ART_TOOLBAR, (24, 24)), wx.NullBitmap, wx.ITEM_NORMAL, "", "")
        self.toolbar.AddTool(ID_RIGHT, "Right sidebar", wx.ArtProvider.GetBitmap(wx.ART_GOTO_LAST, wx.ART_TOOLBAR, (24, 24)), wx.NullBitmap, wx.ITEM_NORMAL, "", "")
        self.SetToolBar(self.toolbar)
        self.toolbar.Realize()

        self.main_panel = wx.Panel(self, wx.ID_ANY)
        self.main_sizer = wx.BoxSizer(wx.HORIZONTAL)

        self.left_panel = wx.Panel(self.main_panel, wx.ID_ANY)
        self.left_panel.SetMinSize((120, -1))
        self.main_sizer.Add(self.left_panel, 0, wx.EXPAND | wx.RIGHT, 2)
        left_sizer = wx.BoxSizer(wx.VERTICAL)
        self.left_list_box = wx.ListBox(self.left_panel, wx.ID_ANY, choices=["Red", "Green", "Blue", "Yellow"])
        left_sizer.Add(self.left_list_box, 1, wx.EXPAND, 2)
        left_button_sizer = wx.BoxSizer(wx.HORIZONTAL)
        left_sizer.Add(left_button_sizer, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.TOP, 2)

        self.add_button = wx.Button(self.left_panel, wx.ID_ANY, "+")
        self.add_button.SetMinSize((30, 30))
        left_button_sizer.Add(self.add_button, 0, wx.RIGHT, 8)

        self.minus_button = wx.Button(self.left_panel, wx.ID_ANY, "-")
        self.minus_button.SetMinSize((30, 30))
        left_button_sizer.Add(self.minus_button, 0, 0, 0)

        self.center_panel = wx.Panel(self.main_panel, wx.ID_ANY)
        self.main_sizer.Add(self.center_panel, 1, wx.EXPAND, 3)

        centre_sizer = wx.BoxSizer(wx.VERTICAL)

        centre_button_sizer = wx.BoxSizer(wx.HORIZONTAL)
        centre_sizer.Add(centre_button_sizer, 0, wx.ALIGN_CENTER_HORIZONTAL, 0)

        self.bold_button = wx.Button(self.center_panel, wx.ID_ANY, "B")
        self.bold_button.SetMinSize((30, 30))
        centre_button_sizer.Add(self.bold_button, 0, wx.RIGHT, 8)

        self.italic_button = wx.Button(self.center_panel, wx.ID_ANY, "I")
        self.italic_button.SetMinSize((30, 30))
        self.italic_button.SetFont(wx.Font(10, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_ITALIC, wx.FONTWEIGHT_NORMAL, 0, ""))
        centre_button_sizer.Add(self.italic_button, 0, 0, 0)

        self.text_ctrl = wx.TextCtrl(self.center_panel, wx.ID_ANY, "", style=wx.TE_MULTILINE)
        centre_sizer.Add(self.text_ctrl, 1, wx.EXPAND | wx.TOP, 2)

        self.right_panel = wx.Panel(self.main_panel, wx.ID_ANY)
        self.right_panel.SetMinSize((120, -1))
        self.main_sizer.Add(self.right_panel, 0, wx.EXPAND | wx.LEFT, 2)

        right_sizer = wx.BoxSizer(wx.VERTICAL)

        self.right_list_box = wx.ListBox(self.right_panel, wx.ID_ANY, choices=["choice 1", "choice 2", "choice 3"])
        right_sizer.Add(self.right_list_box, 1, wx.EXPAND, 2)

        self.right_panel.SetSizer(right_sizer)

        self.center_panel.SetSizer(centre_sizer)

        self.left_panel.SetSizer(left_sizer)

        self.main_panel.SetSizer(self.main_sizer)

        self.Layout()

        self.main_sizer.Hide(self.left_panel)
        self.main_sizer.Hide(self.right_panel)

        self.Bind(wx.EVT_TOOL, self.OnLeft,  id=ID_LEFT)
        self.Bind(wx.EVT_TOOL, self.OnRight, id=ID_RIGHT)


    def OnLeft(self, event):
        if self.main_sizer.IsShown(self.left_panel):
            self.main_sizer.Hide(self.left_panel)
        else:
            self.main_sizer.Show(self.left_panel)
        self.main_sizer.Layout()


    def OnRight(self, event):
        if self.main_sizer.IsShown(self.right_panel):
            self.main_sizer.Hide(self.right_panel)
        else:
            self.main_sizer.Show(self.right_panel)
        self.main_sizer.Layout()


class MyApp(wx.App):
    def OnInit(self):
        self.frame = MyFrame(None, wx.ID_ANY, "")
        self.SetTopWindow(self.frame)
        self.frame.Show()
        return True


if __name__ == "__main__":
    app = MyApp(0)
    app.MainLoop()
1 Like

Thanks for your python code.

I have try them. looks nice!

But seem like different with my wants. as below picture.
Could u give some sample like below?

Many thanks!

Your picture looks more like a wx.Notebook.

I made a quick example in wxGlade:

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
#
# generated by wxGlade 1.1.0b1 on Wed Sep  4 08:22:33 2024
#

import wx

# begin wxGlade: dependencies
# end wxGlade

# begin wxGlade: extracode
# end wxGlade


class MyFrame(wx.Frame):
    def __init__(self, *args, **kwds):
        # begin wxGlade: MyFrame.__init__
        kwds["style"] = kwds.get("style", 0) | wx.DEFAULT_FRAME_STYLE
        wx.Frame.__init__(self, *args, **kwds)
        self.SetSize((400, 300))
        self.SetTitle("frame")

        self.panel_1 = wx.Panel(self, wx.ID_ANY)

        sizer_1 = wx.BoxSizer(wx.VERTICAL)

        self.notebook = wx.Notebook(self.panel_1, wx.ID_ANY)
        sizer_1.Add(self.notebook, 1, wx.EXPAND, 0)

        self.main_pane = wx.Panel(self.notebook, wx.ID_ANY)
        self.notebook.AddPage(self.main_pane, "Main")

        sizer_2 = wx.BoxSizer(wx.VERTICAL)

        self.text_ctrl = wx.TextCtrl(self.main_pane, wx.ID_ANY, "", style=wx.TE_MULTILINE)
        sizer_2.Add(self.text_ctrl, 1, wx.EXPAND, 0)

        self.button_1 = wx.Button(self.main_pane, wx.ID_ANY, "button_1")
        sizer_2.Add(self.button_1, 0, 0, 0)

        self.special_pane = wx.Panel(self.notebook, wx.ID_ANY)
        self.notebook.AddPage(self.special_pane, "Special")

        sizer_3 = wx.BoxSizer(wx.VERTICAL)

        self.list_ctrl_1 = wx.ListCtrl(self.special_pane, wx.ID_ANY, style=wx.LC_HRULES | wx.LC_REPORT | wx.LC_VRULES)
        self.list_ctrl_1.AppendColumn("A", format=wx.LIST_FORMAT_LEFT, width=-1)
        self.list_ctrl_1.AppendColumn("B", format=wx.LIST_FORMAT_LEFT, width=-1)
        self.list_ctrl_1.AppendColumn("C", format=wx.LIST_FORMAT_LEFT, width=-1)
        sizer_3.Add(self.list_ctrl_1, 1, wx.EXPAND, 0)

        self.special_pane.SetSizer(sizer_3)

        self.main_pane.SetSizer(sizer_2)

        self.panel_1.SetSizer(sizer_1)

        self.Layout()
        # end wxGlade

# end of class MyFrame

class MyApp(wx.App):
    def OnInit(self):
        self.frame = MyFrame(None, wx.ID_ANY, "")
        self.SetTopWindow(self.frame)
        self.frame.Show()
        return True

# end of class MyApp

if __name__ == "__main__":
    app = MyApp(0)
    app.MainLoop()

Sure this is what i want. Thanks!

One more question is how to create below boundary?
Screenshot 2024-09-04 163029

I would suggest using a wx.StaticBoxSizer

In the example below I have tried to emulate one of the sections from your picture.

import wx

class MyFrame(wx.Frame):
    def __init__(self, *args, **kwds):
        kwds["style"] = kwds.get("style", 0) | wx.DEFAULT_FRAME_STYLE
        wx.Frame.__init__(self, *args, **kwds)
        self.SetSize((400, 300))
        self.SetTitle("StaticBoxSizer Example")

        self.main_panel = wx.Panel(self, wx.ID_ANY)
        main_sizer = wx.BoxSizer(wx.VERTICAL)

        led_toggle_staticbox_sizer = wx.StaticBoxSizer(wx.StaticBox(self.main_panel, wx.ID_ANY, "LED Toggle"), wx.HORIZONTAL)
        main_sizer.Add(led_toggle_staticbox_sizer, 0, 0, 0)

        led_toggle_grid_sizer = wx.FlexGridSizer(5, 3, 12, 12)
        led_toggle_staticbox_sizer.Add(led_toggle_grid_sizer, 0, wx.ALL, 6)

        self.led1_tb = wx.ToggleButton(led_toggle_staticbox_sizer.GetStaticBox(), wx.ID_ANY, "LED1")
        led_toggle_grid_sizer.Add(self.led1_tb, 0, 0, 0)

        self.led2_tb = wx.ToggleButton(led_toggle_staticbox_sizer.GetStaticBox(), wx.ID_ANY, "LED2")
        led_toggle_grid_sizer.Add(self.led2_tb, 0, 0, 0)

        self.led3_tb = wx.ToggleButton(led_toggle_staticbox_sizer.GetStaticBox(), wx.ID_ANY, "LED3")
        led_toggle_grid_sizer.Add(self.led3_tb, 0, 0, 0)

        self.led4_tb = wx.ToggleButton(led_toggle_staticbox_sizer.GetStaticBox(), wx.ID_ANY, "LED4")
        led_toggle_grid_sizer.Add(self.led4_tb, 0, 0, 0)

        self.led5_tb = wx.ToggleButton(led_toggle_staticbox_sizer.GetStaticBox(), wx.ID_ANY, "LED5")
        led_toggle_grid_sizer.Add(self.led5_tb, 0, 0, 0)

        self.led6_tb = wx.ToggleButton(led_toggle_staticbox_sizer.GetStaticBox(), wx.ID_ANY, "LED6")
        led_toggle_grid_sizer.Add(self.led6_tb, 0, 0, 0)

        self.led7_tb = wx.ToggleButton(led_toggle_staticbox_sizer.GetStaticBox(), wx.ID_ANY, "LED7")
        led_toggle_grid_sizer.Add(self.led7_tb, 0, 0, 0)

        self.led8_tb = wx.ToggleButton(led_toggle_staticbox_sizer.GetStaticBox(), wx.ID_ANY, "LED8")
        led_toggle_grid_sizer.Add(self.led8_tb, 0, 0, 0)

        self.led9_tb = wx.ToggleButton(led_toggle_staticbox_sizer.GetStaticBox(), wx.ID_ANY, "LED9")
        led_toggle_grid_sizer.Add(self.led9_tb, 0, 0, 0)

        self.led10_tb = wx.ToggleButton(led_toggle_staticbox_sizer.GetStaticBox(), wx.ID_ANY, "LED10")
        led_toggle_grid_sizer.Add(self.led10_tb, 0, 0, 0)

        self.led11_tb = wx.ToggleButton(led_toggle_staticbox_sizer.GetStaticBox(), wx.ID_ANY, "LED11")
        led_toggle_grid_sizer.Add(self.led11_tb, 0, 0, 0)

        self.led12_tb = wx.ToggleButton(led_toggle_staticbox_sizer.GetStaticBox(), wx.ID_ANY, "LED12")
        led_toggle_grid_sizer.Add(self.led12_tb, 0, 0, 0)

        self.led_all_tb = wx.ToggleButton(led_toggle_staticbox_sizer.GetStaticBox(), wx.ID_ANY, "ALL")
        led_toggle_grid_sizer.Add(self.led_all_tb, 0, 0, 0)

        label = wx.StaticText(led_toggle_staticbox_sizer.GetStaticBox(), wx.ID_ANY, "Device ID:")
        led_toggle_grid_sizer.Add(label, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT, 0)

        self.led_toggle_spin_ctrl = wx.SpinCtrl(led_toggle_staticbox_sizer.GetStaticBox(), wx.ID_ANY, "0", min=0, max=100)
        led_toggle_grid_sizer.Add(self.led_toggle_spin_ctrl, 0, 0, 0)

        led_toggle_grid_sizer.AddGrowableCol(0)
        led_toggle_grid_sizer.AddGrowableCol(1)
        led_toggle_grid_sizer.AddGrowableCol(2)

        self.main_panel.SetSizer(main_sizer)
        self.Layout()

class MyApp(wx.App):
    def OnInit(self):
        self.frame = MyFrame(None, wx.ID_ANY, "")
        self.SetTopWindow(self.frame)
        self.frame.Show()
        return True

if __name__ == "__main__":
    app = MyApp(0)
    app.MainLoop()

Screenshot at 2024-09-04 13-23-28

Note:

  1. On Linux, many desktop themes do not display the border of a wx.StaticBoxSizer. I had to hack one of the CSS files of my favourite theme in order for the borders to be visible.
  2. The wx.SpinCtrl on Linux looks different to that on Windows.

Tested using wxPython 4.2.1 gtk3 (phoenix) wxWidgets 3.2.4 + Python 3.12.3 + Linux Mint 22

1 Like

Wow it works on my windows as you can see below picture.
I try to read your code. and delete some lines, it still works well.

But how to link event on this buttons?

Yes, sorry, I was experimenting and left in some lines that weren’t needed.

Also, I guessed that because the label said “LED Toggle” that the buttons should be toggle buttons.

If that is the case, then you could bind the toggle buttons to a single event handler like this:

import wx

class MyFrame(wx.Frame):
    def __init__(self, *args, **kwds):
        kwds["style"] = kwds.get("style", 0) | wx.DEFAULT_FRAME_STYLE
        wx.Frame.__init__(self, *args, **kwds)
        self.SetSize((400, 300))
        self.SetTitle("StaticBoxSizer Example")

        self.main_panel = wx.Panel(self, wx.ID_ANY)
        main_sizer = wx.BoxSizer(wx.VERTICAL)

        led_toggle_staticbox_sizer = wx.StaticBoxSizer(wx.StaticBox(self.main_panel, wx.ID_ANY, "LED Toggle"), wx.HORIZONTAL)
        main_sizer.Add(led_toggle_staticbox_sizer, 0, 0, 0)

        led_toggle_grid_sizer = wx.FlexGridSizer(5, 3, 12, 12)
        led_toggle_staticbox_sizer.Add(led_toggle_grid_sizer, 0, wx.ALL, 6)

        self.led_toggle_buttons = []
        for i in range(12):
            label = f"LED{i+1}"
            tb = wx.ToggleButton(led_toggle_staticbox_sizer.GetStaticBox(), wx.ID_ANY, label)
            led_toggle_grid_sizer.Add(tb, 0, 0, 0)
            self.led_toggle_buttons.append(tb)
            self.Bind(wx.EVT_TOGGLEBUTTON, self.OnToggleButton, tb)

        self.led_all_tb = wx.ToggleButton(led_toggle_staticbox_sizer.GetStaticBox(), wx.ID_ANY, "ALL")
        led_toggle_grid_sizer.Add(self.led_all_tb, 0, 0, 0)
        self.led_toggle_buttons.append(self.led_all_tb)
        self.Bind(wx.EVT_TOGGLEBUTTON, self.OnToggleButton, self.led_all_tb)

        label = wx.StaticText(led_toggle_staticbox_sizer.GetStaticBox(), wx.ID_ANY, "Device ID:")
        led_toggle_grid_sizer.Add(label, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT, 0)

        self.led_toggle_spin_ctrl = wx.SpinCtrl(led_toggle_staticbox_sizer.GetStaticBox(), wx.ID_ANY, "0", min=0, max=100)
        led_toggle_grid_sizer.Add(self.led_toggle_spin_ctrl, 0, 0, 0)

        self.main_panel.SetSizer(main_sizer)

    def OnToggleButton(self, event):
        tb = event.GetEventObject()
        label = tb.GetLabel()
        print(label)


class MyApp(wx.App):
    def OnInit(self):
        self.frame = MyFrame(None, wx.ID_ANY, "")
        self.SetTopWindow(self.frame)
        self.frame.Show()
        return True

if __name__ == "__main__":
    app = MyApp(0)
    app.MainLoop()

In this example, the OnToggleButton() method just prints the button’s label, but you could use the label to choose what action to take.

Sure it works . If i select id=3 how could i get this value?
Screenshot 2024-09-05 175151-01

You need to call the spin control’s GetValue() method.

Here is a modified version of the OnToggleButton() method which prints the label of the toggle button, whether the button is selected or not, plus the value from the spin control:

    def OnToggleButton(self, event):
        tb = event.EventObject
        selected = bool(event.Selection)
        label = tb.GetLabel()
        dev_id = self.led_toggle_spin_ctrl.GetValue()
        print(label, selected, dev_id)

Thanks!

Now i have a new question . ask for your patience.

If i Cycle send a message through serial com. how could i use other functions at the same time.

For example when i enable a cycle send function. (like serial com send 0x80 100ms / period)

the python will be involved in this cycle. other function could not be used.

How do deal with this situation?