Don't understand wxPython ids

G

Grant Edwards

I've decided to learn wxPython, and I'm afraid I just don't
grok the whole "id" thing where you have to pull unique
integers out of your, er, the air and then use those to refer
to objects:

From whe wxPython wiki examples:

self.button =wxButton(self, 10, "Save", wxPoint(200, 325))
EVT_BUTTON(self, 10, self.OnClick)

Does the 10 have any function other than as a handle that
allows you to refer to self.button in the EVT* call?

You're supposed to just make up unique id numbers for objects
when a) they've already got unique id numbers [at least the id
builtin thinks so] and b) they've got names that make things
even more readable?

This feels very assmebly-level. No, it's even worse than
assembly language, since even assembly language has labels and
symbols.

Why not this:

self.button =wxButton(self, 10, "Save", wxPoint(200, 325))
EVT_BUTTON(self, self.button, self.OnClick)

Or better yet this:

self.button =wxButton(self, 10, "Save", wxPoint(200, 325), action=self.OnClick)

This last way seems pretty intuitive...

Can somebody clue me in on the advantages of the
progrmmer-generated integer id numbers for objects?
 
G

Greg Krohn

Grant said:
I've decided to learn wxPython, and I'm afraid I just don't
grok the whole "id" thing where you have to pull unique
integers out of your, er, the air and then use those to refer
to objects:

From whe wxPython wiki examples:

self.button =wxButton(self, 10, "Save", wxPoint(200, 325))
EVT_BUTTON(self, 10, self.OnClick)

Does the 10 have any function other than as a handle that
allows you to refer to self.button in the EVT* call?

AFAIK, yes. But then again, I don't know much. Note that you can use -1
as the id and the button will create it's own unique id for you. Then
all you have to do is call button.GetId() whenever you need it.
You're supposed to just make up unique id numbers for objects
when a) they've already got unique id numbers [at least the id
builtin thinks so] and b) they've got names that make things
even more readable?

This feels very assmebly-level. No, it's even worse than
assembly language, since even assembly language has labels and
symbols.

Why not this:

self.button =wxButton(self, 10, "Save", wxPoint(200, 325))
EVT_BUTTON(self, self.button, self.OnClick)

Or better yet this:

self.button =wxButton(self, 10, "Save", wxPoint(200, 325), action=self.OnClick)

This last way seems pretty intuitive...

Can somebody clue me in on the advantages of the
progrmmer-generated integer id numbers for objects?

In the new wxPython (2.5.1.5) there's an easier way to bind events
without using ids at all using widget.Bind, like this:



import wx

class MyFrame(wx.Frame):
def __init__(self, *args, **kwargs):
wx.Frame.__init__(self, *args, **kwargs)

self.button = wx.Button(self, -1, "What's my id?")
self.button.Bind(wx.EVT_BUTTON, self.OnButtonPress)

def OnButtonPress(self, event):
self.button.SetLabel("My id is: %d" % self.button.GetId())


app = wx.App()
frame = MyFrame(None, -1, "Events and ids")
frame.Show(True)
app.MainLoop()



I can only think of one reasn why they chose to use ids in the first
place. Assigning many objects the same id allows you to use one EVT_*
call for those objects. But is that really a useful feature? Who knows?

greg
 
G

Grant Edwards

AFAIK, yes. But then again, I don't know much. Note that you can use -1
as the id and the button will create it's own unique id for you. Then
all you have to do is call button.GetId() whenever you need it.

Ya know, now that you mention it, I think I actually new that
once a couple years back.
In the new wxPython (2.5.1.5) there's an easier way to bind events
without using ids at all using widget.Bind, like this:

import wx

class MyFrame(wx.Frame):
def __init__(self, *args, **kwargs):
wx.Frame.__init__(self, *args, **kwargs)

self.button = wx.Button(self, -1, "What's my id?")
self.button.Bind(wx.EVT_BUTTON, self.OnButtonPress)

That's definitely getting closer to what I would expect in
something designed in the last 15-20 years, though passing the
binding as a parameter to wx.Button() sure seems like the
obvious solution for 90% of the cases I run into. Perhaps I'm
too used to Tk.
I can only think of one reasn why they chose to use ids in the first
place. Assigning many objects the same id allows you to use one EVT_*
call for those objects. But is that really a useful feature? Who knows?

That's about the only thing I could think of. The few
situations where I'd have wanted to do someting like that I'd
gladly put a for-loop iterating over a list of objects in
exchange for being able to use single-line of code the other
90% of the time.
 
D

Doug Holton

That's about the only thing I could think of. The few
situations where I'd have wanted to do someting like that I'd
gladly put a for-loop iterating over a list of objects in
exchange for being able to use single-line of code the other
90% of the time.

