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?
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.
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()
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?
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()
Note:
wx.StaticBoxSizer
. I had to hack one of the CSS files of my favourite theme in order for the borders to be visible.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
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?
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?