Creating a GUI using Python, WxWidgets, and wxPython


After putting off this tutorial for as long as I possibly could (I’m not sure why) I have finally decided to buckle down and learn how to use WxWidgets using the WxPython Python bindings.

Installing

Getting WxWidgets and WxPython installed on my Debian Linux computer was as simple as installing the python-wxgtk2.6 Debian package. If you have any problems installing wxPython on your computer I suggest that you follow the instructions in the wxPython FAQ. Or you can follow the Getting Started instructions on the wxPython wiki.

Setting up the Window

Now that you have WxWidgets and WxPython installed it’s time to write a quick wxPython application. Start up your favourite python editor and enter the following:

#!/usr/bin/env python

import wx

if __name__ == "__main__":
	app = wx.App()
	app.MainLoop()

Save the file as wxHello.py, or whatever you would like. You can run the file if you want but you won’t actually see anything. The first thing that you will notice is that we import wx, this will import wxPython into your program.

The next thing that we do is create a wx.App and then call it’s MainLoop function, which starts the main GUI event loop. Here is a description of the wx.App class from the wxPython documentation :

The wx.App class represents the application and is used to:

  • bootstrap the wxPython system and initialize the underlying gui toolkit
  • set and get application-wide properties
  • implement the windowing system main message or event loop, and to dispatch events to window instances
  • etc.

Every application must have a wx.App instance, and all creation of UI objects should be delayed until after the wx.App object has been created in order to ensure that the gui platform and wxWidgets have been fully initialized.

Normally you would derive from this class and implement an OnInit method that creates a frame and then calls self.SetTopWindow(frame).

Notice the last little instruction there:

Normally you would derive from this class and implement an OnInit method that creates a frame and then calls self.SetTopWindow(frame).

So that is our next step, derive a class from wx.App and override the OnInit member:

#!/usr/bin/env python

import wx

class wxHelloApp(wx.App):
	"""The wx.App for the wxHello application"""

	def OnInit(self):
		"""Override OnInit to create our Frame"""
		frame = wx.Frame(None, title="wxHello")
		frame.Show()
		self.SetTopWindow(frame)
		return True

if __name__ == "__main__":
	app = wxHelloApp()
	app.MainLoop()

Now instead of creating a wx.App we create a wxHelloApp that is derived from a wx.App. All that the wxHelloApp does is override the OnInit() function. In that function it creates a wx.Frame (from the wxWidget docs):

A frame is a window whose size and position can (usually) be changed by the user. It usually has thick borders and a title bar, and can optionally contain a menu bar, toolbar and status bar. A frame can contain any window that is not a frame or dialog.

When we create the frame we set its parent to be nothing, and it’s title to be “wxHello”. If you run the wxHello.py file now you will be greeted by the following:

Python wxPython


It’s not much but it’s a start. Now we are going to do the same thing to the wx.Frame class that we did to the wx.App class:

#!/usr/bin/env python

import wx

class wxHelloFrame(wx.Frame):
	"""This is the frame for our application, it is derived from
	the wx.Frame element."""

	def __init__(self, *args, **kwargs):
		"""Initialize, and let the user set any Frame settings"""
		wx.Frame.__init__(self, *args, **kwargs)
		self.create_controls()

	def create_controls(self):
		"""Called when the controls on Window are to be created"""
		pass

class wxHelloApp(wx.App):
	"""The wx.App for the wxHello application"""

	def OnInit(self):
		"""Override OnInit to create our Frame"""
		frame = wxHelloFrame(None, title="wxHello")
		frame.Show()
		self.SetTopWindow(frame)

		return True

if __name__ == "__main__":
	app = wxHelloApp()
	app.MainLoop()

Now if you run this code the result will be identical to the previous run, the only difference is in the internal code. Instead of creating a wx.Frame directly we create an instance of our wx.Frame derived class wxHelloFrame.

wxHelloFrame does nothing except initialize its parent class and then call the create_controls() function which is where we will create our widgets in the future but for now does nothing.

Adding the Widgets

Now we are going to start adding the widgets to the frame in the create_controls() function. Since we want our window to be resizeable (which they are in wxPython by default) we are going to use wx.Sizer objects so that our widgets also resize (from the WxPython docs):

wx.Sizer is the abstract base class used for laying out subwindows in a window. You cannot use wx.Sizer directly; instead, you will have to use one of the sizer classes derived from it such as wx.BoxSizer, wx.StaticBoxSizer, wx.GridSizer, wx.FlexGridSizer and wx.GridBagSizer.

