This is the development version of the documentation and should not be relied on until release 0.2a0. The documentation for each release is found in the distribution itself.
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, at anygui.org
.
Although the Anygui API is stabilising, it is not yet completely frozen.
This manual first gives you some short instructions on installing
Anygui. It then gives a thorough description
of how to use Anygui to build GUI
applications in Python. Following the
tutorial is a description of how Anygui
functions internally, and after that comes an API reference, which
describes all classes and functions available in the
anygui
package.
The Anygui package comes in the form of a
gzip
compressed tar
archive, a binary
Windows installer and as an RPM package. The Windows installer and RPM
package should be fairly easy to install. To install the
tar
archive, 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.2.tar.gz
If your version of tar doesn't support the z
switch,
you can do something like this:
foo:~/python$ zcat anygui-0.2.tar.gz | tar xvf
Another possibility is:
foo:~/python$ gunzip anygui-0.2.tar.gz foo:~/python$ tar -xvf anygui-0.2.tar
No matter which version you choose, you should end up with a
directory named anygui-0.2
.
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
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.2/lib
) there, or possibly place a symlink in
that directory to the anygui
package.
Once you have Anygui installed, you'll want to make sure you have a usable GUI toolkit. 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
later in this document.)
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.
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.
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()
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.
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.
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 *
Note that the namespace handling still works just fine:
import anygui.backends.tkgui as gui
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))
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.
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]
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 sync
method works the other way:
If you modify an attribute, you can call the sync
method
to keep the widget's appearance in sync with its state. When you
assign to an attribute, sync
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 sync
, see the section
"About Models, Views, and Controllers", below.
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)
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)
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)
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.
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(event): print 'Hello, World!' link(btn, greeting)
The event handling is taken care of by the call to
link
. An event handler receives a single
Event
object as its parameter. (For information about
these, see the API Reference, below.)
This section is currently inconsistent with the way things work in version 0.2. Will be rewritten shortly.
The Anygui MVC mechanism (based on the
sync
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 applications without using them (or, at least,
without thinking about 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 in the
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
sync
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.sync('geometry')
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.sync
to tell it to update itself.
When modifying either x
, y
,
width
, height
, position
,
size
, or geometry
in place like this, you
should supply the name of the attribute as an argument to
sync
, to avoid confusing Anygui. That is because these attributes mirror
each other in various ways, and if you don't supply a name, Anygui won't know which one you modified.
If you assign a value to an attribute, the sync
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
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 Model
s
generate an event (of the type default
) when they are
modified.
Example:
>>> from anygui import * >>> mdl = BooleanModel() >>> mdl.value = 1 >>> def model_changed(event): >>> 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 it with the new value.
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.
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)
The behaviour of a RadioButton
when it
does not belong to a RadioGroup
is not defined by the
Anygui API, and may vary between
backends. 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.
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.)
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).
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 attribute self.container
.)
To get more control over things, you should probably also
override the two internal methods internalAdd
and internalRemove
:
internalAdd
(self, *items,
**kws)
Should add all the components in items, and
associate them with the options in kws
, for later
resizing.
internalRemove
(self,
item)
Should remove the given item.
In this section I will give a broad overview of how Anygui works behind the scenes. This information will be useful if you're trying to understand the source code, or using the library in advanced ways. If you are an ordinary user, simply using Anygui to create relatively simple GUI applications, this section may not be for you.
The Anygui architecture is based on a division between the front-end, various back-ends, and, for each back-end, a native toolkit. The front-end is the part of Anygui you use in your GUI programs. It is designed to appear as a simple GUI toolkit, such as Tkinter or wxPython. The truth is, however, that the front-end does not implement any GUI functionality itself. It delegates all requests for such functionality to the back-end that is currently in use.
There are several back-ends to choose from. When Anygui is imported, an attempt will be made to import each of these back-ends in turn, in a given order (as described in the API reference later in this document). Once a back-end is imported without raising any exceptions (indicating that it can be used in the current Python installation), that back-end will be used.
Anygui is mainly a library of components, but for each component there are actually three objects at work: The proxy is the front-end object that is instantiated and manipulated by the application programmer, the wrapper is a back-end object that gives the proxy access to the currently used GUI toolkit through a simple interface, and the widget is the object that the wrapper is wrapper, an object from the GUI toolkit itself.
As an example, consider what happens when a Button
object is created with the javagui
back-end in
use. First, the application instantiates the Button
class, which is actually only a proxy object. This proxy will then
(automatically) make sure that it has a ButtonWrapper
from javagui
available when needed (that is, when the
Button
has been added to a Window
and the
application is runing). This ButtonWrapper
will, in turn,
manage a JButton
object from the java.swing
package. This native widget is completely hidden from the proxy, which
need not know which back-end is in use. The application, of course,
only interacts with the proxy object, and has no idea the back-end
even exists.
Each proxy object has a state dictionary, which is stored in the
attribute state
. Communication between the proxies and
the wrappers is done by using the push
, which sends
information from this dictionary from the proxy to the wrapper, and
the pull
method, which fetches information from the
wrapper and updates the state dictionary.
For instance, to update the width of a button, you might do the following:
btn.state['width'] = 100 btn.push('width')
In this example, the first line updates the button's state
dictionary. That in itself will have no effect on the back-end
wrapper, because it has not been notified of the change. The next line
calls the push
method. This is what is needed to notify
the wrapper (which can, in turn, tell the native widget what to
do). In this example, the name of the state variable
('width'
) is supplied. That is only an optimisation,
which tells the back-end wrapper which state variables it must
update. If no such names are supplied, all state variables are pushed
through to the native widget. The push
method of the
proxies simply construct a state dictionary with the given state
variables from the proxy's own state variables, and calls the
push
method of the wrapper, with the state dictionary as
the only argument. (This method is described in the section on
wrappers, later in this document.)
Luckily, the application programmer need not use this pushing
mechanism directly. It is automated through the use of proxy
attributes. When you assign a value to a proxy attribute (that does
not begin with an underscode, and that is not called
state
) it will be stored in the state dictionary, and
automatically pushed through to the back-end. So, for example, to
change the width of a button, you need perform one assignment, as
shown in the following example:
btn.width = 100
This assignment is equivalent to the two statements used earlier in this section.
The pushing mechanism is used to make the state of the GUI reflect
changes made to the proxy objects. However, as the user manipulates
the GUI, it may change directly, and thus foul up this synchronisation
between front-end and back-end. To correct this, the pull
method is used.
Again, both the proxies and the wrappers have pull
methods. And, again, the proxy version takes a number of attribute
names (possibly none) that are to be pulled out of the back-end, while
the wrapper version takes a state dictionary. Unlike
Wrapper.push
, however, the Wrapper.pull
method modifies the supplied state dictionary. It modifies only the
state variables that are already there it does not add new
keys. When you supply attribute names to Proxy.pull
you
restrict which keys are used in the state dictionary supplied to
Wrapper.pull
, and thus you restrict which parts of the
proxy that are updated. This is (as with push
) only an
optimisation; calling pull
with no arguments is always
legal.
So, if a user resizes a window, and you want to retrieve the correct width of a button, you can use the following two statements:
btn.pull('width') width = btn.state['width']
As with the push mechanism, this has been automated when you access state variables through proxy attributes, so the previous two lines of code are equivalent to the following:
width = btn.width
Alias attributes implicitly call push
and
pull
because they access proxy attributes behind the
scenes.
The following reference will describe the full official API of the current version (0.2) of Anygui but is currently under development.
Note:The environment variables may be
renamed, and their use may be deprecated. Use the
setup
function instead.
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_DEBUG
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.
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): ... print 'An event occurred:', event.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.
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 an
Event
object which stores 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.
The Event
object is simly a "bunch" object,
with each keyword argument stored as an attribute.
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. def click(event): source = event.source time = event.time print 'Button %s clicked at %f.' % (source.text, event.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
.
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.
Base Classes and Common Behaviour
All components are subclasses the base class
Component
, which means that they all share some
common behaviour.
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 container -- the containing 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.
The container
attribute will rarely be
set directly, but rather implicitly by using the containing
component's add
method.
Common to Application
, Window
, and
Frame
are 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.
There is a variant called rawSet
which does
not call sync
.
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) try
to use slice assignment to change the value (will work for lists
and ListModels
etc.); if that doesn't work, (2)
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 sync
method will automatically be called when you
use modify
.
There is a variant called rawModify
which does
not call sync
.
sync
(*names)
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
sync
method:
lbx.sync()
This method checks whether any attributes have changed,
and make sure that the Component
us up to
date.
Optionally, you can supply the name(s) of the attribute(s)
which have changed. Usually this is just an optimisation (which
may be used by some backends to avoid unnecessary updates), but in
one particular case it is very important: When modifying either
x
, y
, width
,
height
, position
, size
, or
geometry
. Component
s automatically
synchronise these geometric attributes, but since these depend on
each other, you need to specify which one(s) you have
modified.
For instance, the following will confuse Anygui and will raise an exception:
btn.geometry = [10, 10, 50, 50] btn.geometry[1] += 1 btn.sync()
The first line is fine, since it will automatically lead to
an internal call to btn.sync('geometry')
. The last
line, however, will have Anygui trying to synchronise
btn.geometry
, btn.position
, and
btn.y
without knowing which one was modified. It
refuses to guess, and raises an exception. This could be solved by
changing the last line to:
btn.sync('geometry')
Updating Automatically
The following section is not currently correct.
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
sync
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.sync)
Now, if we call lbx.items.append('fourth')
,
lbx.sync
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 following section is not currently correct.
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 sync
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.
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(event): print 'Handler 1' def handler2(event): 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
.
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
is the Placer
(see below).
Note: Other LayoutManager
s
will be documented later.
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.
The following components are planned as part of release 0.2.
The current API description is very tentative.
class Canvas
TBD
class ComboBox
box.items -- C.f. ListBox
class Dialog
TBD
class FileDialog
TBD
class Menu
menu.add(item) -- Add a MenuItem
class MenuBar
menubar.add(menu) -- Add a Menu
A MenuBar
is added to a Window
with win.add(bar)
. This will automatically set
win.menubar
to bar
.
class MenuItem
item.text -- The MenuItem text
class ProgressBar
bar.value -- Progress (from 0 to 1)
class StatusBar
bar.text -- StatusBar text
class Notebook
book.add(frame) -- Add a Frame
The Frame
s added to a Notebook
should have their title
attribute set. This will be
used as the text on the tabs of the Notebook
.
class Table
TBD
class TreeBox
TBD
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
.
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, Phil Cook.
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.