For GUI work in general:
Less is more
GUI work (even in productive frameworks) is about as fun and productive as painting the Eiffel Tower with a toothbrush. Go for a minimal design.
Avoid State Like The Plague
Do you put state in your GUI, or in the model? If you put it in the GUI, you are going to mess yourself up with redundant and inconsistent code paths. If you put it in the model, you risk an overly complex system that gets out of sync when your GUI fails to update from the model. Both suck.
wxPython
If you want to learn wxPython, here are a few traps I noticed:
The tutorial
Use this tutorial - http://wiki.wxpython.org/AnotherTutorial
It's the best one I found.
But remember to toggle line numbers, for easy pasting.
Events
Events are a bit like exceptions, and they are used to make things interactive.
In a vanilla python program, you write something like:
def doit(i):
print 'Doing i = ',i
for i in range(10):
doit()
print 'Results = ',result
In a GUI, you do something like:
def doit(event):
print 'An event',event,'just happened!'
event.Skip()
import wx
app = wx.App()
frame = wx.Frame(None, -1, 'The title goes here')
frame.Bind(wx.EVT_KEY_DOWN, doit)
frame.Show()
app.MainLoop()
Every time the user presses a key down, an event will be raised. Since frame
is bound to the event (frame.Bind(wx.EVT_KEY_DOWN, doit)
), the function doit
will be called with the event as an argument.
Printing to stderr isn't too hot in a gui, but doit could also call up a dialog, or do anything you want it to.
Also, you can generate your own events using timers.
Apps, Frames, Windows, Panels, and Sizers
Everything has a parent. If an event is raised, and the child doesn't skip it (using event.Skip()
), then the parent will also have to handle the event. This is analogous to exceptions raising up to higher-level functions.
A wx.App
is like the Main function.
wx.Window
isn't really used. Stuff inherits from it, and it has all the methods for sizing and layout, but you don't need to know that.
wx.Frame
is a floating frame, like the main window in Firefox. You will have main one frame in a basic application. If you want to edit multiple files then you might have more. A wx.Frame won't usually have parents.
wx.Panel
is part of a parent window. You can have several panels inside a frame. A panel can have a wx.Frame
as a parent, or it might be the child of another panel.
wx.Sizers
are used to automatically layout panels inside frames (or other panels).
Code:
def doit1(event):
print 'event 1 happened'
def doit2(event):
print 'event 2 happened'
import wx
app = wx.App()
frame = wx.Frame(None, -1, 'The title goes here')
panel_1 = wx.Panel(frame,-1,style=wx.SIMPLE_BORDER)
panel_2 = wx.Panel(frame,-1,style=wx.SIMPLE_BORDER)
panel_1.Bind(wx.EVT_KEY_DOWN, doit1)
panel_2.Bind(wx.EVT_KEY_DOWN, doit2)
panel_1.SetBackgroundColour(wx.BLACK)
panel_2.SetBackgroundColour(wx.RED)
box = wx.BoxSizer(wx.HORIZONTAL)
box.Add(panel_1,1,wx.EXPAND)
box.Add(panel_2,1,wx.EXPAND)
frame.SetSizer(box)
frame.Show()
app.MainLoop()
I've been really bad, and not used OOP practices. Just remember that even if you hate OO in most contexts, GUI programming is the place where OOP really shines.
The MCV
I don't get MCV. I don' think you need an MCV. I think a MW (model-widget) framework is fine.
For example - 2 frames that edit the same piece of text:
class Model(object):
def __init__(self):
self.value = 'Enter a value'
self.listeners = []
def Add_listener(self,listener):
self.listeners.append(listener)
def Set(self,new_value):
self.value = new_value
for listener in self.listeners:
listener.Update(self.value)
import wx
app = wx.App()
class CVFrame(wx.Frame):
def __init__(self, parent, id, title, model):
wx.Frame.__init__(self, parent, id, title, size = (100,100))
self.button = wx.Button(self, -1, 'Set model value')
self.textctrl = wx.TextCtrl(self, -1,model.value)
self.button.Bind(wx.EVT_BUTTON,self.OnSet)
self.model = model
model.Add_listener(self)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.button,0,wx.EXPAND)
sizer.Add(self.textctrl,1,wx.EXPAND)
self.SetSize((300,100))
self.SetSizer(sizer)
self.Center()
self.Show()
def OnSet(self,event):
self.model.Set(self.textctrl.GetValue())
def Update(self,value):
self.textctrl.SetValue(value)
model = Model()
frame1 = CVFrame(None, -1, 'Frame 1',model)
frame2 = CVFrame(None, -1, 'Frame 2',model)
app.MainLoop()
wxPython has a listener-subscriber framework, which is a better version of the model I just sketched out (it uses weak refs, so deleted listeners don't hang around, and so on), but that should help you get the idea.