The concept implemented by sizers in wxWidgets is closely related to layout tools in other GUI toolkits, such as Java’s AWT, the GTK toolkit or the Qt toolkit. It is based upon the idea of the individual subwindows reporting their minimal required size and their ability to get stretched if the size of the parent window has changed. This will most often mean that the programmer does not set the original size of a dialog in the beginning, rather the dialog will assigned a sizer and this sizer will be queried about the recommended size. The sizer in turn will query its children, which can be normal windows or contorls, empty space or other sizers, so that a hierarchy of sizers can be constructed. Note that wxSizer does not derive from wxWindow and thus do not interfere with tab ordering and requires very little resources compared to a real window on screen.

We are going to use wx.BoxSizer objects to lay out our widgets (from the wxPython docs):

The basic idea behind a box sizer is that windows will most often be laid out in rather simple basic geometry, typically in a row or a column or nested hierarchies of either. A wx.BoxSizer will lay out its items in a simple row or column, depending on the orientation parameter passed to the constructor.

It is the unique feature of a box sizer, that it can grow in both directions (height and width) but can distribute its growth in the main direction (horizontal for a row) unevenly among its children. This is determined by the proportion parameter give to items when they are added to the sizer. It is interpreted as a weight factor, i.e. it can be zero, indicating that the window may not be resized at all, or above zero. If several windows have a value above zero, the value is interpreted relative to the sum of all weight factors of the sizer, so when adding two windows with a value of 1, they will both get resized equally and each will receive half of the available space after the fixed size items have been sized. If the items have unequal proportion settings then they will receive a coresondingly unequal allotment of the free space.

Whew, that’s a lot of documentation, fortunately wx.Sizer’s aren’t that difficult to use. So first we need to create a horizontal wx.BoxSizer (so that our widgets will resize horizontally) and then add wx.StaticText and wx.TextCtrl widgets to the sizer. After that we simply need to set the wx.boxSizer as the frame’s sizer and we are done.

def create_controls(self):
	"""Called when the controls on Window are to be created"""
	# Horizontal sizer
	self.h_sizer = wx.BoxSizer(wx.HORIZONTAL)

	# Create the static text widget and set the text
	self.text = wx.StaticText(self, label="Enter some text:")
	#Create the Edit Field (or TextCtrl)
	self.edit = wx.TextCtrl(self, size=wx.Size(250, -1))

	#Add to horizontal sizer
	#add the static text to the sizer, tell it not to resize
	self.h_sizer.Add(self.text, 0,)
	#Add 5 pixels between the static text and the edit
	self.h_sizer.AddSpacer((5,0))
	#Add Edit
	self.h_sizer.Add(self.edit, 1)

	#Set the sizer
	self.SetSizer(self.h_sizer)

So, as you can see creating the wx.BoxSize and the wx.StaticText widgets is pretty straightforward. When we create the wx.TextCtrl widget you’ll notice that we are setting the default size of the widget using a wx.Size object. We are setting the default width to be 250 and the default height to be -1. When you use -1 as the value for a parameter in wxWidgets it generally means that that parameter should be ignored. So since we don’t want to set the height of the edit field, we want wxWidgets to do that for us automatically, we set the height to be -1.

You’ll also notice that the first parameter when we created the wx.StaticText and wx.TextCtrl widgets is self, or our wxHelloFrame. This means that the wxHelloFrame will be the parent window of the widgets.

We then add the two widgets to the wx.BoxSizer using the wx.Sizer.Add function. When we add the wx.Statictext we set the resize proportion to be 0 since we don’t want it to resize, and when we add the wx.TextCtrl we set its resize proportion to be 1, since we want it to resize directly with the window as it resizes.

You’ll also notice that we add a spacer in in between the wx.StaticText and wx.TextCtrl widgets using the wx.Sizer.AddSpacer() function. We do this to add a 5 pixel horizontal buffer between the two widgets just so that things look nicer.

Finally we set the wxHelloFrame’s sizer to be the horizontal sizer and we are done. If you run this code you will see the following:

Python wxPython

So that’s pretty neat, you have a window with some widgets that resize. The next thing that we are going to do is add a wx.Button underneath the wx.StaticText widget.

To do this we will need to use another wx.BoxSizer, this one being a vertical sizer. Then we will add our horizontal wx.BoxSizer to the vertical sizer and then add the button. This will place the contents of the horizontal box sizer over top of the button.

