Status of WPY, a portable GUI module for Python

Presented at the December 1995 Python workshop by James C. Ahlstrom

What is WPY?

Wpy is a Python module which provides a class library, a message system and other tools for writing portable graphical user interface (GUI) code. You import "wpy.py" into your Python program and use it to write GUI code which will run unchanged on Unix/X using Tk, or on Microsoft Windows 3.1 and NT. Python is ideally suited to GUI development since it is a fast object-oriented scripting language with advanced data types such as lists and dictionaries. Code written with WPY runs with native look and feel on the supported platforms. WPY is free software published with the same licensing restrictions as Python itself (hardly any). It comes with 2000 lines of documentation and seven small demo programs.

Development of WPY started a year ago, and a paper presented at the May 1995 Python workshop described the WPY design objectives. Since that time progress has been made and lessons learned. Currently there is a strong WPY port to Windows NT and to Unix/Tk. The NT port also runs well on Windows 95, and will run on Windows 3.1 and 3.11 using Microsoft's win32s software. Unfortunately, the win32s system is slow, especially on the small memory systems which can not upgrade to Windows 95. A special Windows 3.1 port is needed. The NT port also runs on OS/2 using a prior version of win32s and OS/2's ability to run Windows programs, but the Windows 3.1 port will be a more satisfactory solution for OS/2 also. The Unix port runs on a stock build of Python with Tk version 4.0 and the tkintermodule.c module included with the Python distribution. All extra WPY support on Unix is supplied in Python in the wpy_tk.py module.

Native ports to other platforms have not been attempted. For the Mac, Microsoft publishes a tool to enable MFC code to be compiled for the Mac, and this should result in an immediate port, assuming a volunteer who owns the tool can be found. A native OS/2 port would be nice, but no volunteers have emerged.

WPY is not a port of something else, it is new code. It is a small simple system with a minimum of layers and a small footprint. On Unix/Tk/X, WPY code consists of the Python modules wpy.py (2247 lines, 76K bytes), wpy_tk.py (1527 lines, 53K bytes) and wpycon.py (607 lines, 14K bytes), plus wpy_tk.def (small). Currently no C code is used to support WPY on Unix. A stock linux binary of Python with Tk is about 900K bytes. On NT, WPY consists of wpy.py, wpycon.py and 12 C++ modules totalling 6412 lines. Python.exe for NT is 312 K bytes consisting of about 230 Kb of stock Python and 82Kb of WPY C++ support, a truly tiny footprint.

Programming Model

The WPY programming model is based on Microsoft Foundation Classes (MFC) with as few changes as possible. A program written for WPY is completely analogous to the equivalent program written in C++, and looks like C++ transliterated into Python. The exceptions (discussed below) are in the direction of a higher level model. More specifically, the following are the most important elements of any GUI programming model:

There are a few differences between WPY and MFC. The most drastic one concerns drawing to a view. In Tk, drawn objects (like text, lines, rectangles, etc.) are placed in a "canvas" (a view) and are assigned a number to identify them. They can be moved, erased, etc. In MFC, the user must draw with a drawing tool (DrawText(), LineTo(), etc.) into a "device context" associated with the view. The device context could also be associated with a printer, and this method provides device independent drawing. But in MFC objects do not retain their identity, they are just ink. To erase one, you would mark the area as invalid, and the MFC system would eventually call the user's OnDraw() method. The OnDraw() method must be provided by the programmer, and it must draw the document to the view. It is possible to write an OnDraw() method which draws the whole view every time it is called, but in practice MFC requires an OnDraw() which will draw only the area to be updated. This must be done to achieve acceptable scrolling speed and a quick re-draw if another window is moved out of the way of the view (an "expose" event in Tk/X). It is harder to write this more sophisticated OnDraw() method. Also, it is often more natural to think of the drawn objects as, well, objects. For example, in an html document, anchor text is a text object in a different color which can react to a mouse press. So the anchor text should be an object.

