Sam Peterson

Sam2.jpg

PLEASE EDIT MY HOMEPAGE!! Please correct anything that's wrong. I want the Truth, the Whole Truth, and Nothing But the Truth! It's my hope that by documenting my experience with wxPython on the wiki I can help out other novice users similar to myself.

Thank you Robin for answering many of my questions! What a community; the head developers take time out of their busy schedules to help the initiates such as myself :-) . I'm flattered.

Email: <peabodyenator AT SPAMFREE gmail DOT com>

I've always been a hobbiest programmer and I'm finally pursuing my Computer Science B.S. at the University of California, Davis. I'm currently familiarizing myself with wxPython because I'm a weird breed of Linux user who loves guis (even though I do most things from the shell).

I use Emacs. I'll be posting elisp stuff I make for wxPython if I get around to writing any elisp stuff :).

Whys, What's, When's, Where's, and How's

I'm a terrible GUI programmer who wants to become a better GUI programmer :) . In learning wxPython I've come across things that weren't obvious to me. I document them here. Please, if the experienced gurus could answer my questions and correct my misconceptions I would be eternally grateful.

Why do I need to use a wx.Frame?

One of the first example programs I tried only contained a wx.Dialog. The program didn't seem to exit properly. I found out from further experimenting tha wx.Frames didn't have this problem. I still don't know why. My guess is that wx.Dialog doesn't contain the proper plumbing in its Destroy() to close the program.

Robin: Dialog-only apps are legal and supported. I expect that you were doing something else that caused the app to think that there were still top-level windows in existence. There are also ways to force the exit if needed.

   1 import wx
   2 
   3 class Dialog(wx.Dialog):
   4     def __init__(self, *args, **kw):
   5         wx.Dialog.__init__(self, *args, **kw)
   6         wx.Button(self, wx.ID_OK, pos=(20,20))
   7         wx.Button(self, wx.ID_CANCEL, pos=(20, 60))
   8 
   9 app = wx.App()
  10 dlg = Dialog(None, title="Dialog-only")
  11 dlg.ShowModal()
  12 dlg.Destroy()
  13 app.MainLoop()

Sam: Well what do you know? Guess it had been a while since I'd tried. I'm not sure where the code was I was using before, I'll try and find it. Knowing myself, I probably did something stupid and weird :) . Here's another question though, since both wx.Frame and wx.Dialog are containers, what are the pros and cons of each? Just from looking at it, the main difference seems to be that using a dialog will mean that your program will block in ShowModal. Dialogs also don't seem to allow themselves to be resized.

Why and When do I call SetAutoLayout?

When first learning wxPython I tried copying and pasting the examples from the Demo and working with them. I found SetAutoLayout a confusing function. Do I call it on a Frame? On a panel? On a panel embedded in a frame? What does it do exactly?

Answer: SetAutoLayout turns on the auto-resizing plumbing of panels that works with sizers. Apparently I don't even need to call it. Supposedly since wxPython 2.4, SetSizer automatically calls SetAutoLayout. Regardless, don't call it on frames Xp.

Robin: It is mainly a historical artifact. Before sizers there were layout constraints and so when sizers were introduced the auto layout flag was added so one or the other of them could be active or not. Since then it was decided that you would never do a SetSizer and not want to do a SetAutoLayout at the same time so it was refactored to do it for you. You can still turn off the auto layout flag if desired, but I don't think anybody ever does.

Where do sizer's go?

Just what can have a sizer?

Answer: Panels mostly. Not frames. What else?

Robin: Any container window, including frames, can have a sizer. The purpose of the sizer is to assist windows in sizing and positioning their child windows, so anything that can have child windows can have a sizer. In theory that means any class derived from wx.Window, but there really isn't any practical application of putting child windows on a wx.Button, so you would typically only do it for containers. BTW, a wx.Window can have a sizer too but it doesn't check the auto layout flag by default, so to get sizers to work with a wx.Window you need to catch its EVT_SIZE event and call self.Layout from the handler.