Right, there a couple of special cases to think of when designing event
bindings for a gui toolkit. One is where you want multiple controls to
share the same event handler (like a button and a menu item that do the
same thing). Another case is allowing people to change event bindings
"on the fly", after a window constructor has already been called and the
window set up.

I proposed some changes similar to you, see this thread:
http://lists.wxwidgets.org/cgi-bin/ezmlm-cgi?11:sss:23979:200311:dcgnanikkojpgmpdpmio#b
but nothing came out of it. I did code up an implementation for Wax
(http://wiki.wxpython.org/index.cgi/Wax ), but I don't know if it was
included.
 
R

Roger Binns

Greg said:
I can only think of one reasn why they chose to use ids in the first
place.

Don't forget how old wxWidgets is, and that it has to interoperate
with the underlying toolkits.
Assigning many objects the same id allows you to use one EVT_*
call for those objects. But is that really a useful feature? Who knows?

Here is how I use the same id in multiple locations. I can have a
menu entry, a toolbar button, and a button inside some HTML all
invoke the same function. The only coupling between them is
the id number.

You can also use id ranges in wxPython 2.5 which makes it easy to
send a whole bunch of different items to the same handler.

class FooFrame(wx.Frame):
ID_FILE_DELETE=wx.NewId()

def _init_(...):
...
menu.Append(self.ID_FILE_DELETE, "&Delete", "Delete the file")
....
toolbar.AddLabelTool(self.ID_FILE_DELETE, "Delete", ....)
....
wx.EVT_MENU(self, self.ID_FILE_DELETE, self.OnFileDelete)
....
wx.EVT_BUTTON(self, self.ID_FILE_DELETE, self.OnFileDelete)
....

def OnStateChange(...):
....
self.GetToolBar().EnableTool(self.ID_FILE_DELETE, True)
....

In my HTML:

<wxp class="Button" module="wx">
<param name="label" value="Delete ..."/>
<param name="id" value="gui.FooFrame.ID_FILE_DELETE"/>
</wxp>

Roger
 
G

Greg Krohn

Grant said:
That's definitely getting closer to what I would expect in
something designed in the last 15-20 years, though passing the
binding as a parameter to wx.Button() sure seems like the
obvious solution for 90% of the cases I run into. Perhaps I'm
too used to Tk.

I totally agree with you. The Tk way seems much more natural.
That's about the only thing I could think of. The few
situations where I'd have wanted to do someting like that I'd
gladly put a for-loop iterating over a list of objects in
exchange for being able to use single-line of code the other
90% of the time.

Ditto. You'd think it wouldn't be so hard to add a kwarg for it. At
least if it's something you can add on the Python side. Hmmm.


greg
 
G

Grant Edwards

Right, there a couple of special cases to think of when
designing event bindings for a gui toolkit. One is where you
want multiple controls to share the same event handler (like a
button and a menu item that do the same thing).

Just pass the same function to both of them when you create
them?
Another case is allowing people to change event bindings "on
the fly", after a window constructor has already been called
and the window set up.

Provide a method to set the action for an object instance after
it's been created.

Both of these have been SOP in other GUI toolkits for a dog's
age, and I can't figure out any benefit to the ID scheme.
Using an outside function to reach in and torque on the
object's internals seems so un-Pythonesque. But, all sorts of
smart people rave about wxPython, so I thought there must be
some benefit to the ID scheme that I'm missing.
 
G

Grant Edwards

Here is how I use the same id in multiple locations. I can have a
menu entry, a toolbar button, and a button inside some HTML all
invoke the same function. The only coupling between them is
the id number.

ID_FILE_DELETE=wx.NewId()
...
menu.Append(self.ID_FILE_DELETE, "&Delete", "Delete the file")
....
toolbar.AddLabelTool(self.ID_FILE_DELETE, "Delete", ....)
....
wx.EVT_MENU(self, self.ID_FILE_DELETE, self.OnFileDelete)
....
wx.EVT_BUTTON(self, self.ID_FILE_DELETE, self.OnFileDelete)

I understand, but compare the above code with this:
menu.Append("&Delete", "Delete the file", action=self.OnFileDelete)
....
toolbar.AddLabelTool("Delete", action=self.OnFileDelete)

Which is easier to understand at a glance?


[pardon the re-arrangement]
You can also use id ranges in wxPython 2.5 which makes it easy to
send a whole bunch of different items to the same handler.

Now _that's_ something you can actually point to as a "feature"
of the ID scheme.
 
G

Grant Edwards

Ditto. You'd think it wouldn't be so hard to add a kwarg for it. At
least if it's something you can add on the Python side. Hmmm.

I guess it's simple enough to subclass the widgets I use and
add one, but it feels like going to buy a new car and having to
bring your own steering wheel.
 
R

Roger Binns

Grant said:
I understand, but compare the above code with this:


Which is easier to understand at a glance?

If it is all in one place in one function then it is no big deal.
In reality if you have editable toolbars, menus loaded from definitions
elsewhere and stuff scattered across several modules then using
ids makes that easier.

Roger
 
G

Greg Krohn

Grant said:
I guess it's simple enough to subclass the widgets I use and
add one, but it feels like going to buy a new car and having to
bring your own steering wheel.

Well, no I meant importing something like this each time (it doesn't
actually work, BTW):


import wx

#Subclass wx.PyEventBinder to catch all EVT_* args
class MyPyEventBinder(wx.PyEventBinder):
def __init__(self, *args, **kwargs):
# Look for EVT_* args, bind them and del them
for kw in kwargs:
if kw.startswith('EVT_'):
self.Bind(getattr(wx, kw), kwargs[kw])
del kwargs[kw]
wx.PyEventBinder.__init__(self, *args, **kwargs)

#A little behind-the-scenes switcheroo
wx.PyEventBinder = MyPyEventBinder

if __name__ == '__main__':

class MyFrame(wx.Frame):
def __init__(self, *args, **kwargs):
wx.Frame.__init__(self, *args, **kwargs)
self.button = wx.Button(self, -1, "What's my id?",
EVT_BUTTON=self.OnButtonPress)

def OnButtonPress(self, event):
self.button.SetLabel("My id is: %d" % self.button.GetId())

app = wx.App()
frame = MyFrame(None, -1, "Events and ids")
frame.Show(True)
app.MainLoop()



If I can't figure this out, I'll try subclassing individule widgets and
throwing it in a module.

greg
 
G

Greg Krohn

Greg said:
If I can't figure this out, I'll try subclassing individule widgets and
throwing it in a module.

greg

Here we go. Use the attached module like this:

#yourmodule.py
import wx
from event_kwargs import *

.... your code ...

self.ok_button = wx.Button(self, -1, "Ok", EVT_BUTTON=self.OnOk)

....more code...


The only testing I've done is in the "if __name__ == '__main__'" part of
the module. I *haven't* tested all the widgets and events, but they
should theoretically work.

greg

#event_kwargs.py
#version: 0.0.1
#author: Greg Krohn

import wx

def _event_wrap(klass):
# Backup the original __init__
klass.__classic_init__ = klass.__init__

# The new __init__ that will deal with EVT_* kwargs
def new__init__(self, *args, **kwargs):
binds = {} # The EVT_* kwargs
new_kwargs = {} # The kwargs minus EVT_* kwargs (to pass to the orig __init__)

# Sort the kwargs into binds or new_kwargs
for kw in kwargs:
if kw.startswith('EVT_'):
binds[kw] = kwargs[kw]
else:
new_kwargs[kw] = kwargs[kw]

# Call the orig, __init__ with the cleaned-up kwargs
kwargs = new_kwargs
self.__classic_init__(*args, **kwargs)

# bind the EVT_* kwargs
for evt in binds:
self.Bind(getattr(wx, evt), binds[evt])

klass.__init__ = new__init__
return klass

#wrap the controls
for obj in dir(wx):
if hasattr(getattr(wx, obj), '__bases__'):
if wx.core.Control in getattr(wx, obj).__bases__:
exec "wx.%s = _event_wrap(wx.%s)" % (obj, obj)

if __name__ == '__main__':

class MyFrame(wx.Frame):
def __init__(self, *args, **kwargs):
wx.Frame.__init__(self, *args, **kwargs)
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.SetSizer(self.sizer)

self.button = wx.Button(self, -1, "What's my id?", EVT_BUTTON=self.OnButtonPress)
self.sizer.Add(self.button, 0, flag=wx.EXPAND)
self.text = wx.TextCtrl(self, -1, style=wx.TE_PROCESS_ENTER , EVT_TEXT_ENTER=self.OnTextEnter)
self.sizer.Add(self.text, 1, flag=wx.EXPAND)


def OnButtonPress(self, event):
self.button.SetLabel("My id is: %d" % self.button.GetId())

def OnTextEnter(self, event):
self.text.SetValue(self.text.GetValue() + '<ENTER>')
self.text.SetInsertionPointEnd()

app = wx.App()
frame = MyFrame(None, -1, "event_kwargs.py")
frame.Show(True)
app.MainLoop()
 
G

Grant Edwards

Here we go. Use the attached module like this:

#yourmodule.py
import wx
from event_kwargs import *

... your code ...

self.ok_button = wx.Button(self, -1, "Ok", EVT_BUTTON=self.OnOk)

...more code...

I _like_ it. Very clever!
The only testing I've done is in the "if __name__ == '__main__'" part of
the module. I *haven't* tested all the widgets and events, but they
should theoretically work.

I'll certainly give it a try. Thanks!
 
G

Greg Ewing

Roger said:
Here is how I use the same id in multiple locations. I can have a
menu entry, a toolbar button, and a button inside some HTML all
invoke the same function. The only coupling between them is
the id number.

Giving an identifier to a command so that it can be
invoked in multiple ways is a good idea, but it would
be much more Pythonic if a *name* could be used as the
identifier rather than a number.

Use of a number here seems to be entirely the result
of blindly carrying over a feature of the C++-oriented
API into Python.
 
G

Grant Edwards

Hmm. I guess it's time to upgrade to 2.5.

I guess not. It appears it would require upgrading so many
other things it would probably take at least a week or two. :(
 
M

Matt Brubeck

Grant Edwards said:
You're supposed to just make up unique id numbers for objects
when a) they've already got unique id numbers [at least the id
builtin thinks so] and b) they've got names that make things
even more readable?

