/!\ do not be surprised <:( , if the below python code does not work properly on your machine these days! <!>


Prerequisites


You must know some stuff before you start with this crash course:

  1. Python programming language. Basic concepts, modules, OOP programming.
  2. Event driven programming.

If 1 is not met you should try the following:

If 2 is not met try:

Altho not prerequisites there are some things that could help you a lot in your GUI adventures. More at BestPractices. It is probably best that you read that after you finish the course.

CrashCourse

The simplest Application

Hello World App:

   1 import wx #import of wx namespace
   2 app = wx.App(0) # creation of the wx.App object (initialisation stuff)
   3 frame = wx.Frame(None, title="Hello World") # creation of a Frame with "Hello World" as title
   4 frame.Show() # frames are invisible by default so we use Show() to make them visible
   5 app.MainLoop() # here the app enters a loop waiting for user input

Although there are only 5 lines this app does everything a GUI is supposed to to: presents information (the "Hello World" title) and reacts to user input (you can drag the frame around the screen, you can resize it and you can minimize, maximize or close it).

This is possible because a lot of stuff is taken care off automatically by wxpython. You don't have to worry about drawing the frame or reacting to mouse input on the minimize/maximize/close buttons, wxpython takes care of that.

Custom Widgets, Custom Event Handling

Hello My World App:

   1 import wx
   2 
   3 class MyFrame(wx.Frame):
   4     def __init__(self):
   5         wx.Frame.__init__(self, None, title="Hello My World")
   6         self.Bind(wx.EVT_CLOSE, self.OnClose)
   7         self.Show()
   8 
   9     def OnClose(self, evt):
  10         dlg = wx.MessageDialog(self, 'Are you sure you want to close My World?',
  11                         'Closing...', wx.YES_NO | wx.ICON_QUESTION)
  12         ret = dlg.ShowModal()
  13         dlg.Destroy()
  14 
  15         if ret == wx.ID_YES:
  16             evt.Skip()
  17 
  18 app = wx.App(0)
  19 MyFrame()
  20 app.MainLoop()

Here we see a custom made Frame. MyFrame does not take any parameters and it knows that it has no parent (None), it knows that its title is "Hello My World" and it also knows that after creation it should show itself.

The line with self.Bind(...) informs wxpython that we want something special to happen when the user tries to close the frame by binding the EVT_CLOSE event to our custom handler. So... when the user clicks on the close button wxpython will first call our custom handler. In this handler we create a dialog asking the user for confirmation and if the user presses the Yes button we pass the event to the next handler by skipping it (evt.Skip()). The next handler is the default handler, the one installed automatically by the wxpython toolkit and this handler destroys the Frame and exists the Main Loop effectively ending the Application.

If we don't skip the event the default handler will not be called so the frame will not close.

In the event handler we used a wx.MessageDialog... the best way to learn more about it is to open the wx documentation provided by wxpython and look for it, there you will find how to customize it. Maybe you want an Ok/Cancel combo or maybe you want an exclamation mark instead of the question mark... you'll find all about this on the wxMessageDialog page in the wx docs. This documentation is for C++ so a beginner pythoneer might have some problems. WxDocsForPythonProgrammers might provide a couple of clues.

Anatomy of a wxpython application

Most of wxpython apps will go through the following scenario:

  1. an app object gets created, its creation will take care of the initialization of the toolkit.
  2. a custom frame/dialog/window gets created and shown.
  3. the app's MainLoop() gets called and this will put the app in a loop where events generated by widgets will be dispatched to their handlers.

In most of the apps, steps 1 and 3 will be just 2 lines: step 1 will be something like app = wx.App(0) and step 3 will be something like app.MainLoop().

Step 2 will involve:

Step 2.2 is rather straightforward: identify the event you want to react to using the documentation and then use widget.Bind(...) to create a link between the event generated by that widget and your custom handler.

Step 2.1 will be most of the GUI related work you'll do on your app. Most of the problems you'll encounter will be here. It will be either that your widgets don't look/behave as you want them to or maybe they aren't laid out properly, this is why it is a good idea to have the docs and the demo open at all time and to learn at least one way of laying out widgets.

The recommended way of laying out widgets is by using sizers. The sizer is a class that will compute the location and size a widget should have. Of course it requires some hints like the order in which the widgets are laid out, if a widget should enlarge as more space is available or if it should stay the same size. Sizers will automatically handle situations where the layout of the frame/dialog/window needs to change. For example, when you resize the frame and more space becomes available, the sizer will automatically recompute widgets size and location and distribute the available space to the managed widgets according to the hints that were given.

wx.BoxSizer is one of the most used sizers. It is very easy to use and with several BoxSizers you can create quite complex layouts. It simply lays out the widgets one after the other from top to bottom if the orientation is wx.VERTICAL or from left to right if the orientation is wx.HORIZONTAL. Adding widgets to the sizer means that the sizer will manage their size and position. When adding the widgets you can also give the sizer certain hints about how the widget should react to changes in layout. The first hint is the proportion, it could be 0 or 1 and above and it tells the sizer if it should expand the widget in the direction of the layout as more space is available. So if you use a BoxSizer with wx.VERTICAL as orientation a proportion of 1 will cause the widget to use all the available vertical space. If more than one widget has 1 as proportion they will share (divide equally) the available space. Using different numbers for proportions will cause the space to be divided proportionally between the widgets. The next attribute is the flag. Here you could tell the sizer if the widget should expand in the other direction, if the widgets should be aligned in the available space or if there should be a border around the widget. More about it in the wxDocs, look for wxSizer, the parent of BoxSizer. Also look at the sizer examples from the Demo.

Hello Sizers App:

   1 import wx
   2 import random
   3 
   4 def GetRandColor():
   5     red = random.randint(0,255)
   6     green = random.randint(0,255)
   7     blue = random.randint(0,255)
   8     return (red, green, blue)
   9 
  10 class ColorPanel(wx.Panel):
  11     def __init__(self, parent, color='rand'):
  12         wx.Panel.__init__(self, parent)
  13         if color == 'rand':
  14             color = GetRandColor()
  15             self.Bind(wx.EVT_MOTION, self.OnMouseMove)
  16         self.SetBackgroundColour(color)
  17 
  18     def OnMouseMove(self, evt):
  19         self.SetBackgroundColour(GetRandColor())
  20         self.Refresh()
  21         evt.Skip()
  22 
  23 class MyFrame(wx.Frame):
  24     def __init__(self):
  25         wx.Frame.__init__(self, None, title="Hello Sizers")
  26         vSizer =wx.BoxSizer(wx.VERTICAL)
  27         hSizer = wx.BoxSizer(wx.HORIZONTAL)
  28         red = ColorPanel(self, 'red')
  29         blue = ColorPanel(self, 'blue')
  30         green = ColorPanel(self, 'green')
  31         rand = ColorPanel(self)
  32 
  33         red.SetSize((100, 100))
  34         rand.SetSize((100, 100))
  35         green.SetSize((-1, 100))
  36         blue.SetSize((-1, 100))
  37 
  38         vSizer.Add(rand, 0, wx.EXPAND)
  39         vSizer.Add(green, 1, wx.EXPAND)
  40         vSizer.Add(blue, 2, wx.EXPAND)
  41 
  42         hSizer.Add(red, 0, wx.EXPAND)
  43         hSizer.Add(vSizer, 1, wx.EXPAND)
  44 
  45         self.SetSizer(hSizer)
  46         self.Fit()
  47 
  48         self.Show()
  49         self.Bind(wx.EVT_CLOSE, self.OnClose)
  50 
  51     def OnClose(self, evt):
  52         dlg = wx.MessageDialog(self, 'Are you sure you want to close My World?',
  53                         'Closing...', wx.YES_NO | wx.ICON_QUESTION)
  54         ret = dlg.ShowModal()
  55         dlg.Destroy()
  56 
  57         if ret == wx.ID_YES:
  58             evt.Skip()
  59 
  60 app = wx.App(0)
  61 MyFrame()
  62 app.MainLoop()

Now, for some clarifications on the above app:

ColorPanel is a custom wx.Panel that can receive a color at initialization, color that will become its background color. If it doesn't receive a color it defaults to 'rand' and beside setting the background color to some random color it also installs a handler for mouse movement so that when the mouse moves OVER the panel the panel changes its background color. As you can see SetBackgroundColour is not fussy, it accepts a named color, a wx.Color object, a predefined wx.Color object like wx.RED or a tuple of 3 integers representing the intensity of red, green and blue.

The layout is taken care of in the Frame constructor. There we create 4 ColorPanels and with the help of 2 BoxSizers we place them in a configuration with the red panel on the left, expanding as the frame grows vertically. The next 3 panels are placed on the right one on top of the other with green and blue expanding vertically in a proportion of 1:2.

The sizer talks to the children (the widgets it manages) and to the parent. From the parent it gets information about available space and from the children it gets information about what sizes they prefer. When you have problems with the layout the first step in debugging is to look for what information the sizer receives. The information about the size is ALWAYS overwritten by the hints you give to the sizer. In the above example we set the size of the red panel to 100x100 pixels but when we add it to the sizer we say that proportion should be 0 and that it should EXPAND. So the sizer (being a HORIZONTAL BoxSizer) honors the information about the width that it receives from the panel due to proportion 0 (as the widget wants) BUT it overrides the information about height due to wx.EXPAND hint.

The green panel has both the proportion set to 1 and the wx.EXPAND flag so it looks like none of its size hints are gonna be honored by the sizer... well not true, having proportion of 1 makes it a reference so when we later ask the frame to Fit the following happens:

Let's see now what happens when we resize the frame:

Final notes

Well that's about it for this Crash Course. You should be able to create a frame and place widgets inside it with BoxSizers, also you should be able to bind the events so that your app could react to user input.

Next steps are the Demo and the wxDocs. I recommend reading for starters the documentation of wxWindow (parent to all widgets) and the "Event handling overview" page from the wxDocs. For example I found out:

The Demo has a very nice feature called "Demo Code". On that notebook page you can safely modify the code and try out stuff. You can switch between your modified version and the original and try them on "Demo" page.

Here, do the following:

Good Luck! and remember... Have Fun! You'll learn at least twice as fast if you're trying to have fun than if you're trying to rush and learn everything.

CrashCourse (last edited 2017-06-19 19:26:43 by ffx)

NOTE: To edit pages in this wiki you must be a member of the TrustedEditorsGroup.