Anygui: Generic GUI for Python
Magnus Lie Hetland
February 8th, 2002
1. Introduction
The Python standard library currently does not
contain any platform-independent GUI packages. It is the goal of
the Anygui project to change this situation. There
are many such packages available, but none has been defined as
standard, so when writing GUI programs for
Python, you cannot assume that your user has the
right package installed.
The problem is that declaring a GUI package as standard
would be quite controversial. There are some packages that are
quite commonly available, such as Tkinter; but it
would not be practical to require all installations to include it,
nor would it be desirable to require all Python GUI
programs to use it, since there are many programmers who prefer
other packages.
Anygui tries to solve this problem in a manner
similar to the standard anydbm package. There is no
need to choose one package at the expense of
all others. Instead, Anygui gives generic access to
several popular packages through a simple API, which makes it
possible to write GUI applications that work with all these
packages. Thus, one gets a platform-independent GUI module which
is written entirely in Python.
To get the latest Anygui distribution, or to
get in touch with the developers, please visit the
Anygui website:
http://www.anygui.org .
1.1 Design Goals
A. Anygui should be an easy
to use GUI package which may be used to create simple graphical
programs, or which may serve as the basis for more complex
application frameworks.
B. Anygui should be a pure
Python package which serves as a front-end for as
many as possible of the GUI packages available for
Python, in a transparent manner.
C. Anygui should include
functionality needed to perform most GUI tasks, but should
remain as simple and basic as possible.
1.2 Warning
The Anygui API is currently very much in flux
as the Anygui team keeps experimenting with
it. Because of that, incompatibilities may occur between
releases. The current release (0.1.1) should be regarded
as a prototype.
1.3 Tutorial
There is also a short tutorial available, which is
included in the installation (doc/tutorial.txt ) and
is available from the website
(http://www.anygui.org ).
2. Installation
The Anygui package comes in the form of a
gzip compressed tar archive. To install
it you will first have to uncompress the archive. On
Windows this can be done with WinZip. in
Mac OS, you can use StuffIt Expander. In
Unix, first move to a directory where you'd like to
put Anygui, and then do something like the
following:
-
foo:~/python$ tar xzvf anygui-0.1.1.tar.gz
If your version of tar doesn't support the z
switch, you can do something like this:
-
foo:~/python$ zcat anygui-0.1.1.tar.gz | tar xvf
Another possibility is:
-
foo:~/python$ gunzip anygui-0.1.1.tar.gz
foo:~/python$ tar -xvf anygui-0.1.1.tar
No matter which version you choose, you should end up with a
directory named anygui-0.1.1 .
2.1 Running setup.py
The simple way of installing Anygui is to use
the installation script that's included in the
distribution. This requires Distutils
(http://www.python.org/sigs/distutils-sig ), which is
included in Python distributions from version
2.0. To install the Anygui package in the default
location, simply run the setup script with the
install command:
-
foo:~$ python setup.py install
This will install Anygui in your standard
Python directory structure. If you don't have
access to this directory (e.g. because Python was
installed by a sysadmin, and you don't have root access) you can
install it somewhere else with the --prefix
option:
-
foo:~$ python setup.py install --prefix=${HOME}/python
2.2 Doing it Manually
Since Anygui consists of only
Python code, nothing needs to be compiled. And the
only thing needed to install Python code is to
ensure that the packages and modules are found by your
Python interpreter. This is as simple as including
the lib directory of the Anygui
distribution in your PYTHONPATH environment
variable. In bash
(http://www.gnu.org/manual/bash/ ), you could do
something like this:
-
foo:~$ export PYTHONPATH=$PYTHONPATH:/path/to/anygui/lib
To make this permanent, you should put it in your
.bash_profile file, or something equivalent. If you
don't want to mess around with this, and already have a standard
directory where you place your Python modules, you
can simply copy (or move) the anygui package (found
in anygui-0.1.1/lib ) there, or possibly
place a symlink in that directory to the anygui
package.
2.3 Making Sure You Have a Usable GUI Package
Once you have Anygui installed, you'll want
to make sure you have a usable GUI package. This is easy to
check: Simply start an interactive Python
interpreter and try to execute the following:
-
>>> from anygui import *
>>> backend()
The backend function will return the name of
the backend in use. If it is neither 'curses' nor
'text' you should be all set for making GUI
programs with Anygui. (The 'curses'
and 'text' backends use plain text to emulate
graphical interfaces on platforms that don't have them.)
Anygui currently supports the following
packages:
-
PythonWin (mswgui) http://starship.python.net/crew/mhammond/win32
Tkinter (tkgui) http://www.python.org/topics/tkinter
wxPython (wxgui) http://www.wxpython.org
Java Swing (javagui) http://www.jython.org
PyGTK (gtkgui) http://www.daa.com.au/~james/pygtk
Bethon (beosgui) http://www.bebits.com/app/1564
PyQt (qtgui) http://www.thekompany.com/projects/pykde
Curses (cursesgui) -- used when no GUI package is available
Plain text (textgui) -- used if curses is not available
Add gui to name returned by the
backend function to get the full name of the
backend module (in the anygui.backends
package). For instance, the msw backend is found in
anygui.backends.mswgui module.
In general, if you end up with a text-based solution,
cursesgui will be preferred over
textgui if your Python-installation has
a wrorking curses module. The exception is if you
are using Anygui in the interactive interpreter, in
which textgui will be preferred, to avoid
interfering with the terminal and locking up the interpreter
prompt. (If you'd like to, for some reason, you can override
this behaviour with the environment variable
ANYGUI_FORCE_CURSES ; see the API Reference
below.)
BeOS Note: The BeOS backend
(beosgui ) is currently not fully functional, but is
included nonetheless.
Of these, Tkinter is compiled in by default
in the MS Windows distribution of
Python (available from
http://www.python.org ), PythonWin (as
well as Tkinter) is included in the
ActiveState distribution, ActivePython
(available from http://www.activestate.com ), and
Java Swing is automatically available in
Jython, the Java implementation of
Python.
Note: In Mac OS 9,
Anygui (using Tkinter) works with with
Python Classic and recent versions of Python
Carbon, but older versions have problems with
Tkinter.
3. Using Anygui
Note: For some examples of working
Anygui code, see the test and
demo directories of the distribution. Remember that
the test scripts are written to test certain features of
Anygui, not to represent recommended coding
practices.
Using Anygui is simple; it's simply a matter of
importing the classes and functions you need from the
anygui module, e.g.:
-
from anygui import *
After doing this you must create an Application
object, at least one Window , and probably a few
components such as Button s and
TextField s. The Window s are added to the
Application (through its add method),
and the various components are added to the
Window . When you have done this, you call the
run method of your Application
instance.
-
# Make components here
win = Window()
# Add components to the Window
app = Application()
app.add(win)
app.run()
3.1 Avoiding Namespace Pollution
Importing everything from Anygui (as in
from anygui import * ) is fine for small programs,
where you're certain that there will be no name clashes. You may
also simply import the names you need:
-
from anygui import Application, Window
The preferred way to use modules like this is usually to
avoid cluttering your namespace, by using simply
import anygui . However, if you are going to create a lot of
widgets, the anygui prefix may be
cumbersome. Therefore, I suggest renaming it to
gui , either with a simple assignment...
-
import anygui; gui = anygui
... or, in recent versions of Python:
-
import anygui as gui
Then you can instantiate widgets like this:
-
win = gui.Window()
The examples in this documentation use the starred import,
for simplicity.
3.2 Importing the Backends Directly
If you wish to import a backend directly (and "hardwire
it" into your program), you may do so. For instance, if you
wanted to use the wxPython backend,
wxgui , you'd replace
-
from anygui import *
with
-
from anygui.backends.wxgui import *
This way you may use Anygui in standalone
executables built with tools like py2exe
(http://starship.python.net/crew/theller/py2exe/ ) or
the McMillan installer
(http://www.mcmillan-inc.com/install1.html ), or with
jythonc with the --deep option or
equivalent.
Note: Compiling jar files of
Anygui programs with Jython may not
work in the current version.
Note that the namespace handling still works just fine:
-
import anygui.backends.tkgui as gui
3.3 Creating a Window
One of the most important classes in Anygui
is Window . Without a Window you have
no GUI; all the other widgets are added to
Window s. Knowing this, we may suspect that the
following is a minimal Anygui program (and we would
be right):
-
from anygui import *
app = Application()
win = Window()
app.add(win)
app.run()
This example gives us a rather uninteresting default
window. You may customise it by setting some of its properties,
like title and size :
-
w = Window()
w.title = 'Hello, world!'
w.size = (200, 100)
If we want to, we can supply the widget properties as
keyword arguments to the constructor:
-
w = Window(title='Hello, world!', size=(200,100))
3.4 The set Method and the Options Class
If you want to change some attributes of a widget, you can
either just set them directly, or (if you'd like to set several
at once), use the set method, just like the
constructor:
-
w.set(title='Hello, again', size=(300,200))
Supplying the same attributes with the same values to a
lot of widgets (if you are making several buttons with the same
size, for instance) can be a bit impractical (you'll learn more
about buttons in a little while):
-
bt1 = Button(left=10, width=50, height=30, text='Button 1')
bt2 = Button(left=10, width=50, height=30, text='Button 2')
bt3 = Button(left=10, width=50, height=30, text='Button 3')
To deal with this, the widget constructors (and the
set method) can take Options objects
as positional parameters:
-
opt = Options(left=10, width=50, height=30)
bt1 = Button(opt, text='Button 1')
bt2 = Button(opt, text='Button 2')
bt3 = Button(opt, text='Button 3')
As you can see, this saves quite a lot of typing. You can
use as many Options arguments as you like.
3.5 The modify Method
Just like set can be used to set the
attributes of a Component , the modify
method can be used to modify them, without
rebinding them to another value. To show the difference,
consider the following example (where foo is an
attribute that does nothing special):
-
>>> from anygui import *
>>> btn = Button()
>>> some_list = [1, 2, 3]
>>> btn.foo = some_list
>>> btn.modify(foo=[4, 5, 6])
>>> btn.foo
[4, 5, 6]
>>> some_list
[4, 5, 6]
>>> btn.set(foo=[7, 8, 9])
>>> btn.foo
[7, 8, 9]
>>> some_list
[4, 5, 6]
As you can see, using modify modifies the
list, while set replaces it. The
modify method is used for (among other things)
implementing Model-View-Controller systems. (More about that
later.)
The modify method works as follows: If there
is a specific internal method for modifying an attribute, that
is called. Otherwise, the supplied value will be assigned to
self.name[:] (where name is the
attribute in question). If that doesn't work (a
TypeError exception is raised), the value will be
assigned to self.name.value . If that doesn't work
either, the attribute will be rebound to the new value, with the
same result as using set . So, to avoid any in-place
modification, all you need to do is use immutable values:
-
>>> from anygui import *
>>> btn = Button()
>>> some_list = [1, 2, 3]
>>> btn.foo = tuple(some_list)
>>> btn.modify(foo=[4, 5, 6])
>>> btn.foo
[4, 5, 6]
>>> some_list
[1, 2, 3]
3.6 The refresh Method
The modify method is used to modify
attributes in-place, e.g. to keep them in sync with a
widget. This is done automatically when you change a widget
through the graphical interface. In a way, the
refresh method works the other way: If you modify an
attribute, you can call the refresh method to keep
the widget's appearance in sync with its state. When you assign
to an attribute, refresh is called automatically;
you only have to call it yourself if you have an attribute which
is a mutable object, and you modify that object.
For more info about the use of refresh , see
the section "About Models, Views, and Controllers",
below.
3.7 Adding a Label
Simple labels are created with the Label
class:
-
lab = Label(text='Hello, again!', position=(10,10))
Here we have specified a position just for fun; we don't
really have to. If we add the label to our window, we'll see
that it's placed with its left topmost corner at the point
(10,10) :
-
w.add(lab)
3.8 Layout: Placing Widgets in a Frame
This section goves a simple example of positioning
Components ; for more information about the
Anygui layout mechanism, please refer to the API
Reference (below).
-
win.add(lab, position=(10,10))
win.add(lab, left=10, top=10)
win.add(lab, top=10, right=10)
win.add(lab, position=(10,10), right=10, hstretch=1)
In the last example hstretch is a Boolean
value indicating whether the widget should be stretched
horizontally (to maintain the other specifications) when the
containing Frame is resized. (The vertical version
is vstretch .)
Just like in component constructors, you can use
Options objects in the add method,
after the component to be added:
-
win.add(lab, opt, left=10)
3.8.1 Placing More Than One Widget
The add method can also position a
sequence of widgets. The first widget
will be placed as before, while the subsequent ones will be
placed either to the right , to the
left , above (up ), or below
(down ), according to the direction
argument, at a given distance (space ):
-
win.add((lab1, lab2), position=(10,10),
direction='right', space=10)
Note: Remember to enclose your
components in a sequence (such as a tuple or a list), since
add allows you to use more positional arguments,
but will treat them differently. If you want to use
Options objects, place them outside (after) the
sequence. For more information see the section about the
Frame class in the API Reference below.
3.9 Buttons and Event Handling
Buttons (as most components) work more or
less the same way as Labels . You can set their
size , their position , their
text , etc. and then add them to a
Frame (such as a Window ). The thing
that makes them interesting is that they emit
events. Each time the user clicks a button,
it sends out a click event. You can catch these
events by linking your button to one or more event
handlers. It's really simple:
-
btn = Button(text='Greet Environment')
def greeting(**args):
print 'Hello, World!'
link(btn, greeting)
The event handling is taken care of by the call to
link . An event handler may receive several keyword
arguments, and if you're not particularly interested in any of
them, simply use something like **args above. (For
more information about this, see the section about global
functions in the API Reference below.)
3.10 About Models, Views, and Controllers
The Anygui MVC mechanism (based on the
refresh method and the Assignee
protocol) is described in the API Reference below. Here is a
short overview on how to use it.
A model is an object that can be
modified, and that can notify other objects, called
views , when it has been modified. A
controller is an object that can modify the
model, in particular as a direct response to a user action (such
as clicking the mouse or typing some text). In
Anygui, Component s double as both
views (showing a model's state to the user) and controllers
(letting the user modify the model). Even though
Anygui supports using models this way, you can also
create complete application without using them.
Models are in general instances of some subclass of the
Model class, although they don't have to be; see
the API Reference below for a description of how they work. (The
Model class is currently internal to the
Anygui package, but it can be found int he
anygui.Models module.) The Model s that
are included in Anygui are:
-
BooleanModel -- represents a Boolean value
ListModel -- behaves like a list
NumberModel -- represents a numerical value
TextModel -- acts like a mutable string
These all have a value attribute which may be
used to change their internal value. They also support other
operations, such as indexing and slicing etc. for
ListModel . These are very easy to use: Just assign
them to an attribute of a Component :
-
# You'll learn about CheckBoxes in a minute
cbx = CheckBox(text='Simple model test')
state = BooleanModel(value=1)
cbx.on = state
Now, if you change state (e.g. with the
statement state.value=0 ) this will automatically be
reflected in the CheckBox (which will be acting
like a view). If the user clicks the CheckBox , the
model will be changed.
To keep a view up-to-date manually you can call its
refresh method. This can be useful if you use a
simple (non-Model ) mutable value such as a list in
an attribute:
-
btn = Button()
rect = [0, 0, 10, 10]
btn.geometry = rect
rect[3] = 20
btn.refresh()
After modifying rect , the button will not
have changed, since it can't detect the change by
itself. (That's only possible when you use a real model.)
Therefore, you call btn.refresh to tell it to update
itself.
If you assign a value to an attribute, the
refresh method will be called automatically, so
another way of doing the same thing is:
-
btn = Button()
rect = [0, 0, 10, 10]
btn.geometry = rect
rect[3] = 20
btn.geometry = rect
Warning: Because of the controller behaviour
of Component s, if the Button is
resized, rect will be modified. If you don't want
this behaviour, use a tuple instead of a list, since tuples
can't be modified.
If you want another object to monitor a model, you can
simply use the link method, since all models
generate an event (of the type default ) when they
are modified.
Example:
-
from anygui import *
>>> mdl = BooleanModel()
>>> mdl.value = 1
>>> def model_changed(**kw):
>>> print 'The model has changed!'
>>> link(mdl, model_changed)
>>> mdl.value = 0
The model has changed
>>> mdl.value = 0
The model has changed
Note the last two lines: We haven't really changed the
model, but the event handler is called nonetheless. If you want
to know whether the model really changed, you must retain a copy
of its state, and compare the new value.
3.11 Using CheckBoxes
A CheckBox is a toggle
button, a button which can be in one of two states,
"on" or "off". Except for that, it works more or less like any
other button in that you can place it, set its text, and
link an event handler to it.
Whether a CheckBox is currently on or off is
indicated by its on attribute.
3.12 RadioButtons and RadioGroups
RadioButton s are toggle buttons, just like
CheckBox es. The main differences are that they look
slightly different, and that they should belong to a
RadioGroup .
A RadioGroup is a set of
RadioButton s where only oneRadioButton is permitted to be "on" at one
time. Thus, when one of the buttons in the group is turned on,
the others are automatically turned off. This can be useful for
selecting among different alternatives.
RadioButton s are added to a
RadioGroup by setting their group
property:
-
radiobutton.group = radiogroup
This may also be done when constructing the button:
-
grp = RadioGroup()
rbn = RadioButton(group=grp)
Note: The behaviour of a
RadioButton when it does not belong to a
RadioGroup is not defined by the
Anygui API, and may vary across backend. Basically,
a RadioButton without a RadioGroup is
meaningless; use a CheckBox instead.
RadioGroups also support an add
method, as all other Anygui container-like
objects:
add (button)
Adds the button to the group, including setting
button.group to the group. As with the
other add methods, the argument may be either a
single object, or a sequence of objects.
3.13 ListBox
A ListBox is a vertical list of items that
can be selected, either by clicking on them, or by moving the
selection up and down with the arrow keys. (For the arrow keys
to work, you must make sure that the ListBox has
keyboard focus. In some backends this requires using the
tab key.)
Note: When using Anygui
with Tkinter, using the arrow keys won't change the
selection, only which item is underlined. You'll have to use the
arrow keys until the item you want to select is underlined; then
select it by pressing the space bar.
A ListBox 's items are stored in its attribute
items , a sequence of arbitrary objects. The
text displayed in the widget will be the result of applying the
built-in Python function str to each
object.
-
lbx = ListBox()
lbx.items = 'This is a test'.split()
The currently selected item can be queried or set through
the selection property (an integer index, counting
from zero). Also, when an item is selected, a
select event is generated, which is the default
event type for a ListBox . This means that you can
either do
-
link(lbx, 'select', handler)
or
-
link(lbx, handler)
with the same result. (This is similar to the
click event, which is default for
Buttons ; for more information, see the API
Reference below.)
3.14 TextField and TextArea
Anygui's two text widgets,
TextField and TextArea are quite
similar. The difference between them is that
TextField permits neither newlines or tab
characters to be typed, while TextArea does. Typing
a tab in a TextField will simply move the focus to
another widget, while pressing the enter key will send an
enterkey event (which is the
TextField 's default event type).
The text in a text component is stored in its
text property (a string or equivalent), and the
current selection is stored in its selection
property (a tuple of two integer indices).
3.15 Making Your Own Components and LayoutManagers
Currently, you can create your own components by combining
others in Frame s, and wrapping the whole thing up
as a class. One of the main reasons for doing this would be to
emulate a feature (such as a tabbed pane) available in some
backends, but not in others. One could then actually
use the native version in the backends
where it is available (such as wx , in this case),
and use the "emulation" in the others. There is some limited
support for this in the backend function (which
will allow you to check whether you are currently using the
correct backend), but in the future, a more complete API will be
developed for this, allowing you access to the coolest features
of your favorite GUI package, while staying "package
independent".
You can already create your own layout managers, by
properly supporting the methods add ,
remove , and resized . The simplest way
of doing this is to subclass LayoutManager , which
gives you the add and remove methods
for free. You can then concentrate on the method
resized which takes two positional arguments,
dw , and dh (change in width and change
in height) and is responsible for changing the geometries of all
the components in the Frame the
LayoutManager is managing. (This frame is available
through the private attribute self._container .)
To get more control over things, you should probably also
override the two internal methods add_components
and remove_component :
add_components (self, *items,
**kws)
Should add all the components in items, and
associate them with the options in kws , for later
resizing.
remove_component (self,
item)
Should remove the given item.
4. API Reference
The following reference describes the full official API of
the current version (0.1.1) of Anygui.
4.1 Environment Variables
Some environment variables affect the behaviour of the
Anygui package. These must be set in the
environment of the program using Anygui. They may
either be set permanently through normal operating system
channels (check your OS documentation for this), or possibly
just set temporarily when running your program. In
Unix shells like bash, you can set the
variables on the command line before your comand, like this:
-
foo:~$ ANYGUI_SOMEVAR='some value' python someprogram.py
where ANYGUI_SOMEVAR is some environment
variable used by Anygui.
Since Jython doesn't support OS environment
variables, you'll have to supply them with the command-line
switch -D :
-
foo:~$ jython -DANYGUI_SOMEVAR='some value' someprogram.py
You can also set these environment variables in your own
program, by using code like the following before you import
Anygui:
-
import os
os.environ['ANYGUI_SOMEVAR'] = 'some value'
This will probably not work well in Jython, though.
The environment variables used by Anygui
are:
ANYGUI_WISHLIST : A whitespace separated list
of backend names in the order you wish for Anygui
to try to use them. The backends are identified with a short
prefix such as wx for wxgui , or
tk for tkgui . For a full list of
available backends, see the section "Making Sure You Have a GUI
Backend" above. Only the backends in this list will be tried; if
you don't set ANYGUI_WISHLIST , then the following
is the default:
-
'msw gtk java wx tk beos qt curses text'
If you insert an asterisk in the wishlist, it will be
interpreted as "the rest of the backends, in default order". So,
for instance,
-
ANYGUI_WISHLIST='tk wx * text curses'
is equivalent to
-
ANYGUI_WISHLIST='tk wx msq gtk java beos qt text curses'
Example:
-
foo:~$ ANYGUI_WISHLIST='tk wx qt' python someprogram.py
ANYGUI_DEBUG : When Anygui tries
to import a backend, it hides all exceptions, assuming they are
caused by the fact that a given backend doesn't work in your
installation (because you don't have it installed or something
similar). However, at times this may not be the reason; it may
simple be that a given backend contains a bug. To track down the
bug, set the ANYGUI_WISHLIST to some true (in a
Python sense) value. (If the value supplied can be
converted to an integer, it will. Otherwise, it will be treated
as a string.) This will make Anygui print out the
stack traces from each backend it tries to import.
There is one exception to this rule: If the true value
supplied is the name of one of the backends (such as
tk or curses ) only the traceback
caused by importing that backend will be shown. This can be
useful to make the output somewhat less verbose.
Example:
-
foo:~$ ANYGUI_DEBUG=1 python someprogram.py
ANYGUI_ALTERNATE_BORDER : This Boolean
variable affects cursesgui , making it use the same
border-drawing characters as textgui
('+' , '-' , and '|' ). This
may be useful if your terminal can't show the special
curses box-drawing characters properly.
ANYGUI_SCREENSIZE : Affects
textgui . Gives the terminal ("screen") dimensions,
in characters. This should be in the format
widthx height,
e.g. 80x24 . If this environment variable is not
supplied, the standard Unix variables
COLUMNS and LINES will be used. If
neither is provided, the default size 80x23 will be
used.
ANYGUI_FORCE_CURSES : Normally,
cursesgui will not be selected if you are in the
interactive interpreter. If you want to force the normal
selection order (trying to use cursesgui before
resorting to textgui ) you can set this variable to
a true value. Note that this is not the same as setting
ANYGUI_WISHLIST to 'curses' , since
that will ignore all other backends.
ANYGUI_CURSES_NOHELP : If you don't want the
help-screen that appears when an Anygui application
is started using cursesgui (or
textgui ), you can set this variable to a true
value.
4.2 Global Functions
application ()
Returns the current Application
object.
backend ()
Returns the name (as used in ANYGUI_WISHLIST )
of the backend currently in use.
Example:
-
if backend() == 'wx':
some_wx_code()
else:
some_generic_code()
link (source,
[event,] handler, weak=0,
loop=0)
Creates a link in the Anygui event system,
between the source (any object) and the
handler (any callable, or a (obj,func)
pair, where func is an unbound method or function,
and obj is an object which will be supplied as the
first parameter to func ). Supplying an
event (a string) will make the link carry only
information about events of that type. If no event is supplied,
'default' will be assumed. Setting
weak to a true value will use weak references when
setting up the link, so that no objects will be "kept alive" by
the link.
A send-loop occurs if an object sends an event "to itself"
(i.e. it is the source argument of a call to
send which hasn't returned at the point where one
of its methods are about to be activated as a handler). The
truth value loop decides whether this handler will
be activated in such a loop. (If send was called
with loop=1 , loops will be allowed anyway.)
Note that source , event , and
handler are strictly positional parameters, while
the others (weak , and loop ) must be
supplied as keyword parameters.
Sometimes one might want an event handler that reacts to a
specific event from any source, or
any event from a specific source; or even
any event from any
source. To do that, simply use the special value
any as either source, event, or both.
Example:
-
from anygui import *
>>> def monitor_events(event, **kw):
... print 'An event occurred:', event
...
>>> link(any, any, monitor_events)
>>> btn = Button()
>>> send(btn, 'foobar')
An event occurred: foobar
If you use send(btn, 'click') in this
example, you will get two events, since the
Button will detect the click event
(which is its default), and issue a default event
as well.
Note: You need to explicitly supply the
event type if you want to respond to any event type; otherwise
you will only respond to the default type.
Event handlers that react to the same event will be called
in the order they were registered (with link ),
subject to the following: (1) All handlers registered with a
specific source will be called before handlers with the value
any as source; (2) all handlers registered with a
specific event (including default ) are called
before handlers with the value any as event.
For more information on sending events, see
send , below.
send (source,
event='default' , loop=0,
**kwds)
When this is called, any handlers (callables) linked to
the source, but which will not cause a send-loop (unless
loop is true) will be called with all the keyword
arguments provided (except loop ), in the order in
which they were linked. In addition to the supplied keyword
arguments, the event framework will add source ,
event , and the time (as measured by the standard
Python function time.time ) when
send was called, supplied with the
time argument.
Note that source , and event , are
strictly positional parameters, while the others
(loop , and any additional arguments the user might
add) must be supplied as keyword parameters.
Example:
-
# Link an event handler to a button, and then manually send a
# default event from the button. This event would have been
# sent automatically if we clicked the button. Note that we
# only use the arguments we need, and lump the rest in **kw.
def click(source, time, **kw):
print 'Button %s clicked at %f.' % (source.text, time)
btn = Button(text='Click me')
link(btn, click)
send(btn) # Fake a button click -- will call click()
For information about the order in which event handlers
are called, see link , above.
Important: Due to the current semantics
of the any value, using it in send may
not be a good idea, since the result might not be what you
expect. For instance, calling send(any, any) will
only activate event handlers which have been linked to the value
any as both source and event, not to "event
handlers with any source and any event". This may change in
future releases. The current behaviour of send with
any is consistent with unlink .
unlink (source,
[event,] handler)
Undoes a call to link with the same
positional arguments. If handler has been registered
with either source or event as
any , that parameter will be irrelevant when
deciding whether or not to remove that link. For instance:
-
link(foo, any, bar)
unlink(foo, baz, bar)
Here the link created by link(foo, any, bar)
will be removed by the call to unlink .
Note: This behaviour (unlinking handlers
registered with the any value) may change in future
releases.
Default Events: When used without the
event argument, both link and send use
an event type called default . Most event-generating
components have a default event type, such as click
for Button s. The fact that this event type is
default for Button means that when a
Button generates a click event it will
also generate a default
event. So, if you listen to both click events and
default events from a Button , your
event handler will always be called twice.
unlinkHandler (handler)
Removes a handler completely from the event
framework.
unlinkMethods (obj)
Unlinks all handlers that are methods of
obj .
unlinkSource (source)
Remove the source (and all handlers linked to it) from the
event framework.
4.3 Classes
Base Classes and Common Behaviour
All components are subclasses of corresponding abstract
components which implement behaviour common to all the
backends. So, for instance, Button subclasses
AbstractButton . These abstract components, again,
subclass AbstractComponent , which implements
behaviour common to all components.
Perhaps the most important behaviour is attribute handling
(inherited from the Attrib mixin), which means that
setting a components attributes may trigger some internal method
calls. For instance,
-
win.size = 300, 200
will automatically resize the component
win . Attributes common to all components are:
-
x -- x-coordinate of upper left corner
y -- y-coordinate of upper left corner
position -- equivalent to (x, y)
width -- component width
height -- component height
size -- equivalent to (width, height)
geometry -- equivalent to (x, y, width, height)
visible -- whether the component is visible
enabled -- whether the component is enabled
text -- text associated with the component
These can all be set as keyword arguments to the component
constructors. Also, Options objects (with similar
constructors) can be used as positional arguments in the
constructor, with all the Options 's attributes
being set in the component as well.
Common to Application , Window ,
and Frame is the contents attribute,
as well as the add and remove
methods. These will be described with the individual classes
below.
All Attrib subclasses (including components,
Application , and RadioGroup ) share the
following methods:
set (*args, **kwds)
Used to set attributes. Works like the Attrib
constructor, setting attributes, and optionally using
Options objects.
modify (*args, **kwds)
Works like the set method, except that the
attributes are modified in place. That
means the following (for an attribute named foo ):
(1) If there exists an internal method (implemented in
Anygui) for modifying the attribute inplace (called
_modify_foo ), use that; otherwise (2) try to use
slice assignment to change the value (will work for lists and
ListModels etc.); if that doesn't work, (3) assign
to the value's value attribute (used to modify
Model s. If neither of these approaches work, simply
rebind the attribute (equivalent to using the set
method).
As with set and ordinary attribute
assignment, the refresh method will automatically be
called when you use modify .
refresh ()
When an attribute of a Component (or
Application , RadioGroup , or an
instance of another Attrib subclass) is assigned a
value, the Component is automatically updated to
reflect its new state. For instance, if you have a
Label lbl , assigning a value to
lbl.geometry would immediately change the
Label 's geometry, and assigning to
lbl.text would change its text.
This is good enough for most cases, but sometimes an
attribute can contain a mutable value, such as a list, and
changing that will not update the Component . For
instance, if you use a list to hold the items of a
ListBox , you could end up in the following
situation:
-
lbx = ListBox()
lbx.items = 'first second third'.split()
# More code...
lbx.items.append('fourth')
After performing this code, nothing will have happened to
the ListBox , because it has no way of knowing that
the list has changed. To fix that, you can simply call its
refresh method:
-
lbx.refresh()
This method checks whether any attributes have changed,
and make sure that the Component us up to
date.
Updating Automatically
Updating Components explicitly can be useful,
but sometimes you would want it to be done for you,
automatically, each time you modify an object that is referred
to by a Component attribute. This can be taken care
of by link and send . If your object
uses send every time it's modified, and you
link the object to your Component 's
refresh method, things will happen by
themselves:
-
class TriggerList:
def __init__(self):
self.list = []
def append(self, obj):
self.list.append(obj)
send(self)
def __getitem__(self, i):
return self.list[i]
lbx = ListBox()
lbx.items = TriggerList()
link(lbx.items, lbx.refresh)
Now, if we call lbx.items.append('fourth') ,
lbx.refresh will automatically be called. To make
your life easier, Anygui already contains some
classes that send signals whend they are modified; these classes
are called Model s.
Model and Assignee
The Anygui models (BooleanModel ,
ListModel , TextModel , and
NumberModel ) are objects that call
send (with the 'default' event) when
they are modified.
An Assignee (part of the Anygui
Model-View-Controller mechanism) is an object that supports the
methods assigned and removed . These
are automatically called (if present) when the object is
assigned to one of the attributes of an Attrib
object (such as a Component ). Model s
use this behaviour to automatically call link and
unlink , so when the Model is modified,
the refresh method of the Attrib object
is called automatically.
All models have a value attribute, which
contains a "simple" version of its state (such as a number for
NumberModel , a list for ListModel ,
etc.) Assigning to this attribute is a simple way of modifying
the model in place.
class Application
To instantiate Window s, you must have an
Application to manage them. You typically
instantiate an application at the beginning of your
program:
-
app = Application()
# Build GUI and run application
In some cases subclassing Application might
be a useful way of structuring your program, but it is in no way
required.
Application has the following methods:
run ()
Starts the main event loop of the graphical user
interface. Usually called at the end of the program which set up
the interface:
-
app = Application()
# Set up interface
app.run()
add (win)
Adds a Window to the
Application , in the same way
Components can be added to Frame s (see
below). A Window will not be visible until it has
been add ed to the current Application
object, and that Application is running . When
constructing new Window s after
Application.run has been called, you should ensure
that you add your Window to your
running Application after all the
Components have been added to your
Window ; otherwise, you may see them appearing and
moving about as Anygui takes care of the
layout. (Before Application.run is called, this is
not an issue, since no Window s will be appear
before that time.)
The parameter win can be either a single
Window , or a sequence of
Windows .
remove (win)
Removes a Window from the application. This
will make the Window disappear.
contents
A read-only property containing a tuple of the
Window s the Application currently
manages.
class Button
A component which, when pressed, generate a
'click' event, as well as a 'default'
event. Thus, in the following example, both
handler1 and handler2 will be called
when the button is pressed:
-
btn = Button()
def handler1(**kw): print 'Handler 1'
def handler2(**kw): print 'Handler 2'
link(btn, 'click', handler1)
link(btn, handler2)
class CheckBox
CheckBox is a kind of button, and thus will
also generate 'click' and 'default'
events when clicked. But in addition, each CheckBox
has a Boolean attribute on , which is toggled each
time the box is clicked. The state of the CheckBox
can be altered by assigning to this attribute.
The on property will be automatically
modified (as per the MVC mechanism) when the user clicks the
CheckBox . This will also cause the
CheckBox to send a click
and a default event.
The on attribute is a useful place to use a
BooleanModel .
class Frame
Frame is a component which can contain other
components. Components are added to the Frame with
the add method:
add (comp, [opts,] **kwds)
Adds one or more components. The parameter
comp may be either a single component, or a
sequence of components. In the latter case, all the components
will be added.
The opts parameter containes an
Options object (see below) which gives information
about how the object should be laid out. These options can be
overridden with keyword arguments, and all this information will
be passed to the LayoutManager (see below) of the
Frame , if any. This LayoutManager is
stored in the layout property.
remove (comp)
Removes a component from the Frame .
contents
This is a read-only property which contains the contents
(a tuple of Components ) of the
Frame .
class Label
A Label is a simple component which displays
a string of text. (Label can only handle one line
of text.)
class LayoutManager
A layout manager is responsible for setting the
geometry properties of a set of components when
their parent Frame changes shape. The default
LayoutManager (and the only one supplied with the
current release) is the Placer (see below).
Note: Although Anygui 0.1
comes only with this layout manager, more will appear in the
future.
class ListBox
Shows a list of options, of which one may be selected. The
ListBox has two special attributes:
items , a sequence of items to display, and
selection , the currently selected (as an index in
the items sequence).
The selection property will be automatically
modified (as per the MVC mechanism) when the user makes a
selection. This will also cause the ListBox to
send a select and a
default event.
class Model
See the section on Model and
Assignee above.
class Options
Options is a very simple class. It is simply
used to store a bunch of named values; basically a dictionary
with a different syntax. (For more information about the
bunch class, see
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52308 .)
You can set the attributes of an Options
object and then supply it as an optional first parameter to the
constructors of widgets:
-
opt = Options()
opt.width = 100
opt.height = 50
opt.x = 10
btn = Button(opt, y=10)
lbl = Label(opt, y=70)
Here btn and lbl will have the
same width , height , and x
attributes, but differing y attributes.
You can also set the attributes of an Options
object through its constructur, just like with
components:
-
opt = Options(width=100, height=50, x=10)
Options objects can also be used when
supplying arguments to the add method of
Frame :
-
# Assuming a Placer LayoutManager:
opt = Options(left=10, right=10, hstretch=1)
win.add(lbl, opt, top=10)
win.add(btn, opt, top=(lbl,10))
class Placer
A simple but powerful layout manager. When adding
components to a Frame whose layout
attribute is set to a Placer , you can supply the
following keyword arguments:
-
left -- the Component's left edge
right -- the Component's right edge
top -- the Component's top edge
bottom -- the Component's bottom edge
hmove -- move horizontally on resize
vmove -- move vertically on resize
hstretch -- stretch horizontally on resize
vstretch -- stretch vertically on resize
direction -- 'left', 'right', 'up', or 'down'
space -- spacing between multiple Components
The geometry specifiers (left ,
right , top , and bottom )
can be set to either None (the default; will use
the Component 's existing coordinates), a distance
(from the corresponding Frame edge), a
Component (will align the edge with the opposite
edge of the given component), or a tuple
(component, distance)
(as with only a Component , except that a gap of
size distance is inserted between the
two).
The movement arguments (hmove and
vmove ) specify (with a Boolean value) whether the
Component should be moved (horizontally,
vertically, or both) to maintain the given distance to the
surrounding Frame 's edges; the stretching arguments
(hstretch and vstretch ) specify
whether the Component may be stretched to maintain
these distances.
class RadioButton
A RadioButton is a toggle button, just like
CheckBox , with slightly different appearance, and
with the difference that it belongs to a
RadioGroup . Only one RadioButton can
be active (have its on attribute be a true Boolean
value)in the RadioGroup at one time, so when one is
clicked or programmatically turned on, the others are
automatically switched off by the RadioGroup . Each
RadioButton also has a value
attribute, which should be unique within its
RadioGroup . When one RadioButton is
active, the value attribute of its
RadioGroup is automatically set to that of the
active RadioButton . The RadioGroup of
a RadioButton is set by assigning the
RadioGroup to the group attribute of
the RadioButton . Setting the value
attribute of the RadioGroup will automatically
activate the correct RadioButton .
class RadioGroup
See RadioButton above.
class TextArea
A multiline text-editing Component . Its text
is stored in the text attribute, which will be
modified (according to the MVC mechanism) when the component
loses focus. It also supports the Boolean editable
property, which may be used to control whether the user can edit
the text area or not.
class TextField
A one-line text-editing Component . (See also
TextArea , above.) If the enter/return key is
pressed within a TextField , the
TextField will send a
enterkey event.
class Window
A window, plain and simple. Window is a type
of Frame , so you can add components to it and set
its layout property etc. To make your window
appear, you must remember to add it to your
Application , just like you add other components to
Frame s and Window s:
-
win = Window()
app = Application()
app.add(win)
app.run()
Window s have a title attribute
which may be used by the operating system or window
manager to identify the window to the user in various ways.
5. Known Problems
For an overview of known bugs in the current release, see
the file KNOWN_BUGS found in the distribution.
6. Plans for Future Releases
For an overview of future plans, see the TODO
file found in the distribution.
7. Contributing
If you want to contribute to the Anygui
project, we could certainly use your help. First of all, you
should visit the Anygui web site at
http://www.anygui.org , subscribe to the developer's mailing
list (devel@anygui.org ) and the user's list
(users@anygui.org ), and try to familiarise yourself
with how the package works behind the scenes. Then, you may either
help develop the currently supported GUI packages, or you may
start writing a backend of your own. Several potential backend
targets may be found at
http://starbase.neosoft.com/~claird/comp.lang.python/python_GUI.html .
8. Anygui License
Copyright © 2001, 2002 Magnus Lie Hetland, Thomas Heller, Alex Martelli, Greg Ewing, Joseph
A. Knapka, Matthew Schinckel, Kalle Svensson,
Shanky Tiwari, Laura Creighton, Dallas T. Johnston, Patrick K. O'Brien.
.
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use, copy,
modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall
be included in all copies or substantial portions of the
Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
|