"Wax" is a wxPython wrapper under development at:

http://zephyrfalcon.org/labs/
http://zephyrfalcon.org/weblog/arch_Wax.html

Wax has a much more Pythonic API than wxPython, and gets rid of the
numeric IDs completely. For example:

b = Button(parent, "Save", onsave)

Alternately:

b = Button(parent, "Save")

# ...

b.OnClick = onsave

Here's a longer snippet from the Wax primer:

def CreateMenu(self):
menubar = MenuBar()

menu1 = Menu(self)
menu1.Append("&New", self.New, "Create a new file")
menu1.Append("&Open", self.Open, "Open a file")
menu1.Append("&Save", self.Save, "Save a file")

menubar.Append(menu1, "&File")

self.SetMenuBar(menubar)
 
C

Christopher Barker

Greg said:
Use of a number here seems to be entirely the result
of blindly carrying over a feature of the C++-oriented
API into Python.

Honestly, that's exactly what it is. wxPython is a wrapper around a VERY
large C++ toolkit. It uses SWIG to create wrappers that pretty much
match the C++ API. This has had two big advantages:

1) Robin Dunn could actually get it working in a relatively short period
of time

2) the docs match as well, so We had a pretty complete (if confusing)
set of docs from the beginning

Note that neither of these has anything to do with what a good Python
API is, but I don't think we'd have wxPython at all if Robin had set out
to make the best API he could.

