Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

The fundementals of wxPython

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.

  1. Can someone explain which container goes inside which one?
  2. Does the panel go into the frame? If so, where is it added to the frame?
  3. What about the box? Does it go into the panel, or does the panel go into it?
  4. Were do the buttons go? Is it into the box?
  5. Why is panel.SetSizer() used in one place and box.Add() used in another place?
like image 814
hrs Avatar asked Sep 19 '25 06:09

hrs


1 Answers

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.

like image 99
pedram Avatar answered Sep 21 '25 21:09

pedram