I have set up a wxpython GUI that has a menu bar and some items in the menu bar. What I would like to do is select an item in my menu bar (ex. File - Options), and when i select "Options" have a dialog box pop up in which I can set different parameters in my code. Similar behaviors would be wx.FontDialog and wx.FileDialog -- However, I want mine to be custom in that i could have radio buttons and check boxes as selectable options. How do I do this?
Snippets of my code are:
Here is where I set up part of the Main application and GUI (I have layout and box sizers set up in another section):
class TMainForm(wx.Frame):
def __init__(self, *args, **kwds):
kwds["style"] = wx.DEFAULT_FRAME_STYLE
wx.Frame.__init__(self, *args, **kwds)
self.Splitter = wx.SplitterWindow(self, -1, style=wx.SP_3D|wx.SP_BORDER)
self.PlotPanel = wx.Panel(self.Splitter, -1)
self.FilePanel = wx.Panel(self.Splitter, -1)
#self.SelectionPanel = wx.Panel(self.Splitter,-1)
self.Notebook = wx.Notebook(self.FilePanel, -1)#, style=0)
self.ReportPage = wx.Panel(self.Notebook, -1)
self.FilePage = wx.Panel(self.Notebook, -1)
Here is where I set up part of the Menu Bar:
self.MainMenu = wx.MenuBar()
self.FileMenu = wx.Menu()
self.OptimizeMenu = wx.Menu()
self.HelpMenu = wx.Menu()
self.OptimizeOptions= wx.MenuItem(self.OptimizeMenu, 302, "&Select Parameters","Select Parameters for Optimization",wx.ITEM_NORMAL)
self.OptimizeMenu.AppendItem(self.OptimizeOptions)
self.MainMenu.Append(self.OptimizeMenu, "&Optimization")
Here is where I bind an event to my "options" part of my menu bar. When I click on this, I want a pop up menu dialog to show up
self.Bind(wx.EVT_MENU, self.OnOptimizeOptions, self.OptimizeOptions)
This is the function in which i'm hoping the pop up menu will be defined. I would like to do it in this format if possible (rather than doing separate classes).
def OnOptimizeOptions(self,event):
give me a dialog box (radio buttons, check boxes, etc)
I have only shown snippets, but all of my code does work. My GUI and menu bars are set up correctly - i just don't know how to get a pop up menu like the wx.FileDialog and wx.FontDialog menus. Any help would be great! Thanks
You would want to instantiate a dialog in your handler (OnOptimizeOptions). Basically you would subclass wx.Dialog and put in whatever widgets you want. Then you'd instantiate it in your handler and call ShowModal. Something like this psuedo-code:
myDlg = MyDialog(*args)
myDlg.ShowModal()
See the Custom Dialog part on zetcodes site: http://zetcode.com/wxpython/dialogs/ (near the bottom) for one example.
EDIT - Here's an example:
import wx
########################################################################
class MyDialog(wx.Dialog):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Dialog.__init__(self, None, title="Options")
radio1 = wx.RadioButton( self, -1, " Radio1 ", style = wx.RB_GROUP )
radio2 = wx.RadioButton( self, -1, " Radio2 " )
radio3 = wx.RadioButton( self, -1, " Radio3 " )
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(radio1, 0, wx.ALL, 5)
sizer.Add(radio2, 0, wx.ALL, 5)
sizer.Add(radio3, 0, wx.ALL, 5)
for i in range(3):
chk = wx.CheckBox(self, label="Checkbox #%s" % (i+1))
sizer.Add(chk, 0, wx.ALL, 5)
self.SetSizer(sizer)
########################################################################
class MyForm(wx.Frame):
#----------------------------------------------------------------------
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "wx.Menu Tutorial")
# Add a panel so it looks the correct on all platforms
self.panel = wx.Panel(self, wx.ID_ANY)
menuBar = wx.MenuBar()
fileMenu = wx.Menu()
optionsItem = fileMenu.Append(wx.NewId(), "Options",
"Show an Options Dialog")
self.Bind(wx.EVT_MENU, self.onOptions, optionsItem)
exitMenuItem = fileMenu.Append(wx.NewId(), "Exit",
"Exit the application")
self.Bind(wx.EVT_MENU, self.onExit, exitMenuItem)
menuBar.Append(fileMenu, "&File")
self.SetMenuBar(menuBar)
#----------------------------------------------------------------------
def onExit(self, event):
""""""
self.Close()
#----------------------------------------------------------------------
def onOptions(self, event):
""""""
dlg = MyDialog()
dlg.ShowModal()
dlg.Destroy()
#----------------------------------------------------------------------
# Run the program
if __name__ == "__main__":
app = wx.PySimpleApp()
frame = MyForm().Show()
app.MainLoop()
Related
I want to add a few submenu items to an existing popup menu item. I found several examples on how to create a submenu for a standard menu, but none about creating a submenu for a popup menu.
I tried to apply to the popup menu the same logic as for a standard menu, but failed to do that properly (while at the same time I have no trouble in creating a standard menu with submenus).
In a portion of code like the one below, how to add a submenu ?
# ...
class ButtonContext(wx.Menu):
def __init__(self, parent):
super(ButtonContext, self).__init__()
self.parent = parent
button_popup = wx.MenuItem(self, wx.ID_ANY, 'test popup')
self.Append(button_popup)
self.Bind(wx.EVT_MENU, self.button_action, button_popup)
def button_action(self, event):
event.Skip()
# ...
This will work, in fact popup menus are based on wx.Menu class so the logic and the behaviour are the same
class ButtonContext(wx.Menu):
def __init__(self, parent):
super(ButtonContext, self).__init__()
self.parent = parent
button_popup = wx.MenuItem(self, wx.ID_ANY, 'test popup')
submenu_popup = wx.Menu()
submenu_button_popup = wx.MenuItem(self, wx.ID_ANY, 'test submenu popup')
submenu_popup.Append(submenu_button_popup)
self.AppendSubMenu(submenu_popup, "submenu")
self.Append(button_popup)
self.Bind(wx.EVT_MENU, self.button_action, button_popup)
Just to illustrate the SetSubMenu(menu) method which can also be used to create a sub-menu in this instance.
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("test frame")
sizer_0 = wx.BoxSizer(wx.HORIZONTAL)
self.window_1 = wx.SplitterWindow(self, wx.ID_ANY)
self.window_1.SetMinimumPaneSize(20)
sizer_0.Add(self.window_1, 1, wx.EXPAND, 0)
self.pane_1 = wx.Panel(self.window_1, wx.ID_ANY)
sizer_1 = wx.BoxSizer(wx.HORIZONTAL)
self.pane_2 = wx.Panel(self.window_1, wx.ID_ANY)
sizer_2 = wx.BoxSizer(wx.HORIZONTAL)
self.button = wx.Button(self.pane_2, wx.ID_ANY, "test button")
sizer_2.Add(self.button, 0, 0, 0)
self.pane_1.SetSizer(sizer_1)
self.pane_2.SetSizer(sizer_2)
self.window_1.SplitVertically(self.pane_1, self.pane_2)
self.SetSizer(sizer_0)
self.Layout()
self.Bind(wx.EVT_CONTEXT_MENU, self.OnButtonContextMenu, self.button)
self.Show()
def OnButtonContextMenu(self, event):
self.PopupMenu(ButtonContext(self))
class ButtonContext(wx.Menu):
def __init__(self, parent):
super(ButtonContext, self).__init__()
self.parent = parent
button_popup = wx.MenuItem(self, wx.ID_ANY, 'test popup')
self.Append(button_popup)
submenu = wx.Menu()
submenu1 = wx.MenuItem(submenu, wx.ID_ANY, 'Submenu 1')
submenu2 = wx.MenuItem(submenu, wx.ID_ANY, 'Submenu 2')
submenu3 = wx.MenuItem(submenu, wx.ID_ANY, 'Submenu 3')
submenu.Append(submenu1)
submenu.Append(submenu2)
submenu.Append(submenu3)
button_popup2 = wx.MenuItem(self, wx.ID_ANY, 'A sub menu')
button_popup2.SetSubMenu(submenu)
self.Append(button_popup2)
self.Bind(wx.EVT_MENU, self.button_action, button_popup)
self.Bind(wx.EVT_MENU, self.submenu1_action, submenu1)
def button_action(self, event):
print("Test Menu Button")
event.Skip()
def submenu1_action(self, event):
print("Submenu 1")
event.Skip()
class MyApp(wx.App):
def OnInit(self):
self.frame = MyFrame(None, wx.ID_ANY, "")
return True
if __name__ == "__main__":
app = MyApp()
app.MainLoop()
I would like to make a wxpython program that has a notification center just like the one on windows or mac. Whenever I have a message, the message will show inside the the notification panel, and the user could close that message afterwards.
I have a sample code for illustration as follows:
import wx
import wx.lib.scrolledpanel as scrolled
class MyFrame(wx.Frame):
def __init__(self, parent, id, title):
wx.Frame.__init__(self, parent, id, title)
topPanel = wx.Panel(self)
panel1 = wx.Panel(topPanel, -1)
button1 = wx.Button(panel1, -1, label="generate message")
self.panel2 = scrolled.ScrolledPanel(
topPanel, -1, style=wx.SIMPLE_BORDER)
self.panel2.SetAutoLayout(1)
self.panel2.SetupScrolling()
button1.Bind(wx.EVT_BUTTON, self.onAdd)
sizer = wx.BoxSizer(wx.HORIZONTAL)
sizer.Add(panel1,-1,wx.EXPAND|wx.ALL,border=10)
sizer.Add(self.panel2,-1,wx.EXPAND|wx.ALL,border=10)
self.sizer2 = wx.BoxSizer(wx.VERTICAL)
topPanel.SetSizer(sizer)
self.panel2.SetSizer(self.sizer2)
def onAdd(self, event):
new_text = wx.TextCtrl(self.panel2, value="New Message")
self.sizer2.Add(new_text,0,wx.EXPAND|wx.ALL,border=1)
self.panel2.Layout()
self.panel2.SetupScrolling()
class MyApp(wx.App):
def OnInit(self):
frame = MyFrame(None, -1, 'frame')
frame.Show(True)
return True
app = MyApp(0)
app.MainLoop()
In the above I code, the right panel (i.e. panel2) serves as a notification center that all the messages should shown inside it. On the left panel (i.e. panel1) I have a button to generate message just to mimic the notification behavior. Ideally the message on the right panel should be a message box that you could close (maybe a frame? Or a MessageDialog?)
Any hint or advice is much appreciated, and an example would be the best!
Thanks!
Finally figured out myself, it was easier than I initially thought.
Here is the code:
import wx
import wx.lib.scrolledpanel as scrolled
class MyFrame(wx.Frame):
def __init__(self, parent, id, title):
wx.Frame.__init__(self, parent, id, title)
self.number_of_panels = 0
topPanel = wx.Panel(self)
panel1 = wx.Panel(topPanel, -1)
button1 = wx.Button(panel1, -1, label="generate message")
self.panel2 = scrolled.ScrolledPanel(
topPanel, -1, style=wx.SIMPLE_BORDER)
self.panel2.SetAutoLayout(1)
self.panel2.SetupScrolling()
button1.Bind(wx.EVT_BUTTON, self.onAdd)
sizer = wx.BoxSizer(wx.HORIZONTAL)
sizer.Add(panel1,0,wx.EXPAND|wx.ALL,border=5)
sizer.Add(self.panel2,1,wx.EXPAND|wx.ALL,border=5)
self.sizer2 = wx.BoxSizer(wx.VERTICAL)
topPanel.SetSizer(sizer)
self.panel2.SetSizer(self.sizer2)
def onAdd(self, event):
self.number_of_panels += 1
panel_label = "Panel %s" % self.number_of_panels
panel_name = "panel%s" % self.number_of_panels
new_panel = wx.Panel(self.panel2, name=panel_name, style=wx.SIMPLE_BORDER)
self.closeButton = wx.Button(new_panel, label='Close %s' % self.number_of_panels)
self.closeButton.panel_number = self.number_of_panels
self.closeButton.Bind(wx.EVT_BUTTON, self.OnClose)
self.sizer2.Add(new_panel,0,wx.EXPAND|wx.ALL,border=1)
self.panel2.Layout()
self.panel2.SetupScrolling()
def OnClose(self, e):
if self.panel2.GetChildren():
e.GetEventObject().GetParent().Destroy()
self.number_of_panels -= 1
self.panel2.Layout() # Reset layout after destroy the panel
class MyApp(wx.App):
def OnInit(self):
frame = MyFrame(None, -1, 'frame')
frame.Show(True)
return True
app = MyApp(0)
app.MainLoop()
Basically I can destroy the newly created panel. I just need to know which panel it is when I click the close button. This should work very similar to the Notification Center.
I'm learning wxpython.I read the documentation and after playing little bit with it, now I'm creating a small application containing some panels. On One panel I have created a login page. OnSubmit of the login panel, I want to switch to another panel. I'm not sure how to do that. Here is my code (ScreenGrab might help you) :
(Unwanted function and class definitions not shown here):
Toolbook_Demo.py
class ToolbookDemo( wx.Toolbook ) :
def __init__( self, parent ) :
print ""
wx.Toolbook.__init__( self, parent, wx.ID_ANY, style=
wx.BK_DEFAULT
#wx.BK_TOP
#wx.BK_BOTTOM
#wx.BK_LEFT
#wx.BK_RIGHT
)
# Make an image list using the LBXX images
il = wx.ImageList( 32, 32 )
for x in range( 4 ) :
imgObj = getattr( images, 'LB%02d' % ( x+1 ) )
bmp = imgObj.GetBitmap()
il.Add( bmp )
self.AssignImageList( il )
imageIdGenerator = getNextImageID( il.GetImageCount() )
panellogin = userlogin.TabPanel( self )
print panellogin.Hide()
notebookPageList = [ (userlogin.TabPanel( self ), 'Login'),
(panelTwo.TabPanel( self ), 'Panel Two'),
(panelThree.TabPanel( self ), 'Panel Three'),
(panelOne.TabPanel( self ), 'Home')]
imID = 0
for page, label in notebookPageList :
self.AddPage( page, label, imageId=imageIdGenerator.next() )
imID += 1
# An undocumented method in the official docs :
self.ChangeSelection( 0 ) # Select and view this notebook page.
# Creates no events - method SetSelection does.
self.Bind( wx.EVT_TOOLBOOK_PAGE_CHANGING, self.OnPageChanging )
self.Bind( wx.EVT_TOOLBOOK_PAGE_CHANGED, self.OnPageChanged )
userlogin.py
import wx
class TabPanel( wx.Panel ) :
""" This will be [inserted into] the first notebook tab. """
def __init__( self, parent ) :
wx.Panel.__init__( self, parent=parent, id=wx.ID_ANY )
sizer = wx.FlexGridSizer(rows=3, cols=2, hgap=5, vgap=15)
# Username
self.txt_Username = wx.TextCtrl(self, 1, size=(150, -1))
lbl_Username = wx.StaticText(self, -1, "Username:")
sizer.Add(lbl_Username,0, wx.LEFT|wx.TOP| wx.RIGHT, 50)
sizer.Add(self.txt_Username,0, wx.TOP| wx.RIGHT, 50)
# Password
self.txt_Password = wx.TextCtrl(self, 1, size=(150, -1), style=wx.TE_PASSWORD)
lbl_Password = wx.StaticText(self, -1, "Password:")
sizer.Add(lbl_Password,0, wx.LEFT|wx.RIGHT, 50)
sizer.Add(self.txt_Password,0, wx.RIGHT, 50)
# Submit button
btn_Process = wx.Button(self, -1, "&Login")
self.Bind(wx.EVT_BUTTON, self.OnSubmit, btn_Process)
sizer.Add(btn_Process,0, wx.LEFT, 50)
self.SetSizer(sizer)
def OnSubmit(self, event):
UserText = self.txt_Username.GetValue()
PasswordText = self.txt_Password.GetValue()
if authentcated(UserText, PasswordText):
#Switch to another panel
#Hide login panel until current session expires
#Show another panels only
I don't think you want to login using a panel in a Notebook-type widget that will just take you to the next tab. The user can click on the next tab without logging in. I am guessing you would like the first tab's panel to change to something else. The easiest way to do that is to call it's Hide method and Show a different panel. Fortunately, that's really easy to do. Here's an example that uses a menu to switch between the panels:
import wx
import wx.grid as gridlib
########################################################################
class PanelOne(wx.Panel):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
wx.Panel.__init__(self, parent=parent)
txt = wx.TextCtrl(self)
########################################################################
class PanelTwo(wx.Panel):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
wx.Panel.__init__(self, parent=parent)
grid = gridlib.Grid(self)
grid.CreateGrid(25,12)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(grid, 0, wx.EXPAND)
self.SetSizer(sizer)
########################################################################
class MyForm(wx.Frame):
#----------------------------------------------------------------------
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY,
"Panel Switcher Tutorial")
self.panel_one = PanelOne(self)
self.panel_two = PanelTwo(self)
self.panel_two.Hide()
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.sizer.Add(self.panel_one, 1, wx.EXPAND)
self.sizer.Add(self.panel_two, 1, wx.EXPAND)
self.SetSizer(self.sizer)
menubar = wx.MenuBar()
fileMenu = wx.Menu()
switch_panels_menu_item = fileMenu.Append(wx.ID_ANY,
"Switch Panels",
"Some text")
self.Bind(wx.EVT_MENU, self.onSwitchPanels,
switch_panels_menu_item)
menubar.Append(fileMenu, '&File')
self.SetMenuBar(menubar)
#----------------------------------------------------------------------
def onSwitchPanels(self, event):
""""""
if self.panel_one.IsShown():
self.SetTitle("Panel Two Showing")
self.panel_one.Hide()
self.panel_two.Show()
else:
self.SetTitle("Panel One Showing")
self.panel_one.Show()
self.panel_two.Hide()
self.Layout()
# Run the program
if __name__ == "__main__":
app = wx.App(False)
frame = MyForm()
frame.Show()
app.MainLoop()
You can use the same logic to swap out a panel in the Toolbook.
I'm trying to understand wxPython, but most of the documentation out there just presents programs in a monkey-see-monkey-do way without explaining the fundamentals of the library.
Consider this snippet of code:
import wx
class MyFrame(wx.Frame):
def __init__(self, parent, id, title):
wx.Frame.__init__(self, parent, id, title, (-1, -1), wx.Size(250, 50))
panel = wx.Panel(self, -1)
box = wx.BoxSizer(wx.HORIZONTAL)
box.Add(wx.Button(panel, -1, 'Button1'), 1 )
box.Add(wx.Button(panel, -1, 'Button2'), 1 )
box.Add(wx.Button(panel, -1, 'Button3'), 1 )
panel.SetSizer(box)
self.Centre()
class MyApp(wx.App):
def OnInit(self):
frame = MyFrame(None, -1, 'wxboxsizer.py')
frame.Show(True)
return True
app = MyApp(0)
app.MainLoop()
I see three containers here -
A frame, a panel, and a box.
And then there are three buttons.
Can someone explain which container goes inside which one?
Does the panel go into the frame? If so, where is it added to the frame?
What about the box? Does it go into the panel, or does the panel go into it?
Were do the buttons go? Is it into the box?
Why is panel.SetSizer() used in one place and box.Add() used in another place?
Let us slowly develop a wxPython application to see how it works.
This is the least amount of code required to make a wxPython application. It contains a wx.Frame (you may understand this as a window). There is nothing in the window. app.MainLoop() is the loop that captures any events such as mouse-clicks, closing or minimizing windows.
import wx
app = wx.App()
frame = wx.Frame(None, -1, 'A Frame')
frame.Show()
app.MainLoop()
A window on its own is not very interesting but is still fairly powerful. We can add things like menu items and titles and even select styles such as No Maximize button. Let's do all that.
import wx
class Frame(wx.Frame):
def __init__(self, *args, **kwargs):
super(Frame, self).__init__(*args, **kwargs)
menubar = wx.MenuBar() # Create a menubar
fileMenu = wx.Menu() # Create the file menu
fitem = fileMenu.Append(wx.ID_EXIT, 'Quit', 'Quits application') # Add a quit line
menubar.Append(fileMenu, '&File') # Add the File menu to the Menubar
self.SetMenuBar(menubar) # Set the menubar as THE menu bar
self.Bind(wx.EVT_MENU, self.OnQuit, fitem) # Bind the quit line
self.Show() # Show the frame
def OnQuit(self, e):
self.Close()
app = wx.App()
Frame(None, -1, 'A Frame', style=wx.RESIZE_BORDER
| wx.SYSTEM_MENU | wx.CAPTION | wx.CLOSE_BOX) # Some styles
app.MainLoop()
You'll notice things got a little more complicated fairly quickly. It's actually to help us stay organized. We moved our frame into its own class and we defined some characteristics. We asked for a menu, bound the menu item to the OnQuit() method which close the application. This is all at the most basic layer.
Let's add a panel. A panel is like a chalkboard. It sits on top of a wx.Frame (like a Chalkboard sits against a wall). Once we have a panel, we can start adding sizers and widgets.
import wx
class Frame(wx.Frame):
def __init__(self, *args, **kwargs):
super(Frame, self).__init__(*args, **kwargs)
menubar = wx.MenuBar()
fileMenu = wx.Menu()
fitem = fileMenu.Append(wx.ID_EXIT, 'Quit', 'Quits application')
menubar.Append(fileMenu, '&File')
self.SetMenuBar(menubar)
self.Bind(wx.EVT_MENU, self.OnQuit, fitem)
panel = wx.Panel(self, -1) # Added a panel!
self.Show()
def OnQuit(self, e):
self.Close()
app = wx.App()
Frame(None, -1, 'A Frame', style=wx.RESIZE_BORDER
| wx.SYSTEM_MENU | wx.CAPTION | wx.CLOSE_BOX)
app.MainLoop()
You'll notice a slight difference depending on your platform. The window is now filled in, with a lighter colour. That's our panel. Now let's add some buttons.
import wx
class Frame(wx.Frame):
def __init__(self, *args, **kwargs):
super(Frame, self).__init__(*args, **kwargs)
menubar = wx.MenuBar()
fileMenu = wx.Menu()
fitem = fileMenu.Append(wx.ID_EXIT, 'Quit', 'Quits application')
menubar.Append(fileMenu, '&File')
self.SetMenuBar(menubar)
self.Bind(wx.EVT_MENU, self.OnQuit, fitem)
panel = wx.Panel(self, -1)
btn = wx.Button(panel, label='I am a closing button.') # Add a button
btn.Bind(wx.EVT_BUTTON, self.OnQuit) # Bind the first button to quit
btn2 = wx.Button(panel, label='I am a do nothing button.') # Add a second
self.Show()
def OnQuit(self, e):
self.Close()
app = wx.App()
Frame(None, -1, 'A Frame', style=wx.RESIZE_BORDER
| wx.SYSTEM_MENU | wx.CAPTION | wx.CLOSE_BOX)
app.MainLoop()
Now we have buttons that sit on the panel. But they look awful. They're stuck one on top of another. We could manually position them using the pos=(x,y) attribute but that's very tedious. Let's call in our friend the boxsizer.
import wx
class Frame(wx.Frame):
def __init__(self, *args, **kwargs):
super(Frame, self).__init__(*args, **kwargs)
menubar = wx.MenuBar()
fileMenu = wx.Menu()
fitem = fileMenu.Append(wx.ID_EXIT, 'Quit', 'Quits application')
menubar.Append(fileMenu, '&File')
self.SetMenuBar(menubar)
self.Bind(wx.EVT_MENU, self.OnQuit, fitem)
panel = wx.Panel(self, -1)
btn = wx.Button(panel, label='I am a closing button.')
btn.Bind(wx.EVT_BUTTON, self.OnQuit)
btn2 = wx.Button(panel, label='I am a do nothing button.')
vbox = wx.BoxSizer(wx.VERTICAL) # Create a vertical boxsizer
vbox.Add(btn) # Add button 1 to the sizer
vbox.Add(btn2) # Add button 2 to the sizer
panel.SetSizer(vbox) # Tell the panel to use this sizer as its sizer.
self.Show()
def OnQuit(self, e):
self.Close()
app = wx.App()
Frame(None, -1, 'A Frame', style=wx.RESIZE_BORDER
| wx.SYSTEM_MENU | wx.CAPTION | wx.CLOSE_BOX)
app.MainLoop()
That's already much better! Let's see what a horizontal sizer looks like.
import wx
class Frame(wx.Frame):
def __init__(self, *args, **kwargs):
super(Frame, self).__init__(*args, **kwargs)
menubar = wx.MenuBar()
fileMenu = wx.Menu()
fitem = fileMenu.Append(wx.ID_EXIT, 'Quit', 'Quits application')
menubar.Append(fileMenu, '&File')
self.SetMenuBar(menubar)
self.Bind(wx.EVT_MENU, self.OnQuit, fitem)
panel = wx.Panel(self, -1)
btn = wx.Button(panel, label='I am a closing button.')
btn.Bind(wx.EVT_BUTTON, self.OnQuit)
btn2 = wx.Button(panel, label='I am a do nothing button.')
vbox = wx.BoxSizer(wx.VERTICAL) # A vertical sizer
hbox = wx.BoxSizer(wx.HORIZONTAL) # And a horizontal one?? but why?
hbox.Add(btn) # Let's add the buttons first
hbox.Add(btn2)
panel.SetSizer(hbox) # see why we need to tell the panel which sizer to use? We might have two!
self.Show()
def OnQuit(self, e):
self.Close()
app = wx.App()
Frame(None, -1, 'A Frame', style=wx.RESIZE_BORDER
| wx.SYSTEM_MENU | wx.CAPTION | wx.CLOSE_BOX)
app.MainLoop()
Interesting! Notice how I kept our vbox? Let's try combining the two. We'll need lots more buttons and maybe some TextCtrls too.
import wx
class Frame(wx.Frame):
def __init__(self, *args, **kwargs):
super(Frame, self).__init__(*args, **kwargs)
menubar = wx.MenuBar()
fileMenu = wx.Menu()
fitem = fileMenu.Append(wx.ID_EXIT, 'Quit', 'Quits application')
menubar.Append(fileMenu, '&File')
self.SetMenuBar(menubar)
self.Bind(wx.EVT_MENU, self.OnQuit, fitem)
panel = wx.Panel(self, -1)
btn = wx.Button(panel, label='I am a closing button.')
btn.Bind(wx.EVT_BUTTON, self.OnQuit)
btn2 = wx.Button(panel, label='I am a do nothing button.')
txt1 = wx.TextCtrl(panel, size=(140,-1))
txt2 = wx.TextCtrl(panel, size=(140,-1))
txt3 = wx.TextCtrl(panel, size=(140,-1))
btn3 = wx.Button(panel, label='I am a happy button.')
btn4 = wx.Button(panel, label='I am a bacon button.')
btn5 = wx.Button(panel, label='I am a python button.')
# So many sizers!
vbox = wx.BoxSizer(wx.VERTICAL)
hbox1 = wx.BoxSizer(wx.HORIZONTAL)
hbox2 = wx.BoxSizer(wx.HORIZONTAL)
hbox3 = wx.BoxSizer(wx.HORIZONTAL)
hbox4 = wx.BoxSizer(wx.HORIZONTAL)
hbox1.Add(btn)
hbox1.Add(btn2)
hbox1.Add(txt1)
hbox2.Add(txt2)
hbox2.Add(txt3)
hbox2.Add(btn3)
hbox3.Add(btn4)
hbox4.Add(btn5)
vbox.Add(hbox1)
vbox.Add(hbox2)
vbox.Add(hbox3)
vbox.Add(hbox4)
panel.SetSizer(vbox)
self.Show()
def OnQuit(self, e):
self.Close()
app = wx.App()
Frame(None, -1, 'A Frame', style=wx.RESIZE_BORDER
| wx.SYSTEM_MENU | wx.CAPTION | wx.CLOSE_BOX)
app.MainLoop()
We've added buttons and text widgets to horizontal sizers first, and then to vertical sizers second. We've got sizers in sizers, which is a very common way of doing things in wxPython.
__________________________
|__hbox1_|_______________| \
|_hbox2____|______|____|_| \___VBOX
|___hbox3______|_________| /
|_______|__hbox4_|_______| /
Sort of like that. In each hbox we have a number of widgets. Up to you how many you want. Hope this helps.
wxPython is complex, but so are other GUI toolkits. Let's break it down a bit. To answer the first question, the usual layout of the wxPython GUI is one of the following:
frame -> panel -> sizer -> widgets
frame -> sizer -> panel -> sizer -> widgets
I usually go with the first one. If the layout is complex, I may nest sizers, so I end up with something like this:
frame -> panel -> sizer -> sizer1 -> widgets
-> sizer2 -> widgets
2) The first panel should always be added to the frame as its sole widget:
wx.Frame.__init__(self, None, title="Test")
panel = wx.Panel(self)
3) The boxSizer usually goes into the panel. I usually have a top level boxSizer that I give to the panel and then create nested sizers inside of it.
4) The buttons and other widgets go in BOTH the panel and the sizer! You set the button's parent to the panel, then to layout the widgets inside the panel, you put them in the panel's sizer object. If you were to set the button's parent as the frame, you would have a mess.
5) SetSizer is used to tell wxPython which widget the sizer belongs to. In your code, you give the panel the box sizer instance. The Add() method of the sizer object is used to add widgets (and sizers) to the sizer itself.
I hope that answers all your questions. You might also find the following article useful as it links to the majority of the documentation I use for wx: http://www.blog.pythonlibrary.org/2010/12/05/wxpython-documentation/
I would strongly recommend getting hold of a copy of wxPython in Action by Noel Rappin and Robin Dunn. Since Robin is one of the main authors/architects of wxPython you will get very good insights and clear explanations.
N.B. Before anybody asks I have absolutely no commercial connection with the book, authors or publishers.
I would like to be able to display Notebook and a TxtCtrl wx widgets in a single frame. Below is an example adapted from the wxpython wiki; is it possible to change their layout (maybe with something like wx.SplitterWindow) to display the text box below the Notebook in the same frame?
import wx
import wx.lib.sheet as sheet
class MySheet(sheet.CSheet):
def __init__(self, parent):
sheet.CSheet.__init__(self, parent)
self.SetLabelBackgroundColour('#CCFF66')
self.SetNumberRows(50)
self.SetNumberCols(50)
class Notebook(wx.Frame):
def __init__(self, parent, id, title):
wx.Frame.__init__(self, parent, id, title, size=(600, 600))
menubar = wx.MenuBar()
file = wx.Menu()
file.Append(101, 'Quit', '' )
menubar.Append(file, "&File")
self.SetMenuBar(menubar)
wx.EVT_MENU(self, 101, self.OnQuit)
nb = wx.Notebook(self, -1, style=wx.NB_BOTTOM)
self.sheet1 = MySheet(nb)
self.sheet2 = MySheet(nb)
self.sheet3 = MySheet(nb)
nb.AddPage(self.sheet1, "Sheet1")
nb.AddPage(self.sheet2, "Sheet2")
nb.AddPage(self.sheet3, "Sheet3")
self.sheet1.SetFocus()
self.StatusBar()
def StatusBar(self):
self.statusbar = self.CreateStatusBar()
def OnQuit(self, event):
self.Close()
class MyFrame(wx.Frame):
def __init__(self, parent, id, title):
wx.Frame.__init__(self, parent, id, title, wx.DefaultPosition, wx.Size(450, 400))
self.text = wx.TextCtrl(self, -1, style = wx.TE_MULTILINE)
self.Center()
class MyApp(wx.App):
def OnInit(self):
frame = Notebook(None, -1, 'notebook.py')
frame.Show(True)
frame.Center()
frame2 = MyFrame(None, -1, '')
frame2.Show(True)
self.SetTopWindow(frame2)
return True
app = MyApp(0)
app.MainLoop()
Making two widgets appear on the same frame is easy, actually. You should use sizers to accomplish this.
In your example, you can change your Notebook class implementation to something like this:
class Notebook(wx.Frame):
def __init__(self, parent, id, title):
wx.Frame.__init__(self, parent, id, title, size=(600, 600))
menubar = wx.MenuBar()
file = wx.Menu()
file.Append(101, 'Quit', '' )
menubar.Append(file, "&File")
self.SetMenuBar(menubar)
wx.EVT_MENU(self, 101, self.OnQuit)
nb = wx.Notebook(self, -1, style=wx.NB_BOTTOM)
self.sheet1 = MySheet(nb)
self.sheet2 = MySheet(nb)
self.sheet3 = MySheet(nb)
nb.AddPage(self.sheet1, "Sheet1")
nb.AddPage(self.sheet2, "Sheet2")
nb.AddPage(self.sheet3, "Sheet3")
self.sheet1.SetFocus()
self.StatusBar()
# new code begins here:
# add your text ctrl:
self.text = wx.TextCtrl(self, -1, style = wx.TE_MULTILINE)
# create a new sizer for both controls:
sizer = wx.BoxSizer(wx.VERTICAL)
# add notebook first, with size factor 2:
sizer.Add(nb, 2)
# then text, size factor 1, maximized
sizer.Add(self.text, 1, wx.EXPAND)
# assign the sizer to Frame:
self.SetSizerAndFit(sizer)
Only the __init__ method is changed. Note that you can manipulate the proportions between the notebook and text control by changing the second argument of the Add method.
You can learn more about sizers from the official Sizer overview article.
You can use a splitter, yes.
Also, it makes sense to create a Panel, place your widgets in it (with sizers), and add this panel to the Frame.