The fundementals of wxPython - python

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.

Related

wxpython toolbar textctrl spacing

I have textctrl in wxPython toolbar, I couldn't figure out how to align it with icons(22x22) and have some spacing/padding between toolbar icons and text control and also to frame borders.
import wx
class MyGUI(wx.Frame):
def __init__(self, parent, id, title):
wx.Frame.__init__(self, parent, id, title, wx.DefaultPosition, wx.Size(500, 500))
menubar = wx.MenuBar()
file = wx.Menu()
help = wx.Menu()
file.Append(101, '&Open', 'Open a new document')
file.Append(102, '&Save', 'Save the document')
file.AppendSeparator()
quit = wx.MenuItem(file, 105, '&Quit\tCtrl+Q', 'Quit the Application')
quit.SetBitmap(wx.Image('media/22/actions/system-log-out.png', wx.BITMAP_TYPE_PNG).ConvertToBitmap())
file.AppendItem(quit)
menubar.Append(file, '&File')
menubar.Append(help, '&Help')
self.SetMenuBar(menubar)
self.CreateStatusBar()
self.toolbar = self.CreateToolBar()
textctrl = self.toolbar.AddControl( wx.TextCtrl( self.toolbar, wx.ID_ANY,size=(100, -1)))
tconn = self.toolbar.AddLabelTool(wx.ID_UNDO, '', wx.Bitmap('media/22/actions/server-connect-icon.png'))
tplay = self.toolbar.AddLabelTool(wx.ID_REDO, '', wx.Bitmap('media/22/actions/media-playback-start.png'))
self.toolbar.AddSeparator()
texit = self.toolbar.AddLabelTool(wx.ID_EXIT, '', wx.Bitmap('media/22/actions/stop.png'))
self.toolbar.EnableTool(wx.ID_EXIT, False)
self.toolbar.Realize()
class MyApp(wx.App):
def OnInit(self):
frame = MyGUI(None, -1, 'MyGUI')
frame.SetBackgroundColour('#004681')
frame.SetIcon(wx.Icon('media/launch.ico', wx.BITMAP_TYPE_ICO))
frame.Show(True)
return True
if __name__ == '__main__':
app = MyApp(0)
app.MainLoop()
The toolbar widget follows the look-and-feel of the OS's native toolbar, so there's really no way to change that. See the following thread:
http://wxpython-users.1045709.n5.nabble.com/wxToolBar-Icon-Spacing-td3406146.html
In it there are a couple of workarounds using blank images or labels for spacing. Alternatively, you could just create your own toolbar with a horizontal box sizer and add all the widgets to it. Then you'll be able to use the sizer to space things out a bit.

wxPython and add Options Menu when select in Menu Bar

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()

wxPython: TextCtrl doesn't ever receive focus when inside a panel inside another panel inside a frame

So I have the following code set up to demonstrate the problem:
import wx
class testPanel(wx.Panel):
def __init__(self, parent):
super(testPanel, self).__init__(parent)
self.hsizer = wx.BoxSizer(wx.HORIZONTAL)
self.txt = wx.TextCtrl(self, style=wx.TE_MULTILINE)
self.hsizer.Add(self.txt, proportion=1,
flag=wx.EXPAND)
self.SetSizer(self.hsizer)
self.hsizer.Fit(self)
self.Show(True)
class testFrame(wx.Frame):
def __init__(self, parent):
super(testFrame, self).__init__(parent)
self.mainPanel = wx.Panel(self)
self.vsizer = wx.BoxSizer(wx.VERTICAL)
self.txt1 = testPanel(self)
self.txt2 = testPanel(self)
self.vsizer.Add(self.txt1, proportion=1,
flag=wx.EXPAND)
self.vsizer.Add(self.txt2, proportion=1,
flag=wx.EXPAND)
self.mainPanel.SetSizer(self.vsizer)
self.vsizer.Fit(self.mainPanel)
self.mainSizer = wx.BoxSizer(wx.HORIZONTAL)
self.mainSizer.Add(self.mainPanel, proportion=1,
flag=wx.EXPAND)
self.SetSizer(self.mainSizer)
self.mainSizer.Fit(self)
self.Show(True)
app = wx.PySimpleApp()
frame = testFrame(None)
frame.Show(True)
app.MainLoop()
When this is run, everything displays as expected, but the two wx.TextCtrls won't receive focus ever. This isn't the case when the extra layer of panel is removed, but I can't avoid having that extra panel.
Use:
self.txt1 = testPanel(self.mainPanel)
self.txt2 = testPanel(self.mainPanel)
and they will get focus.
In wxPython things go better when you design a straight line of inheritance, with branches but without crossovers. As your testPanels are in a sizer that belongs to mainPanel, the natural parent is not the Frame (self) but mainPanel,