def create_controls(self):
	"""Called when the controls on Window are to be created"""

	#Horizontal sizer
	self.h_sizer = wx.BoxSizer(wx.HORIZONTAL)
	#Vertical sizer
	self.v_sizer = wx.BoxSizer(wx.VERTICAL)

	#Widget Creation
	#Create the static text widget and set the text
	self.text = wx.StaticText(self, label="Enter some text:")
	#Create the Edit Field (or TextCtrl)
	self.edit = wx.TextCtrl(self, size=wx.Size(250, -1))
	#Create the button
	self.button = wx.Button(self, label="Press me!")

	#Add to horizontal sizer
	#add the static text to the sizer, tell it not to resize
	self.h_sizer.Add(self.text, 0,)
	#Add 5 pixels between the static text and the edit
	self.h_sizer.AddSpacer((5,0))
	#Add Edit
	self.h_sizer.Add(self.edit, 1)

	#Add to the vertical sizer to create two rows
	self.v_sizer.Add(self.h_sizer, 0, wx.EXPAND)
	#Add button underneath
	self.v_sizer.Add(self.button, 0)

	#Set the sizer
	self.SetSizer(self.v_sizer)

So now we create both a horizontal and vertical wx.BoxSizer and a wx.Button widget with specific label text. We then add the horizontal size to the vertical sizer, and then we add the button creating two rows of widgets. We set the resize proportion of both the button and the horizontal sizer to be 0 since we don’t want them resizing vertically. However we set the horizontal sizer’s flag to be wx.EXPAND which means:

The item will be expanded to fill the space allotted to the item.

We do this so that the horizontal sizer will continue to resize horizontally. Finally we set the sizer of the wxHelloFrame to be the vertical sizer. If you run the code you will see the following:

Python wxPython

One thing that you will notice when you run the program is that the window starts off too big for the widgets and you can resize the window smaller then the size of the widgets. This isn’t very nice so we need to add in some code to set the initial size of the window and to set the minimum size of the window.

The other thing that we need to do is do something when the button is clicked. We will make it so that when the button is clicked a message box comes up displaying whatever text was typed in the wx.TextCtrl.

def create_controls(self):
	"""Called when the controls on Window are to be created"""

	#Horizontal sizer
	self.h_sizer = wx.BoxSizer(wx.HORIZONTAL)
	#Vertical sizer
	self.v_sizer = wx.BoxSizer(wx.VERTICAL)

	#Widget Creation
	#Create the static text widget and set the text
	self.text = wx.StaticText(self, label="Enter some text:")
	#Create the Edit Field (or TextCtrl)
	self.edit = wx.TextCtrl(self, size=wx.Size(250, -1))
	#Create the button
	self.button = wx.Button(self, label="Press me!")
	#bind the button click to our press function
	self.button.Bind(wx.EVT_BUTTON, self.on_button_pressed)

	#Add to horizontal sizer
	#add the static text to the sizer, tell it not to resize
	self.h_sizer.Add(self.text, 0,)
	#Add 5 pixels between the static text and the edit
	self.h_sizer.AddSpacer((5,0))
	#Add Edit
	self.h_sizer.Add(self.edit, 1)

	#Add to the vertical sizer to create two rows
	self.v_sizer.Add(self.h_sizer, 0, wx.EXPAND)
	#Add button underneath
	self.v_sizer.Add(self.button, 0)

	#Set the sizer
	self.SetSizer(self.v_sizer)
	#Fit ourselves to the sizer
	self.v_sizer.Fit(self)
	#Set the Minumum size
	self.SetMinSize(self.v_sizer.GetMinSize())
	
def on_button_pressed(self, event):
	"""Called when the button is clicked.  It will
	show a simple dialog with the text from the
	edit field."""
	#Get the message text
	message_text = "You typed: %s" % (self.edit.GetValue())
	msg_box = wx.MessageDialog(self
		, message_text
		, "Hello!"
		, wx.OK | wx.CENTRE | wx.ICON_EXCLAMATION)
	#Show the Dialog
	msg_box.ShowModal()

Binding the wx.Button to our function is pretty simple:

#bind the button click to our press function
self.button.Bind(wx.EVT_BUTTON, self.on_button_pressed)

We simply call the wx.Button.Bind() function, bind it to the wx.EVT_BUTTON event, and tell it what function of ours to call when the button is pressed.

Then to initially size the window and set its minimum size we do the following:

#Fit ourselves to the sizer
self.v_sizer.Fit(self)
#Set the Minumum size
self.SetMinSize(self.v_sizer.GetMinSize())

To size the frame to the sizer we call the wx.Sizer.Fit() function. We then set the minimum size of the frame using the wx.Frame.SetMinSize() function to the minimum size of the vertical sizer which we get using the ws.Sizer.GetWinSize() function.

Next we handle the button’s press event using the following function:

