Making wxPython a standard module?

Paul McNett p at ulmcnett.com
Sat Jun 14 17:41:25 EDT 2008


Grant Edwards wrote:
> On 2008-06-14, Torsten Bronger <bronger at physik.rwth-aachen.de> wrote:
> 
>>> I've never used any of the designers, but I agree 100% that
>>> wxPython code is nasty ugly. wxPython has a very un-Pythonic
>>> API that's is, IMO, difficult to use.
>> I know that such requests may start a never-ending thread but
>> I'd really like to know what you mean with this.
> 
> [...]
> 
> Well, if we want this thread to be never ending, I'd better put
> a little dramatic hyperbole into my answer, so here goes... ;)

(blatant self-promotion warning: I'm one of the founders of Dabo, and it
sounds like you may like to take a look at it, given your comments below)


> IMO, a few of the "un-Pythonic" things about wxPython are:
> 
>  1) Window ID numbers.
> 
>       "You don't need to know what it's for, just pass a -1."
>  
>     Their very existence at the user level feels wrong.
> 
>     I'm told that for approximately 3 uber-sophisticated
>     wxWidgets programmers window IDs can be useful in some rare
>     situations.  Meanwhile everybody else working under
>     "normal" conditions has to pass a useless positional
>     parameter every time they instantiate a widget.  Things that
>     are useful only in exceptional situations should only be
>     visible in exception situations.

Dabo is a nice wrapper around wxPython, which among other things does
away with id numbers. There are a few places (wxMenu events, for one)
where you need to deal with window id's, but Dabo doesn't expose the
user to that, it handles it the way it should work.


>  2) the "flags" parameter.
> 
>       "1975 called, and they want their bit-masks back."
>  
>     The mashing together of a several dozen different,
>     completely unrelated attributes into the "flags" parameter
>     is a trick left over from C/assembly language programming
>     on machines who's memory size was measure in KB.  Rather
>     than OR-ing together a bunch of bit-patterns to make the
>     window act the way you want, you should be setting
>     individually named object attributes or passing optional,
>     named parameters to the class method.

Dabo uses properties for everything, including the individual style
bits. And it handles making the setting in the right place so it "just
works" without the user needing to know, for instance, that flag x only
works after the class is fully instantiated, or vice-versa.

Properties can be set on the object once instantiated, sent to the
constructor, or set in a subclass:

# create a textbox by instantiating the baseclass
# and sending property values to the constructor:
txt = dabo.ui.dTextBox(self, Value="Hello", FontBold=True)

# tweak some other properties:
txt.FontItalic = True
txt.BackColor = "yellow"

>  3) the parent/child tree
> 
>       "the only thing less well understood than Window IDs"
> 
>     I've been writing wxPython apps for about 9 years now, and
>     I still have only a very vague idea what the parent/child
>     tree is for.  Everybody I know just makes everything the
>     child of the first panel they put in the application frame.
>     The same people who specify Window IDs other than -1
>     probably use complex parent/child trees for something.

Every container object needs to know about its children, and every
object needs to know about its parent. So, in Dabo we have 2 properties:
Children and Parent.

>  4) sizers
> 
>       "they're like aspirin -- they work, but nobody knows exactly how"
> 
>     OK, that's a bit out-of-date since I seem to recall that
>     somebody did finally figure out how aspirin works a couple
>     years back.  The way sizers work seems pretty complex
>     compared to other GUI toolkits I've used, and the extra
>     complexity doesn't seem to provide any extra capability.
>     
>     The one thing that seems to me to be particular complicated
>     is controlling which objects "stretch" in what axis when a
>     window is resized.  I've been using them for many years,
>     but I've never gotten them more than about 90% figured out.
> 
>     Every time I write a wxPython apps, I'm initially surprised
>     at its behavior when the window is resized and have to
>     spend some trial-and-error time fiddling with the sizer
>     parameters.  I don't remember having to do that in tkInter
>     or in Trestle: things "just worked".

Sizers are admittedly a bit complex in Dabo, too. Or, sizers aren't
complex, but the code that creates them gets pretty wordy pretty fast.

vs = dabo.ui.dSizer("v")
hs = dabo.ui.dSizer("h")
hs.append(dabo.ui.dLabel(self, Caption="Name:"))
hs.append(dabo.ui.dTextBox(self))
vs.append(hs)


>  5) binding
> 
>       "What? you wanted a button that _did_ something when you clicked it?"
> 
>     Binding has actually improved a bit in the past few years.
>     It's not as obscure as it used to be, but it's still an
>     extra explicit step that shouldn't be required. It should
>     only take one line of code to create a button widget that
>     calls a specified callable when it's clicked. Something
>     like this:
> 
>       b = wx.Button(label="Click Me", action=myCallable)
> 
>     Instead you used to have to create a button and then call
>     some utility function in some other object to bind that
>     button to a callable (IIRC this was one place where Window
>     IDs could be used).  Now, the button actually has a method
>     you can use.  It's still an extra step...

Dabo takes event bindings as arguments to the constructor, such as:

def onButtonHit(self, evt):
   print "button hit"
but = dabo.ui.dButton(self, OnHit=self.onButtonHit)

Hit is the default event, where you use "action". For buttons, it occurs
when the user pushes it. There are a whole host of other events, such as
MouseHover, Idle, etc., and you can bind to any of them in the
constructor by using the syntax above.

We also do auto event binding:

class MyTextBox(dabo.ui.dTextBox):
   onValueChanged(self, evt):
     print "on value changed"

When instantiated, this textbox automatically runs the onValueChanged
handler when the value changes, even though we never made an explicit
binding.

(auto event binding can also be turned off easily).


>  6) Thousands of wx.UPPER_CASE_INTEGER_HEX_CONSTANTS
> 
>       "After all, everything is really just a base-2 integer."
>  
>     Since we don't have objects or attributes or named
>     parameters or strings, all information must be passed into
>     and out of the library as arbitrary integers constants. The
>     really great thing about that sort of API is it's
>     versatility: you can pass any value any where!  Pass a
>     width in pixels where a bitmask of window attributes is
>     expected?  No problem! 

All these flags in the global wx namespace is IMO one of its biggest warts.

But in the end, wxPython is the best GUI toolkit for Python, by far,
which is why we picked it when embarking on Dabo. We definitely made the
right choice.

> Well, the build I was running has finished, so that's probably
> enough...    

I guess I'll end the shameless self-promotion now, too.

Paul





More information about the Python-list mailing list