wxPython Panel in existing window: slow and small

I'm experiencing very different behavior when creating a wx.Panel depending on whether the main window's already called Show().
With the below code, the application opens quickly with MainPanel created & populated before MainFrame.Show(). Using "New" to re-create it has multi-second lag while it re-makes the 200 texts. Also the MainPanel doesn't expand to take up the whole size of the window until the window is resized.
This is definitely an issue with panel creation rather than panel destruction; if I remove the MainPanel creation in MainFrame's init, then the New action has the same speed & size issues.
Two questions:
Is there anything I can do to speed up panel creation after MainFrame.Show()?
What needs to be done when creating MainPanel after MainFrame.Show()ed to have the MainPanel expand to the size of its parent?
#!/usr/bin/python
# -*- coding: utf-8 -*-
import wx
import wx.lib.scrolledpanel
class MainPanel(wx.lib.scrolledpanel.ScrolledPanel):
def __init__(self,parent):
wx.lib.scrolledpanel.ScrolledPanel.__init__(self, parent=parent)
self.SetupScrolling()
sizer = wx.BoxSizer(wx.VERTICAL)
for i in range(1,200):
sizer.Add(wx.StaticText(self, wx.ID_ANY, "I'm static text"))
self.SetSizer(sizer)
self.SetAutoLayout(True)
class MainFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title="FrameTest", size=(600,800))
self.InitMenu()
self.panel = None
self.panel = MainPanel(self)
def InitMenu(self):
self.menuBar = wx.MenuBar()
menuFile = wx.Menu()
menuFile.Append(wx.ID_NEW, "&New")
self.Bind(wx.EVT_MENU, self.OnNew, id=wx.ID_NEW)
self.menuBar.Append(menuFile, "&File")
self.SetMenuBar(self.menuBar)
def OnNew(self, evt):
if self.panel:
self.panel.Destroy()
self.panel = MainPanel(self)
if __name__ == "__main__":
app = wx.App(0)
frame = MainFrame()
frame.Show()
app.MainLoop()
UPDATE: joaquin's SendSizeEvent() definitely solves the first problem. For the second, I've found that hiding containers works out well. I'm guessing that after the window's been shown, it's trying to unnecessarily re-(display/layout/something) after every new widget and that's slowing it down. If I add Hide & Show to the panel's init, then there's no more lag and it works in both situations.
class MainPanel(wx.lib.scrolledpanel.ScrolledPanel):
def __init__(self,parent):
wx.lib.scrolledpanel.ScrolledPanel.__init__(self, parent=parent)
self.SetupScrolling()
self.Hide()
sizer = wx.BoxSizer(wx.VERTICAL)
for i in range(1,200):
sizer.Add(wx.StaticText(self, wx.ID_ANY, "I'm static text"))
self.SetSizer(sizer)
self.SetAutoLayout(True)
self.Show()
The panel will get the correct size if the Frame feels a SizeEvent. So this works for your second question:
def OnNew(self, evt):
if self.panel:
self.panel.Destroy()
self.panel = MainPanel(self)
self.SendSizeEvent()
Control of windows and widgets becomes easier by using sizers. With sizers in your main frame you can use the sizer Layout method to fit widgets in place.
Could not find a way of speeding up panel rewrite. But you can diminish the bizarre visual effect by not deleting the panel but clearing the sizer instead (you dont need SendSizeEvent() for this case):
class MainPanel(wx.lib.scrolledpanel.ScrolledPanel):
def __init__(self,parent):
wx.lib.scrolledpanel.ScrolledPanel.__init__(self, parent=parent)
self.SetupScrolling()
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.fill()
self.SetSizer(self.sizer)
def fill(self):
tup = [wx.StaticText(self, wx.ID_ANY, "I'm static text") for i in range(200)]
self.sizer.AddMany(tup)
self.Layout()
class MainFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title="FrameTest", size=(600,800))
self.InitMenu()
self.panel = None
self.panel = MainPanel(self)
def InitMenu(self):
self.menuBar = wx.MenuBar()
menuFile = wx.Menu()
menuFile.Append(wx.ID_NEW, "&New")
self.Bind(wx.EVT_MENU, self.OnNew, id=wx.ID_NEW)
self.menuBar.Append(menuFile, "&File")
self.SetMenuBar(self.menuBar)
def OnNew(self, evt):
if self.panel:
self.panel.sizer.Clear()
self.panel.fill()

wxPython: displaying multiple widgets in same frame

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.

Categories

Resources