def on_button_pressed(self, event):
	"""Called when the button is clicked.  It will
	show a simple dialog with the text from the
	edit field."""
	#Get the message text
	message_text = "You typed: %s" % (self.edit.GetValue())
	msg_box = wx.MessageDialog(self
		, message_text
		, "Hello!"
		, wx.OK | wx.CENTRE | wx.ICON_EXCLAMATION)
	#Show the Dialog
	msg_box.ShowModal()

This function is pretty straight forward, first we get the text from the wx.TextCtrl widget using the wx.TextCtrl.GetValue() function. Then we create a wx.MessageDialog instance. The wx.MessageDialog is “a simple dialog that shows a single or multi-line message, with a choice of OK, Yes, No and/or Cancel buttons.” We set the parent, title, text of the dialog and use the following flags to control how it looks and functions:

  • wx.OK – Show an OK button.
  • wx.ICON_EXCLAMATION – Shows an exclamation mark icon.
  • wx.CENTER – Is supposed to center the dialog on it’s parent.

Then we show the dialog using the wx.Dialog.ShowModal() function.

Python wxPython

Here is the all the code:

#!/usr/bin/env python

import wx

class wxHelloFrame(wx.Frame):
	"""This is the frame for our application, it is derived from
	the wx.Frame element."""

	def __init__(self, *args, **kwargs):
		"""Initialize, and let the user set any Frame settings"""
		wx.Frame.__init__(self, *args, **kwargs)
		self.create_controls()

	def create_controls(self):
		"""Called when the controls on Window are to be created"""

		#Horizontal sizer
		self.h_sizer = wx.BoxSizer(wx.HORIZONTAL)
		#Vertical sizer
		self.v_sizer = wx.BoxSizer(wx.VERTICAL)

		#Widget Creation
		#Create the static text widget and set the text
		self.text = wx.StaticText(self, label="Enter some text:")
		#Create the Edit Field (or TextCtrl)
		self.edit = wx.TextCtrl(self, size=wx.Size(250, -1))
		#Create the button
		self.button = wx.Button(self, label="Press me!")
		#bind the button click to our press function
		self.button.Bind(wx.EVT_BUTTON, self.on_button_pressed)

		#Add to horizontal sizer
		#add the static text to the sizer, tell it not to resize
		self.h_sizer.Add(self.text, 0,)
		#Add 5 pixels between the static text and the edit
		self.h_sizer.AddSpacer((5,0))
		#Add Edit
		self.h_sizer.Add(self.edit, 1)

		#Add to the vertical sizer to create two rows
		self.v_sizer.Add(self.h_sizer, 0, wx.EXPAND)
		#Add button underneath
		self.v_sizer.Add(self.button, 0)

		#Set the sizer
		self.SetSizer(self.v_sizer)
		#Fit ourselves to the sizer
		self.v_sizer.Fit(self)
		#Set the Minumum size
		self.SetMinSize(self.v_sizer.GetMinSize())

	def on_button_pressed(self, event):
		"""Called when the button is clicked.  It will
		show a simple dialog with the text from the
		edit feld."""
		#Get the message text
		message_text = "You typed: %s" % (self.edit.GetValue())
		msg_box = wx.MessageDialog(self
			, message_text
			, "Hello!"
			, wx.OK | wx.CENTRE | wx.ICON_EXCLAMATION)
		#Show the Dialog
		msg_box.ShowModal()

class wxHelloApp(wx.App):
	"""The wx.App for the wxHello application"""

	def OnInit(self):
		"""Override OnInit to create our Frame"""
		frame = wxHelloFrame(None, title="wxHello")
		frame.Show()
		self.SetTopWindow(frame)

		return True

if __name__ == "__main__":
	app = wxHelloApp()
	app.MainLoop()

Wrap up

So that’s it for this simple wxWidgets tutorial I hope everything makes sense. All-in-all I found my first experience with wxPython to be quite enjoyable, I would have liked the wxPython documentation to be a bit more robust but using it and some of the other examples on the Internet was enough for me to muddle my way through.

I still think that I prefer pyGTK to wxPython but the fact that I have more experience with pyGTK probably plays a large role in that. I also like that the fact the pyGTk seems to take a different approach to the other GUI tool kits that I have used making it able to do some really neat things. wxWidgets on the other hand seems to be more similar to other toolkits, which made me instantly more familiar with some of its functionality.

As always if you have any questions or comments, or if you notice any bugs or style irregularities feel free to add a comment below.

If you have any of your own wxPython tips or want to ask some wxPython questions why don’t you do so in the learningPython fourms?

Now it’s time for a glass of wine!

selsine

