1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
|
The following was posted to comp.lang.python in November 1994:
James C. Ahlstrom jim@interet.com
Recently I have been working on a Python GUI Abstraction Layer. I realize
that other people are working on this, and I am posting this to see who
you are (please reply) and to relate what I found out so far. To keep the
length down I plan to followup later this week. I would appreciate any
criticism you may have.
The idea is that it should be possible to write the GUI part of a program
in Python, and have it run unchanged on several different GUIs using the
native Look and Feel. We could then look forward to complicated Python
programs which would run unchanged on Unix/Motif, Unix/Tk, MS Windows and
OS/2. Python is ideal for GUI work because it is a pure object-oriented
language.
Python would need to provide an interface which supports a rich set of
features available on today's GUIs in a way which could be easily ported
to all of them. Since each GUI has its own view of the world, this is not
easy. The way to do it is to read the Win32, Tk, XVT, SOM, Mac, and
Motif manuals in detail. Then think of what they do and express it in
Python in a natural and object-oriented way.
So, as an exercise, I wrote a short program which displays a resizable di-
alog box with two buttons. I designed the Python code to be compatible
with the systems I am familiar with, namely MS Windows, XTV (XVT Software,
Boulder, CO), and Tk. Regrettably, I know little about the Mac. I have
an actual implementation in Tk using tkinter (not Tkinter.py).
Despite the simplicity of the example, I found many problems. Once I port
it to XVT and WinNT I may find more. Here is the program:
----snip----
# This program will run unchanged on any system which has a "wpy.py".
import wpy # Standard module name on all platforms, but contents
# varies for Motif, Tk, MSW, OS2, etc.
app = wpy.App() # Represents the whole application. Not visible.
dialog = wpy.Dialog("Usual Hello World Demo") # A top-level window.
button1 = wpy.Button(dialog, "OK") # Two buttons. Look and feel
button2 = wpy.Button(dialog, "Quit") # will be different for
# different platforms and
# Make the button widths the same. # implementations.
button1.sizeX = max(button1.sizeX, button2.sizeX) # Buttons come with a size.
button2.sizeX = button1.sizeX
# Place buttons on 1/3 centers near bottom of dialog. The model allows
# an absolute pixel size/position, and a size/position relative to any
# rectangular object, default: the parent. Similar to Tk "placer".
button1.anchor = button2.anchor = "center"
button1.locY = button2.locY = 0.70
button1.locX = 0.3333
button2.locX = 0.6667
# Make function to be called when button is pressed. This button resizes
# the dialog to demonstrate re-size events. Dialog has re-size decoration,
# so user may resize dialog him/herself, and the layout will adjust.
dialog.sizeY = 0.4 # Parent of dialog is app with size of screen.
def OkFunc(self, event): # Standard form of event handler.
if dialog.sizeX == 0.4: # Just toggle between two sizes.
dialog.sizeX = 0.6
else:
dialog.sizeX = 0.4
dialog.SendSizeEvent() # Send a resize event to dialog.
# NOTE: You must generate a resize event yourself if your code changes any
# sizes. If the user changes a size by using the window decoration, the
# system generates the resize event. In general, all code is event driven.
button1.OnPress = OkFunc # Function to call on button press.
button2.OnPress = app.Exit # Use one of app's standard functions.
# Show dialog, start the application, respond to events.
dialog.Show() # All objects must be created with "show". "Show" also
app.MainLoop() # creates all child objects. "MainLoop" must be last.
----snip----
Here are the details on the implementation using tkinter. All source and
documentation is available from ftp.interet.com. It has been tested on
SunOS 4.1.1 and Linux. Keep in mind that it is a demonstration of concept
rather than something currently useful for writing programs.
The main Python program imports the module "wpy.py", which is meant to be
mostly the same for any platform. This module contains all the class de-
finitions and most of the logic for the window system. It contains its
own geometry manager for example. The Tk geometry managers are not used
because they are not available on other platforms.
The module "wpy.py" imports the module "wpyos.py" which contains the code
which is highly dependent on Tk. The module "wpyos" imports "tkinter" but
not "Tkinter.py". I am hoping that when ported to MS Windows etc., "wpy"
will remain mostly the same and only "wpyos" will be rewritten. If this
proves impractical, I guess we may as well just use one module. For any
platform, there will be a different "wpy" and "wpyos". I envision a
"wpyos.tk", "wpyos.msw", wpyos.os2", etc. with "wpyos.py" a link to the
correct version.
A basic question is whether to use "x = object.locationX" and
"object.locationX = 150" to get/set location, or whether to use "x =
GetLocationX(object)" and "SetLocationX(object, 150)". The first case
seems to need setattr/getattr to notify the underlying system to change
the size. This conundrum applies to any attributes of an object which may
be changed.
The design used relies on instance variables for most purposes, and events
for notification of changes. This enables us to write x=a+b rather than
set_x(get_a() + get_b()). There is no use of getattr/setattr.
The instance variables won't do anything until the programmer sends an
event to the relevant object. This seems natural (at least to me) for
geometry because all modern GUIs are event driven, and the user can change
the size and location of a top level window at any time by using the win-
dow decorations. In that case, an event "EventSize" is generated and must
be handled by the event handler "OnSize". Why not just let the programmer
generate events too? So widget configuration would be done by a large set
of instance variables, a smaller set of methods (functions) and
setattr/getattr only when all else fails. Most variables and methods
would be inherited.
Python events are generic and are not the same as X/MSW/OS2 events. Since
there is not much point to an object oriented language unless inheritance
is used extensively, event handlers are designed to be inherited. If you
do not like the geometry manager, just write your own and replace the On-
Size event handler with it. This works both for individual objects and
for classes of objects.
There is more documentation in the form of comments in the source code.
What do you think?
|