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?