In the end, conformity with the Python language was judged more important than conformity with MFC. Python is too high level a language to pester the programmer with complex OnDraw() methods and expose events just to support decent scrolling. So the WPY drawing model more closely resembles Tk than MFC, although the MFC semantics are preserved. Specifically, the user must write an OnDraw() method which draws the whole view, just as in standard MFC. But the WPY system (on NT) will attempt to refresh the screen from its own record of the drawn objects, and will seldom call the users OnDraw() method. Also, the drawing tools return class instances which represent the objects being drawn, and WPY provides methods to re-draw the objects (for example, with changed text), and to find the object under a mouse click. These operations are supported directly by Tk, and on NT support comes from C++ code (for speed).

Current Features

WPY is currently at version 0.32, and is being actively developed. It is at the "incomplete but useful" stage. The available classes are:

Geometry Management

Consider a dialog box with a text message and two buttons, Yes and No. Geometry management means the way a GUI produces a layout for that dialog, that is, how you must specify the size and position of the controls. Geometry should be device independent so that the layout is valid for any screen resolution. Good geometry management makes using a GUI much easier because a considerable amount of programmer time is devoted to layout.

In Windows, layout is done with a resource editor which would display a picture of the dialog box and allow the user to move controls to the desired position. The resultant positions are expressed relative to a character size on the screen and are recorded in resource files. These resource files are incorporated into the program as binary data. Use of resource files is not possible within WPY due to its dynamic nature. MFC itself lacks any geometry management. A control is created with the C++ "new" operator, and then the Create method is called for the new control. The size and position of the control must be specified in pixels in the Create method. MFC does have messages which can be used to perform layout. When a window is created the OnCreate() method of the window is called followed by the OnSize() method. The size of the window is available when OnSize() is called. If the user resizes the window, another OnSize() message is generated. Positions of controls can be set within OnSize() and will be recalculated when required. For dialogs (the usual container of controls) only an OnInitDialog() message exists since dialogs can not be resized by the user.

Tk has two geometry managers available, the placer and the packer. The placer fixes the position of the control either in pixels or as a decimal relative to its window. That is, either as the point (40, 80) in pixels, or as (0.10, 0.20) where the decimal locations are relative to the window the control is in. The packer is more sophisticated. The packer locates controls around the edges of the window using up the available space as it goes along, and then sizes the window to be the right size to contain the controls. It is possible to add a border to controls to space them out from the edge. The ability to size the container window is especially valuable, since this size normally depends on its contents. But I personally find the packer awkward to use since controls are seldom really wanted at the edge of windows. Others find it quite convenient. In Tk, geometry is an attribute of the control, and controls have a border, an edge to be packed at, and a packing order. The Tk system maintains the correct location itself based on these attributes, and there is no need for an OnSize() message.

In WPY, controls have a two step creation process as in MFC. The object is first constructed from its class and then its Create() method is called. In WPY, the control has a default size in pixels when it is constructed even though the object does not exist in the GUI until it is created with Create(). This size is used to perform layout in the OnSize() method of the window or the OnInitDialog() message of the dialog. There a number of utility methods available which resemble the Tk placer, and which enable the programmer to specify positions in pixels, or in decimals relative to any other window or control. The size of a meter and of a character are available in pixels so that device independent layout is possible. To layout the above dialog example, create the following as a function within the dialog's OnInitDialog() method. First construct the message text as a static control. An aspect ratio can be specified for multi-line messages. Then construct the two buttons with their text. Call the utility WpyMakeEqualSize() for the two buttons to set their size to the largest one. Set the width of the dialog to 1.2 times the width of the text, and the height to 1.2 times the text height plus 3 times the button height. Then call either WpyPlace or WpyMakeEqualSpaceX to place the button centers at 0.333 and 0.667 within the dialog and 1.5 button heights up. Finally call Create() for each control.

In WPY, geometry is not an attribute of the control as in Tk, nor does it lack geometry management as in MFC. Geometry management is a function to be executed in response to a request by the system. The controls have a size and location attribute, but this only affects the control when it is created or when its Move() method is called. So geometry is set in a function created by the programmer making use of the known sizes of the controls before they are created. I personally find it easy to write these functions, but I have a mathematical background. Others may find it awkward. My feeling is that better geometry management is desirable, and that the current system is too low level. The good news is that all OS GUI interfaces are done, and a new geometry manager can be written all in Python independent of any particular platform. Or the fabled Visual Python layout tool could be written in WPY to perform visual layout as in Windows.