Sam: This is very helpful. Easy to read and well put.

Why would I call Fit?

In many of the code examples I saw the Fit member function of sizers being called. I sort of assumed these always had to be called.

Answer: Fit is meant to resize a sizer's parent window to its contents. It only needs to be called if you need to do that.

What the heck is the difference between wx.EXPAND and wx.GROW?

I've seen both in example code. I pretty much know what wx.EXPAND does. What does wx.GROW do?

Robin: Nothing. They are aliases for each other.

Where is the documentation on filters for the wx.FileDialog?

I'd like to figure out how to have more than one globbing file pattern like *.jpg,*.gif,*.png.

Robin: The sample in the C++ docs for wxFileDialog shows two patterns. One for *.bmp and *.gif, and another for *.png.

Sam: Aha! Thanks for the C++ docs tip. Using a semi-colon to seperate the patterns lets you use more than one glob on the same pattern. Thanks.

How many sizers are there?

BoxSizer? GridSizer? FlexGridSizer? RowColSizer? GridBagSizer? Are there more? When and where should I use 'em?

Robin: You missed StaticBoxSizer. RowColSizer has pretty much been obsoleted by GridBagSizer. There can be other sizers as well, as they can be implemented in Python by deriving from wx.PySizer. (In fact GridSizer and FlexGridSizer existed in Python before they did in C++.) When to use each kind depends on what the needs of the layout are.

When do I need to call Layout?

The Layout function on Sizers seems to need to be called manually at times. In what situations do I need to do this? I'm having problems where embedding a panel within a panel seems to sometimes require that I call the Layout method on the child manually. I thought I shouldn't have to do this. Am I doing something wrong? Nevermind, solved my problem, left out a SetSizer line while I was copying and pasting text.

Robin: There are times when it is needed. Keep in mind that Layout is normally called from the EVT_SIZE handler of the window that the sizer belongs to, so if something is done that needs a new layout but does not result in the window changing size, then you'll need to call Layout manually. For example, if the font of a wx.StaticText is changed then a new layout may need to be done to accomodate the new label size, but since the parent Panel has not changed size the relayout will not automatically be done.

Mac OS X issues

I run Fedora Core 4 on an Athlon as my primary machine, but I also have an iBook running OS X 10.3.9 with Python 2.4 and wxPython 2.6.0.0. I've noticed some issues:

  1. GridBagSizers don't work. Resizing their parent windows caused them to continually grow on my version. I had to rewrite something because of that.

  2. wx.Gauge seems to have one vertical size. Super-skinny. I can't get it to be bigger than that.
  3. Everything needs more borders or the layout is too clumped together.
  4. Menus don't seem to work...?

Robin: 2.6.0.0 is pretty old. You should upgrade.

  1. If it still happens in the new version please send a note about it and a small sample that shows the problem to the wxPython-users [MailingLists mail list].

  2. If the native widget doesn't support changing size then it won't change size.
  3. See the Apple human Interface Guide. wxMac tries to follow the standards set there wherever possible. Mac people are usually pretty picky and if an app doesn't follow the HIG they tend to either complain a lot or laugh it out of town.
  4. In what way? What kind of menus? What doesn't work? Again a note and a sample app sent to wxPython-users will help to either explain how to properly do what you want to do or will serve as a test-case for somebody to fix a bug.

Sam: Will do as soon as I get the chance.

My simple programs

I'll probably make this a subpage eventually since it crowds my homepage. I don't intend to make the Wiki my own personal sourceforge either. I hope that someone finds the code useful for learning wxPython or, even better, someone looks at this code, pukes, and tells me how to write better code.

Warning: Hopefully my programs aren't too offensive. One of them (currently only one program here anyway :) , more to come...) does involve downloading the Model of the Day off playboy.com. I was bored. You've been warned.