FWIW, wxPython is slowly growing more "pythonic" wrappers. There are
also a couple of efforts to make another layer between wxPython and the
user:

PythonCard
Wax
MindWrapper

Of these, PythonCard is probably the farthest along.

-Chris

--
Christopher Barker, Ph.D.
Oceanographer

NOAA/OR&R/HAZMAT (206) 526-6959 voice
7600 Sand Point Way NE (206) 526-6329 fax
Seattle, WA 98115 (206) 526-6317 main reception

(e-mail address removed)
 
R

Roger Binns

Greg said:
Giving an identifier to a command so that it can be
invoked in multiple ways is a good idea, but it would
be much more Pythonic if a *name* could be used as the
identifier rather than a number.

That is kinda what my code is doing :) The name just
happens to be a static class member mapped to an integer.
Use of a number here seems to be entirely the result
of blindly carrying over a feature of the C++-oriented
API into Python.

There has been much debate about this on the wxPython lists.
One school of thought is to make wxPython as "Pythonic" as
possible, with its own set of documentation and own
community. The fact that there is a C++ library underneath
would be a minor implementation detail.

A second school of thought (to which I belong) is that wxPython
should stick to the C++ API closely unless changes significantly
improve developer productivity. The means that wxPython
remains a member of the wxWidgets community, the standard
wxWidgets documentation remains the same and appropriate,
and it is easy to translate C++/Perl/other language wxWidgets
programs into wxPython and vice versa even for the people
who do not know the languages.

Of course there is no "right answer" as to what is best,
and ultimately it is up to Robin and his good taste. At
the moment wxPython is largely in the second camp, with
some amount of the former present. Some people have
implemented stuff in the former camp on top of wxPython
(eg wax).

People are of course welcome to criticise wxWidgets, but
as someone who as actually done programs on multiple
different platforms and GUI toolkits, I am very impressed.
I'll listen to people who have actually written something
that runs on several platforms and operating systems first.

Roger
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Staff online

Members online

Forum statistics

Threads
473,770
Messages
2,569,586
Members
45,088
Latest member
JeremyMedl

Latest Threads

Top