Design Issues

Normal MFC programs rely on "resources" for their menus, icons, window header text, and for other static text items. Resources are compiled-in static data, and so are incompatible with WPY which must interpret Python programs on the fly. Getting rid of all of MFC's dependencies on resources was the most difficult WPY design issue. Luckily there seems to be a progression towards less reliance on resources in recent MFC releases. See also another paper presented at this workshop on the method used by WPY to extend the MFC C++ classes in Python.

All significant technical issues now seem to be solved, and the "only" remaining problem is how to support the same GUI model on different platforms. To implement a typical new WPY feature, it is first necessary to figure out how to implement it on MFC. It is easiest to do that in Python after first making a few MFC calls available to Python in a C-language interface module in the usual way. The reason is that it is faster to program in Python, and programming in MFC is (at least for me) often an experimental science. There always seems to be multiple ways to do something, only one of which makes sense and works. After some experimentation, an understanding of how MFC "feels" about a feature is attained. Then the identical feature is written in Tk in Python scripts. Even though the Tk version will require more support because the Python feature will be phrased in MFC terms, the time required to implement an MFC feature in Tk is about the same as in MFC because Tk is simpler and yet quite powerful. After an understanding of Tk is acquired, it is time to design a sensible WPY feature which is simple, high level, elegant and easily implemented in both MFC and Tk. The implementation is split among the files wpy.py and wpy_tk.py, and some is in C++ for MFC. Because of Python's excellent C interfaces, the mix of C and Python is optimized for simplicity while keeping in mind that wpy.py is common to all platforms. If problems develop due to incompatible models, the abstraction level is raised and details are omitted in favor of simplicity. Finally a new WPY feature is available and documented, and a simple port to two platforms is available.

A typical problem would be Microsoft's Multi Document Interface or MDI. Their Single Document Interface (SDI) just means that there is only one window for the app, and that is not a problem. But MDI appears as a main window with multiple small windows clipped to be contained within it. Each small window has a menu, but its menu is displayed on the main window when the small window is active. If there are no small windows, a default main menu is displayed. There are also menu commands to rearrange the order of small windows. The basic problem here isn't technical, it is that the look of MDI seems to drive Unix people crazy, and they hate it. They are used to just plain one window, or just plain several windows when required. But access to MDI is required for Windows people. So if a programmer writes an MDI Python WPY app, how does it look on Unix? I have published a first and second try, but I am still looking for a third. Try demo05.py on Unix and see.

Generally I find MFC to be quite complicated, large, full of features, generally high level in window creation, and yet surprisingly low level in other areas. For example, classes generally have dozens of non-orthogonal methods, plus many other behaviors available from dozens more flag bits, yet MFC expects the programmer to worry about an excessive amount of low level detail. For example, MFC buttons do not provide their own checking nor do menu buttons. The search through all the MFC features looking for the right way to do something is often a long one.

On the other hand, Tk is a smaller, tighter, more sensible package with a better balance of size versus power. But it is consistently low or medium level in abstraction. For example, there is no concept of "window" nor how to create one. Instead you just make anything you want out of the basic widgets, and even then the anonymous X window manager will do something unknown with it anyway. This follows the traditional X-Windows approach of being vendor neutral and allowing the programmer to put the scroll bar on the left if he wants. But there is no way to enforce any user interface requirements. Still, Tk is an admirable job and it is easy to like it for Unix programming. Mr. Osterhout is working on a Windows version of Tk which will support the native look and feel of Windows, but I am on record as being skeptical that that is possible without a major change in Tk.

In summary, WPY keeps the good features of MFC and follows it closely enough to make it easy to learn, yet still looks natural in Python, protects the programmer from drudgery and presents a high level model. At least that has been the intent.

James C. Ahlstrom
jim@interet.com