del.icio.us del.icio.us

17 Responses to “Creating a GUI using Python, WxWidgets, and wxPython”

  1. Creando interfaces grĂĄficas con wxPython | El blog de Leech
    Says:

    [...] En el blog de Learning Python publicaron un articulo bastante interesante donde explican como crear interfaces grĂĄficas multiplataformas con la librerĂ­a wxWidgets y su port para Python wxPython. EstĂĄ orientado para el principiante en interfaces grĂĄficas, y tambiĂŠn en quienes se inician en Python. La verdad que yo recomiendo mucho este conjunto de utilidades (Python, wxPython y la librerĂ­a pyWin32 para exportar a Windows) dado que su portabilidad permite desarrollar una misma aplicaciĂłn para distintos sistemas operativos, sin muchos cambios entre versiones. [...]

  2. Todd P
    Says:

    I’ve been researching a lot about cross-platform UIs. I’m looking into XUL, and even some of the Adobe offerings. Have you seen much on the actual packaging of something written with wxPython and distribution for Mac OSX and Windows? (I haven’t really found much on this, and I’m curious how the end user experience would be for this.)

  3. kongmeng
    Says:

    Thanks for the article, also liked your one on PyGTK. I’m more of the wxPython fan myself. I’ve found it has a very active user community with lots of open source widgets. A plus when it comes to making all sorts of fancy programs with minimal effort.

  4. selsine

    selsine
    Says:

    HI Todd,

    I have not looked into the packaging of a wxPython application all that much. I did stumble upon a short py2exe tutorial on the wxPython website. For OSX you you might want to read this Optimizing for Mac OS X from the wxPython site. It describes using py2app to create stand-alone applications for OSX.

    If you’ve learn anything more about this I’d love to hear about it.

  5. selsine

    selsine
    Says:

    Hi kongmeng,

    Thanks for the kind words! I still think that I like working with PyGTK a bit more, however I really can see positives on both sides.

  6. Utopia Parkway » Blog Archive » links for 2007-01-31
    Says:

    [...] learning python Âť Blog Archive Âť Creating a GUI using Python, WxWidgets, and wxPython (tags: python reference) [...]

  7. PyArticles
    Says:

    Very Good articles ! Please check out another at Python Articles

  8. JohnN
    Says:

    Good advice, thanks for the tips.

  9. Python GUI « The Lumber Room
    Says:

    [...] Also noticed I had another post that was a link to: learning python Âť Blog Archive Âť Creating a GUI using Python, WxWidgets, and wxPython [...]

  10. Jeremy
    Says:

    I just found this article from http://wiki.wxpython.org/FrontPage. This was the best presentation on sizers that I have read. Your examples made it very easy to understand a concept that was so difficult for me before. Thank you very much!

  11. Siggi Maack
    Says:

    I wish I could find more of these elementary tutorials to speed up my
    learning cure. Basically, these tutorials are necessary due to a lacking
    comprehensive wxwidgets documention. that is really bottom-up.
    Nonetheless, thank you.
    Siggi

  12. Jon Morgan
    Says:

    Thanks that was really useful. Do you have any guidance on setting up event handlers in wxWidgets?

  13. SamB
    Says:

    Easy instructions to install wxwidget and wxpython
    http://wiki.wxpython.org/InstallingOnUbuntuOrDebian

  14. Matt Linder
    Says:

    Thanks for taking the time to share your knowledge, the article it very good!

  15. Daniel
    Says:

    Hi Todd,

    Thank you for this great tutorial. I already had some experience with PyGTK and now I want to familiarize me with wxPython to start developing a bit for Mac OSX. Your tutorial helped me a lot to understand the “basic wxPython”.

    Greetings from Germany!
    Daniel

  16. Wilma
    Says:

    You really make it seem so easy with your presentation
    but I find this topic to be actually something which I think
    I would never understand. It seems too complex and
    very broad for me. I am looking forward for your next post, I will try to get the hang of
    it!

  17. Dermot Doran
    Says:

    I would recommend changing one line of this “good” tutorial. The section that describes the adding of widgets to the create_controls method/function begins with the line:
    “Now we are going to start adding the widgets to the frame in the create_controls() function”.

    However, in a tutorial that is aimed at people new to wxPython a better wording would, in my opinion, have been:
    “Now we are going to start adding the widgets to the application by modifying the create_controls() function defined in the wxHelloFrame class.

    I know this is being very, very picky, but I have seen people define a second create_controls function outside of the class definition and get very frustrated. I hope you are not irritated by this observation.

    Cheers!!

    Dermot.

Leave a Reply

 

Popular Posts