Everything's LGPL version 2 unless stated otherwise. If it can be that is. I realize TheRules mean I relinquish copyright of this page to the wxPython folks. There seems to be questions regarding that, but anyway I don't care too much about all the legal nonsense.

   1 #!/usr/bin/env python
   2 """
   3 Model of the day app.
   4 
   5 Heard of message of the day?  Well this is model of the day.
   6 Playboy.com stores about a months worth of images on their web site
   7 that can be accessed simply by changing the date on the url.  This app
   8 takes the user's input, or randomly selects a date and tries to
   9 download the image.
  10 
  11 This is my first significant wxPython program and it's merely used as
  12 a test bed for me to get used to wxPython."""
  13 
  14 __author__  = "Sam Peterson <peabodyenator@gmail.com>"
  15 __version__ = 0.01
  16 
  17 import wx, time, random, urllib2, cStringIO, os
  18 
  19 class ImagePanel(wx.Panel):
  20     def __init__(self, parent, data, image, *args, **kwargs):
  21         wx.Panel.__init__(self, parent, *args, **kwargs)
  22 
  23         self.filter = "Image Files (*.jpg,*.gif,*.png)|*.jpg"
  24         try: self.dirname = self.GetParent().GetParent().dirname
  25         except AttributeError: self.dirname = os.path.expanduser('~')
  26         self.data = data
  27         self.sizer = wx.BoxSizer(wx.VERTICAL)
  28         self.SetSizer(self.sizer)
  29         self.SetAutoLayout(True)
  30         self.bmp = image.ConvertToBitmap()
  31         self.sbmp = wx.StaticBitmap(self, bitmap=self.bmp)
  32         self.sizer.Add(self.sbmp)
  33 
  34         # button sizer
  35         button_sizer = wx.BoxSizer(wx.HORIZONTAL)
  36         self.sizer.Add(button_sizer, flag=wx.ALL, border=2)
  37 
  38         # buttons
  39         self.ok_btn = wx.Button(self, label="&Save")
  40         button_sizer.Add(self.ok_btn, flag=wx.ALL, border=2)
  41         self.close_btn = wx.Button(self, label="&Close")
  42         button_sizer.Add(self.close_btn, flag=wx.ALL, border=2)
  43 
  44         # events
  45         self.close_btn.Bind(wx.EVT_BUTTON, self.OnClickClose)
  46         self.ok_btn.Bind(wx.EVT_BUTTON, self.OnClickSave)
  47         self.sizer.Fit(self.GetParent())
  48 
  49     def OnClickSave(self, event):
  50         dlg = wx.FileDialog(self.GetParent(), defaultDir=self.dirname,
  51                             wildcard=self.filter, style=wx.SAVE)
  52         ok = False
  53         if dlg.ShowModal() == wx.ID_OK:
  54             ok = True
  55             try: self.GetParent().GetParent().dirname = dlg.GetDirectory()
  56             except: pass
  57             for each_file in dlg.GetPaths():
  58                 if os.path.splitext(each_file)[1].upper() != ".JPG":
  59                     each_file = each_file + ".jpg"
  60                 output = file(each_file, "wb")
  61                 output.write(self.data)
  62                 output.close()
  63 
  64         dlg.Destroy()
  65 
  66         if ok: self.GetParent().Close()
  67 
  68     def OnClickClose(self, event):
  69         self.GetParent().Close()
  70 
  71 class ProgressPanel(wx.Panel):
  72     def __init__(self, *args, **kwargs):
  73         wx.Panel.__init__(self, *args, **kwargs)
  74 
  75         # variables
  76         self.downloading = False
  77         self.amount = 0
  78         self.max_range = 100
  79         self.sizer = wx.BoxSizer(wx.VERTICAL)
  80 
  81         # text sizer
  82         text_sizer = wx.BoxSizer(wx.HORIZONTAL)
  83 
  84         # text controls
  85         self.day_lbl = wx.StaticText(self, label="Day:")
  86         text_sizer.Add(self.day_lbl)
  87 
  88         self.day_txt = wx.TextCtrl(self, value="%s" % time.strftime("%d"))
  89         text_sizer.Add(self.day_txt)
  90 
  91         text_sizer.Add((-1,-1),1)
  92 
  93         self.month_lbl = wx.StaticText(self, label="Month:")
  94         text_sizer.Add(self.month_lbl, flag=wx.ALIGN_RIGHT)
  95 
  96         self.month_txt = wx.TextCtrl(self, value="%s" % time.strftime("%m"))
  97         text_sizer.Add(self.month_txt)
  98 
  99         self.sizer.Add(text_sizer, 1, wx.EXPAND | wx.RIGHT)
 100 
 101         # progress bar
 102         self.progress = wx.Gauge(self, range=100)
 103         self.sizer.Add(self.progress, flag=wx.EXPAND | wx.ALL)
 104 
 105         # label sizer
 106         label_sizer = wx.BoxSizer(wx.HORIZONTAL)
 107         self.sizer.Add(label_sizer)
 108 
 109         # download label
 110         self.download_label = wx.StaticText(self, label="Downloading:")
 111         label_sizer.Add(self.download_label, flag=wx.EXPAND)
 112 
 113         # url area
 114         self.url_label = wx.StaticText(self, label="None")
 115         label_sizer.Add(self.url_label, flag= wx.EXPAND)
 116 
 117         # button sizer
 118         button_sizer = wx.BoxSizer(wx.HORIZONTAL)
 119         self.sizer.Add(button_sizer)
 120 
 121         # view image button
 122         self.view_image_btn = wx.Button(self, label="View &Image")
 123         #self.view_image_btn.Disable()
 124         button_sizer.Add(self.view_image_btn, flag=wx.ALL, border=2)
 125 
 126         # stop button
 127         self.stop_btn = wx.Button(self, label="&Stop")
 128         self.stop_btn.Disable()
 129         button_sizer.Add(self.stop_btn, flag=wx.ALL, border = 2)
 130 
 131         # random button
 132         self.x_btn = wx.Button(self, label="&Random Porn!")
 133         button_sizer.Add(self.x_btn, border=2, flag=wx.EXPAND | wx.ALL)
 134 
 135         # size label
 136         self.size_lbl = wx.StaticText(self, label="Size = XXXXXXXXXXXX")
 137         button_sizer.Add(self.size_lbl, flag=wx.EXPAND | wx.ALL, border=2)
 138 
 139         # set events
 140         self.stop_btn.Bind(wx.EVT_BUTTON, self.OnClickStop)
 141         self.x_btn.Bind(wx.EVT_BUTTON, self.OnClickX)
 142         self.view_image_btn.Bind(wx.EVT_BUTTON, self.OnClickViewImage)
 143         self.month_txt.Bind(wx.EVT_CHAR, self.OnClickViewImage)
 144         self.day_txt.Bind(wx.EVT_CHAR, self.OnClickViewImage)
 145 
 146         self.SetSizer(self.sizer)
 147         self.SetAutoLayout(True)
 148 
 149         # set growable row and column
 150         #self.sizer.AddGrowableRow(0)
 151         #self.sizer.AddGrowableCol(2)
 152 
 153     def OnClickViewImage(self,event):
 154         event_type = event.GetEventType()
 155         if event_type == wx.wxEVT_CHAR:
 156             if event.KeyCode() == wx.WXK_RETURN:
 157                 self._view_image()
 158         else:
 159             self._view_image()
 160         event.Skip()
 161 
 162     def _view_image(self):
 163         day = int(self.day_txt.GetValue())
 164         month = int(self.month_txt.GetValue())
 165         self.BeginDownload(day, month)
 166         
 167 
 168     def OnClickStop(self, event):
 169         if self.stop_btn.GetLabel() == "Stop":
 170             self.downloading = False
 171             self.stop_btn.SetLabel("&Resume")
 172         else:
 173             self.downloading = True
 174             self.Slurp()
 175             self.stop_btn.SetLabel("&Stop")
 176 
 177     def Slurp(self, *arg, **kwargs):
 178         """Slurp up the image in the background via Future Calls"""
 179         try:
 180             if self.downloading:
 181                 data = self.socket.read(4096)
 182                 amount_read = len(data)
 183                 if data:
 184                     self.data.write(data)
 185                     self.amount += amount_read
 186                     self.progress.SetValue(int((float(self.amount) / self.content_length) * 100))
 187                     self.future_call.Restart(10)
 188                 else:
 189                     self.data.seek(0)
 190                     self._clean()
 191                     image = wx.ImageFromStream(self.data)
 192                     self.data.seek(0)
 193                     size = (image.GetWidth(), image.GetHeight() + 80)
 194                     frame = wx.Frame(self, title="Yowza!", size=size)
 195                     img_panel = ImagePanel(frame, self.data.read(), image)
 196                     frame.Show(True)
 197         except IOError, e:
 198             self._clean()
 199             self.Complain(e)
 200 
 201     def _clean(self):
 202         self.progress.SetValue(0)
 203         self.downloading = False
 204         self.amount = 0
 205         self.size_lbl.SetLabel("Size = 0")
 206         self.url_label.SetLabel("None")
 207         self.stop_btn.Disable()
 208 
 209     def Complain(self, message):
 210         dlg = wx.MessageDialog(self, style=wx.OK,
 211                                message="I can'a do it captin'!\n%s" % str(message),
 212                                caption="Trouble!")
 213         dlg.ShowModal()
 214         dlg.Destroy()
 215 
 216 
 217     def BeginDownload(self, day=None, month=1):
 218         """Starts a not-really-but-sort-of asynchronous network download"""
 219         if day == None:
 220             day = random.randint(1,30)
 221             month = random.randint(1,int(time.strftime("%m")))
 222         if self.downloading: return
 223         else:
 224             self.stop_btn.Enable()
 225             date = time.strftime("%y%m%d", (int(time.strftime("%y")),month,day,1,1,1,1,1,1))
 226             url = 'http://playboy.com/sex/day/%s/imx/motd.jpg' % date
 227             self.url_label.SetLabel(url)
 228             try: self.socket = urllib2.urlopen(url)
 229             except IOError, e:
 230                 self._clean()
 231                 self.Complain(e)
 232                 return
 233             self.downloading = True
 234             info = self.socket.info()
 235             self.content_length = int(info['content-length'])
 236             self.size_lbl.SetLabel("Size = %d" % self.content_length)
 237             self.data = cStringIO.StringIO()
 238             self.amount = 0
 239             self.future_call = wx.FutureCall(20, self.Slurp, "test")
 240 
 241     def OnClickX(self, event):
 242         """Starts a not-really-but-sort-of asynchronous network download"""
 243         if self.downloading: return
 244         else:
 245             self.BeginDownload()
 246     def UpdateProgress(self, amount):
 247         self.amount += amount
 248         self.progress.SetValue(self.amount % self.max_range)
 249     def ClearProgress(self):
 250         self.progress.SetValue(0)
 251 
 252 def test_dialog():
 253     app = wx.PySimpleApp()
 254     frame = wx.Dialog(None)
 255     #panel = ProgressPanel(frame)
 256     frame.ShowModal()
 257     app.MainLoop()
 258     app.Exit()
 259     
 260 
 261 def main():
 262     app = wx.PySimpleApp()
 263     frame = wx.Frame(None, title='Test suite')
 264     panel = ProgressPanel(frame)
 265     panel.sizer.Fit(frame)
 266     frame.SetMinSize(frame.GetSize())
 267     frame.Show(True)
 268     app.MainLoop()
 269     app.Exit()
 270 
 271 if __name__ == '__main__':
 272     main()
 273     #test_dialog()


CategoryHomepage

SamDude (last edited 2008-03-11 10:50:32 by localhost)

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