PAGE - Python GUI Builder

  INTRODUCTION
    Visual Tcl
    Basic Operation of Visual Tcl
    A Very Short Description of using PAGE
    Kind Words for Tcl
    The Use of Tix
    Status of PAGE
  Installation
    Installation on Linux
    Installation on Win32
    Testing the installation
  Using Page
    Using PAGE to build a Single Top-Level Application.
      Using Page
    Python console
    PAGE Preferences
    Selecting and Placing Widgets
    Widget Aliases
    Adding Balloon Help to Widgets
    Linking Events to Actions
    Bindings Window
    Defining Functions
      Toplevel Widget
      Menu Specification
      TixNoteBook Specification
      Checkbutton Widget
      Radiobutton Widget
    Running the Generated Gui
      The init() function
  Using PAGE to Build Applications with Multiple Top-Level Windows
    Python Digital Picture Album
    General Scheme
    Looking at p.py
  Rework
    Loading Python Files - More on Rework
    p2t
  Default Considerations
  Real Examples
    Vrex.py
  Final Recommendations
  Acknowledgements

INTRODUCTION

This is a short document about PAGE - a Python Automated GUI buildEr. Not a great acronym but I got three out of four letters at least. I don't think much of acronyms. I saw UNIVAC in the '50s and thought that was clever; ever since, I have regarded acronyms as imitations. The only other names that came to mind were guiPy and PyG.

I have felt that Python suffers from two serious admissions. One is an easily-used GUI-building tool. PAGE is an attempt to provide such a tool.

PAGE is a tool which helps to create GUI interfaces for use with Python programs. I find that I easily write small programs in Python but find adding any kind of a GUI is a bit of a strain. Python ships with Tkinter which is an interface to the Tk package. But there are several drawbacks to day-to-day use. First of all there is no real documentation shipped with the product. Recently, John Grayson's book "Python and Tkinter Programming", Manning, 2000 appeared and it does help considerably but there is a lot of good information there (>600 pages) that I don't have time to dig out on a continuous basis. I only want to process it once. The other problem is that there are no tools which will bury some of the Tkinter details. It is that tool lack that I have attempted to fill. Grayson's book made it possible. I recommend it.

To say it a different way. Python is great; it has been my favorite language for more than five years. But without having a better way of building GUI's, it is really missing something for it is then relegated to being a command line scripting tool. For me GUI's are the door to desktop applications. Hence, PAGE. I started off making PAGE do many parts of the job, including importing what is necessary, initialization, putting widgets in a GUI window, binding actions to widgets, building menus, and defining the functions for the application. All that is good, but I find that most of the time, I just need to layout widgets and get their attributes right. Part of that, of course, includes binding actions to widget events. That is, I use PAGE to help me build the GUI and generate code for the GUI. I then cut and paste code from the generated GUI into my application and begin modifying it. See the section below where I describe building a program for managing a digital photograph album. Building menus can also be confusing and PAGE helps to do that. But again, when the pattern is seen, they can be easily modified.

PAGE is not an end-all, be-all tool, but rather one that attempts to ease the burden on the Python programmer. It is aimed at the user who will put up with a less-than-most-general GUI capability in order to get an easily generated GUI. It is a helper tool. It does not build an entire application but rather is aimed at building a single GUI window.

The building of an application is really a combination of two coding activities. One is writing the code which creates the GUI, the combination of supporting widgets which comprise the visual aspects of the GUI, i. e., the stuff you actually see on the screen. Part of this is to generate the links between program units and the selection of buttons, menus and so forth. The other part is the organization of the application and the coding of Python functions which implement it. Tools like VB and to a certain extent Visual TCL attempt to do project management. They try to keep track of numerous program modules and their interaction with the computer environment. PAGE leave all that to the user.

In all its glory, PAGE assists in doing both of these functions as has been described above. However, as time goes on, I have tended to use page more and more for the first category, and to use more conventional means to do the other stuff. To express it another way, I have always found it more mysterious and forbidding to lay out the windows and the widgets, and getting their attributes right, as well as binding actions to widget selection rather than to code the actual python functions which animate those windows. In the Photograph Album example below, I mainly used PAGE to create the several top level windows and then used 'cut and paste' of the generated python code into the application. Then seeing the code pattern of windows objects, I wrote and debugged most of the functions using 'standard' python development techniques. For me that means emacs and emacs support packages for python.

I found that the desirable code segments for inclusion in the base application were (1) the top-level window object definition, (2) the code to create the top-level window object, and (3) code to destroy the top level window object. That observation led to changing the code generator so that it would include templates for creating and destroying the top-level window object.

Let us consider some X window definitions and concepts in order not to be confused by the terms used here, including top-level window. Some of this information is taken from "Tcl and the Tk Toolkit" by john K. Ousterhout. "The X Window System provides facilities for manipulating windows on displays. ... Each screen displays a hierarchical collection of rectangular windows, starting with a 'root window' that covers the entire area of the screen. The root window may have any number of child windows, each of which is called a 'top-level window'. An X application typically manages several top-level windows, one for each of the application's major panels and dialogs." One can also speak of an application's 'root' top-level window. This is the terminology from Grayson's book. It refers to the main application window that is opened or created when you initialize tk. Sometimes only the one window is needed. The photo album example below uses several top-level windows and is probably the more common case.

In building a multi top-level window example, I recommend that one use PAGE to build the 'root' top-level window and then use the corresponding generated Python module as the basis for the main module of the application. When one needs additional top-level windows, use PAGE to specify them and cut and paste code for the window object as well as the templates for creating and destroying them in either the main module or new modules which are then accessed through the 'import' statement. When PAGE generates Python code, it also includes a bunch of boiler plate for a header which invokes Python, imports Tkinter and Tix, initializes and creates the main window. This boiler plate in not needed for other top-level windows in the application. But you do want it for initializing the root window for the application.

When one feels confident in using PAGE, he looks at the code patterns generated by PAGE and feels confident in modifying the code for effects not anticipated in PAGE. The thought behind PAGE is that there is a large and intimidating body of information needed to start building a GUI. PAGE encapsulates that information and produces working code. At that point the user can see and understand the code patterns and can easily customize the code. You can see some of that in the Photograph Album example included.

PAGE is based on Stewart Allen's Visual Tcl program. In fact, it is vTcl, with a bunch of problems corrected and an additional module that generates Python code. And yes, it is written in Tcl! Why not? Tcl is a good program to have installed on your machine because there are a lot of good programs written in that language. Tkinter is the most common graphical interface used with Python so Tcl/Tk is needed anyway. Tcl seems ideal for implementing this program and since Tcl/Tk is needed to run the GUI anyway, there is little additional software support required. Further, it leaves the door open to use the same techniques for GUI builders in other languages which support Tk. The disadvantage of using a Tcl implementation is that one is restricted to widget sets which are available to both Tcl and Python. That seems to mean Tix or Iwidgets and I had a problem with Iwidgets. Thus one cannot use BWidgets or Pmw. At some point soon, the program should be translated to Python.

Visual Tcl

I was looking at different GUI generators for Tcl/Tk with a view of converting the output of one to Python when I came across Visual Tcl and the work of Constantin Teodorescu in generating Java. I thought if one could automatically generate Java, it should be easier to generate Python.

I believe that Stewart Allen wrote Visual Tcl in the 1996-1998 time period to aid in the development of Tcl/Tk applications. Constantin Teodorescu modified vTcl to generate Java code using SWING as the widget set. It was looking at the Java generator that convinced me to try doing this. Let me say that Visual Tcl seems to be very well written. I was able to discern its behavior in spite of the nearly total lack of documentation. When I needed to do something I usually found an existing procedure.

I did change a number of things in Visual Tcl which I think were bugs and there are several things that I ignored because I couldn't figure them out. I ignored all that stuff dealing with compound widgets because it is not my desire to build compound widgets using PAGE. I am getting all my compound widgets from the Tix package. I ended up making so many changes to get Visual Tcl working properly that I have stuck with an early version of the code. I intend to review that decision in the near future.

The Mode switching from between TEST and EDIT has no meaning for PAGE and has been removed from the program. The Tcl interpreter is good, but it can't handle functions written in Python. So you can see that I used only what I could figure out and exploit, and tried to leave all the other stuff intact.

Finally, I support only the placer geometry manager. In the past, I have worked with Visual Basic and the placer geometry manager seemed comfortable and got me results that I wanted. (As far as VB is concerned I find the V almost incredible and the B really putrid.) As further justification, Constantin Teodorescu also limited the Java generation to the placer geometry manager. It seems really natural with the drag and drop paradigm. One disadvantage is that some of the sizes could be wrong if fonts change. This is because several Tk widgets have sizing based on font parameters. This has implications if the GUI is generated with different fonts or resolution than are active during execution. There may also be problems if the generated GUI is moved to other platforms or environments. The more recent work by Mike Clarkson on the new WmDefault package relieves many of those problems. This is discussed further below.

I find as time goes on I have less and less patience with programmers who fail to document their work. I had problems understanding how Visual Tcl is supposed to work in several areas including the menu editor and the binding window because user documentation is inadequate. Copy-and-paste, undo-and-redo have problems that might not have occurred if the code had been blessed with a sprinkling of comments. Visual Tcl has a whole capability dealing with a "Command Console". I was never able to figure it out, so suspecting that it would only help with debugging Tcl, I dropped it.

Basic Operation of Visual Tcl

Obviously, I used only the basic operations and this is what they are: One creates a window and selects a widget type from the tool bar. A widget of that type then is instantiated in the window and placed in the upper left hand corner. One then uses the mouse to put the widget where it is wanted and to size the beast correctly. To modify other attributes there are a set of windows tailored to editing attributes. The main one is the Attribute Editor for changing attributes such as color, relief, labels, text, etc. Another allows one to add supporting functions and there is one for specifying widget bindings.

If you wish to change a GUI by opening in Visual Tcl an existing tcl file, Visual Tcl will source the specified file and change all the bindings for all the widgets to allow things like resizing, selection, deletion, etc. I never figured out how to manipulate bindings for itcl widgets and so I have excluded them from my consideration. I tried a question to the itcl newsgroup, but never got an answer. The key to the operation of Visual Tcl and PAGE is that the mouse bindings for the widgets can be changed so that you can drag them around, resize them, bring up special purpose menues, etc.

There is a tutorial text file for Visual Tcl in the doc directory. It is not very thorough but go ahead and read it. Look at the examples and then come back to here. Note that the tutorial guides you thru the generation of an entire Tcl/Tk project rather than just how to build a single GUI window as is the intent with PAGE, but it will give you a feel for the intent and use of Visual Tcl to construct individual windows.

One of the tutorial examples has the user building an example with two interacting windows. I use PAGE for building individual windows to be used by an encapsulating program. So there is a bit of emphasis change involved. I don't want to write my whole program using PAGE, just a GUI file that I import or paste into the real application. That is, the basic function of PAGE is to facilitate the creation of individual GUI windows. It allows one to select widgets from a menu and place them in the window using the mouse. One can then modify the attributes of the widget, such as location and size, with the mouse and modify the other attributes with the Attribute Editor. Its operation is similar to that of VB. It is not meant for the creation of extremely sophisticated graphical presentations. Rather, it is for people like me; I find that Tkinter represents a body of knowledge that is difficult to acquire, retain, and keep at my finger tips.

A Very Short Description of using PAGE

A user begins by invoking PAGE using a script, 'pg' in linux, and selects File->New which opens a window, a toplevel widget, entitled New_Toplevel_1. This is the GUI window. In this manual, I use the terms Button-1 and Button-3 rather than left button and right button because a user may be using a left handed mouse.

  1. Step 1. Using Button-1 on the mouse drag it to where you want it and resize it appropriately. (For many of the Tix complex widgets, one has to press the control key along with Button-1 to select the entire widget. Go to the attribute editor and change any of the attributes that you want.

    Believe it or not, Step 1 is pretty much it. To add another widget select the parent widget with Button-1. (Many times it will be the top level widget.) Then select a widget from the tool bar and a small version of it will appear in the upper right corner of the parent widget. Then go to Step 1.

    Of course, there are widgets that allow you to do more and there is the complication of building menus and binding events to program actions that will be discussed in later sections as well as some of the specific attributes of different widgets. Many of the Tix widgets have subwidgets which have their own properties, so one may find it desirable to select the subwidgets and modify their properties.

  2. The remaining step is to generate the Python code. This is done by selecting the Toplevel widget with Button-1, pressing Button-3, and finally selecting the popup menu item 'Generate Python'. Another way to generate the python code is click on the 'Gen_code' in the menu bar of the main window; that will give you the same menu item 'Generate Python'. You will see a new window, the Python console where the code can be expected to appear. Save the code.

Kind Words for Tcl

This project brought me back to Tcl/Tk. This section tell why Tcl is a great language and even suggests to the Python community, that there are a number of very good things in Tcl that would be great additions to Python.

In building PAGE, I rekindled considerable respect for Tcl. About five years ago my company had a very bad experience involving an exceptional programmer who built a very complex program using Tcl extensions cascaded upon Tcl extensions. The last extension in the chain was his own. He then left the company and the whole structure collapsed.

This project has elevated Tcl in my esteem. All the primitives that I needed were available. Some touches are nice even in an unintended way. For instance, Visual Tcl adopted the convention of starting every procedure name with 'vTcl:' thereby inadvertently avoiding any possibility of name clashes with Python since ':' is not a legal character of a Python name.

The mechanism for instantiating widgets is to generate tcl/tk code and execute it on the fly. Very nice indeed.

Basically, all I had to do was write a few pages of code in one module to process a list of widgets known to Tcl/Tk, extract their attributes using the 'configure' primitive and put out calls to Tkinter. One can trivially tell whether an attribute differs from the default or not. Similarly, the 'bind' command gives access to bindings set for widget events. It was easy to deal with subwidgets and parent widgets using 'parent' and 'subwidget' primitives. The Visual Tcl implementation contains a well designed global data structure using a small number of associative arrays which make it easy to add new widgets and manipulate widgets. Again, I found it very easy to understand and manipulate in spite of a nearly total lack of documentation.

The one aspect of the Tk implementation that truly surprised me was that when a Tcl file is invoked by 'source' the syntax of a proc body is not analyzed. This means that it is possible when using Visual Tcl to specify functions with Python syntax or any other language. You can then save the file as a Tcl file and source it. For me that meant that I could generate an interface in Visual Tcl including support functions written in Python and generate a Python program, go off and debug the generated program including the Python functions and then go back to Visual Tcl to update the interface and generate an updated Python program which preserved my changes to Python functions. All I had to do was write a very small utility to transfer the functions from the Python program back into the Tcl version.

Here is an example of a Tcl procedure with Python syntax:

proc {self.list_handler_2} {} {
def list_handler_2(self, x):
      index = self.lbox.curselection()
      greeting(index)

The proc statement seems to be enough to get thru the 'source' operation so that the 'body' command will yield:

def list_handler_2(self, x):
      index = self.lbox.curselection()
      greeting(index)

At first, I thought this must be an old version of Tcl before they put in a compiler. Later, I came to realize that this behavior is the consequence of the defined behavior of the Tcl 'proc' command. If so, then the approach that I took to generate Python interfaces could be used to generate code for any language that support interfaces to Tk.

In the binding of events to Python code, the conical form for the command is

lambda e: function(arg, ...)
When a tcl file is sourced with this command, the corresponding event may occur and an error message will occur because Tcl does not know how to deal with lambda .... However, it was possible to add a null proc lambda to get over that hurdle. All of the stuff following the 'lambda' is just a series of string parameters which can be ignored.

I like the fact that there are many commands which with a proper argument string will commit a value and with fewer arguments will query status.

The power of Tcl/Tk makes it unnecessary to build a data base for the widgets and their attributes and bindings or worry about default settings. Tk does it all for you. It has handy primitives like 'widget configure' which coughs up all the attributes and their defaults for filling in the Attribute Editor fields so that the author only had to build a framework for constructing the Attribute Editor to take advantage of the Tk facilities. When the window/widget manipulation is completed Visual Tcl generates a true Tcl program. If you later want to modify the program all that is necessary is to 'source' the generated file and you are ready to carry on - an essential feature. When it comes to emitting code for functions, the 'body' primitive provides input token stream for the proc including '\n' characters. Event binding are easily handled with the 'bind' command.

It is very nice to create a variable or procedure name with string manipulation operations and then access the created name. This is a common thing to be able to do with an interpreter but is not discussed in Python. At least, I have never found it. I can do it, but it seems pretty convoluted.

With Tcl it is possible to explicitly test for the existence of a variable or a procedure using the 'info exists' command and that is very nice. I wish that were in python. I know it can be done with the try - except construct, but that seems rather expensive in terms of key strokes and execution time when compared to calling a builtin function that does not invoke the error handler overhead.

Tcl is one of many languages with the ability to set and test a variable in one line; it's great and missing in Python. Tcl makes it trivial by having a set command which effects the assignment and returns the value assigned.

Switch statements: Where, oh, where, are they in Python? For a language that stresses code clarity, the lack of a switch statement seems a real shame to me. I have seen the recipe 1.6 in the Python Cookbook. That recipe along with 1.9, which shows how to set and test of a variable in one line, certainly read like a parody on language design.

Tcl has arrays which behave like Python dictionaries only better because it more naturally provides multidimensional indices.

Since all the commands in tcl behave like functions, adding, renaming, and overriding are trivial; doesn't seem possible in Python. Since assignment ('=') is not treated like an operator, there is no __equals__ to be modified.

Tcl really makes hay from the presence of the $ operator which allows one to speak about both the name and the value of a variable. You see important consequences everywhere in the language, especially when compared with a language like Python which is missing that capability. It seems related to the __equals__ problem above, in that an __equals__ function would be hard to write without call-by-name in the language.

Lest you infer that I think Tcl is perfect, I feel that I would have gotten PAGE working quicker if Tcl had a source level debugger. I am always disappointed when language designers neglect to build in a decent debugger. This is especially true with interpreted languages where all the facilities are there for the hooking in a debugger. (I have had a similar conversation with Guido and may turn my attention in that direction next.) The only explanation that I can think of is that the authors are much better programmers than I am. That is certainly true of Ousterhout and van Rossum. In a similar vein, the Tcl trace back could be more easily interpreted by telling the user file and the line number from the top of the file and not an offset from a branch point within a proc. I am willing to believe that the interpreter makes the sort of thing I want harder, but that's what computers are for. Of course, Don Libs has put a debugger in expect but it has never been picked up by the Tcl folks.

(I have just tried the TclPro debugger which recently went Open Source. Two interesting points with the TclPro debugger are (1) the debugger is slow. It's first step is to instrument the tcl modules constituting the program. It seems to involve a syntax analysis which probably takes half a minute or so with Page. Also, (2) the debugger doesn't work with Page, because the generated tcl modules contain functions with Python syntax. That's OK with the tcl interpreter because there is no attempt to execute them. However the TclPro debugger assumes that when such a file is 'sourced' the functions will be executed so it does an analysis and barfs on the Python routines and halts. I think that is another indication that a debugger should be built into the tcl interpreter.)

The other thing I would like to see in Tcl/Tk is the adoption of more widgets. Tix, BWidgets, Pwm were all developed in answer to the same problem - the paucity of Tk widgets. Once shown the way, the Tk folks should have included them.

I found "Practical Programming in Tcl and Tk" by Brent B. Welch very helpful.

The Use of Tix

I certainly wanted a wider variety of widgets than the standard Tk widget set and for the reasons above I needed a package which was available in both Tcl/Tk and Python. That came down to Tix which was written for Tcl/Tk and PyTix which is a Python wrapper for Tix. What I attempted to do was to add a subset of tix widgets to those supported by Visual Tcl. In particluar, I wanted the tixLabelFrame, tixLabelEntry, and the Scrolled widgets. In addition, I wanted tixMeter which is a progress bar and the tixNoteBook. I also include capability for balloon help. Others are welcome to include other widgets if they are so inclined. Since Python includes things like file selection widgets and dialog boxes, I skipped over those Tix widgets for PAGE.

I had problems using Tix. For a while it seemed to have disappeared from the web, however, it turned up again on SourceForge.net. I spent much time getting many features of the TixNoteBook working. It seemed like a really nice widget, one that would conserve real estate in a GUI, so I persevered. I figured out a method to allow the PAGE user to change the number of pages in the tixNoteBook. Another widget I really wanted was a progress bar which is implemented in Tix, even though it's absent from the overall man page. Even worse, it was missing from PyTix package; I added it to PyTix. Later, the Meter was added to Tix.py and really simplified installation of PAGE. The meager documentation in Visual Tcl suggests that Tix plays undesirable games with fonts, colors, and so forth. Fortunately, this has been change in the latest version. I modified Tix to avoid the problem through the use the TkoptionsDB file. In the 2.2 version I have abandoned that artifact because Tix now uses the WmDefault package which makes a Tix program more nearly portable across environments. There is a later section which discusses defaults and the desirability of using them. I would have liked to include the tixPanedWindow in PAGE but I was unable to get it working correctly. However, some of the problems which I experienced may have been resolved in more recent versions of Tix. But it will have to wait until my next release.

I have come to be a real fan of the WmDeaults package. It works great with different color schemes under KDE. In fact, I now think that the WmDeaults approach is a great reason for using Tix as opposed to other widget extension packages or even just vanilla Tk.

Status of PAGE

PAGE supports many of the Tk widgets and many of the Tix widgets. The other widgets seem to be of lower priority to me. For instance, hooking up scroll bars to widgets confuses to me so I skipped it in favor of Tix scrolled widgets like scrolled list box, scrolled text box, and scrolled tree. (I really like trees. Use them all the time!) There seems to be no need for both horizontal and vertical scales since they differ by an attribute. There is the TixOptionMenu widget which is a mystery to me, I implemented something but I am convinced that it is incorrect.

In deference to my tired old eyes, I have enlarged the icons on the tool box of those widgets that I handle.

As mentioned above the TEST mode doesn't make much sense in a Python context and I have totally ignored the Compound menu. Also, I have done nothing with the Options menu other than play with the Bindings choice and I may have that wrong. I prefer to have aliases set within the attribute editor. You can now do it either way.

You may notice that the File menu is augmented to include an 'Open Py' choice. I will get into that later, for now it suffices to say that this is related to rework. I regard rework as a very important aspect of PAGE. This is an aspect of PAGE that is under constant review.

I have put a fair amount of time into providing the tixNotebook widget. Most of the desired function works. The pages inside the notebook appear to be frames but one cannot utilize all of the options of a frame. For instance, one cannot change the color of an individual page or of a tab.. I believe that to be a limitation in Tix.

I would like to have supported the paned window, but I had problems. I don't think it is implemented correctly with regard to the placer geometry manager. So it is not included in this release.

I have 'added' new attributes. Tk, for instance, has a special attribute for specifying the action to be taken when a widget is selected. It is called 'command'. I imagine that they treat it differently from other actions because so many GUI operations depend on selecting a widget with Button-1. I felt that there are many widgets which require loading like list boxes, text boxes, etc., that I added for those widgets a 'loadcommand' attribute. I also added 'bindcommand' before I fully understood the Bindings window which is a much more general mechanism for adding bindings. Seeing that the Bindings window provides all of the function needed, I removed the bindcommand attribute and I probably should remove the loadcommand attribute as well. Consider it deprecated.

There is now a utility p2t.py which facilitates rework. It is discussed in a later section. I have not worked on p2t.py for a long time. I think I shall give it up or do something else.

I have found it necessary to give up on the implementation of the TixOptionMenu widget. It is another case where the Tix documentation leaves me unable to determine just how it is supposed to work. So I can not fix it.

Installation

Installation on Linux

Python 2.2 included Tix support as well as Tcl so all that one needs no alteration to the Python installation. You do require a full installation of Tix 8.1.4 because PAGE is a Tix program and as such demands a working version of tixwish. And building that requires a complete Tcl installation. It is necessary to have these particular versions of Python and Tix and you certainly want and 8.4.5 or later for Tcl/Tk. I have just installed Mandrake 9.0 and everything I need for running PAGE is there. Now the recommended release of Tix is Tix 8.1.4.

The distribution is a single file, page-2.3xx.tgz. Just untar it in your home directory. All files will be put in /page. Put /page in your PATH environmental variable.

The entry module for PAGE is /page/page.tcl. A Unix shell script 'pg' invokes the program. Be sure to check that the values of the two variables in that script reflect the proper locations. To start the program, execute the command 'pg'. If you supply a file name as the one allowable parameter, that file will be opened. If you are running under Windows, use the bat file, winpg.bat.

inc.py is a little utility that takes the functions from a '.py' file and stuffs them into the corresponding '.tcl' file. It is incorporated in the 'Open Py' file menu pick.  Just let it lie in the install directory. I hope to replace this soon with a less confusing alternative, p2t.py.

The steps for installing PAGE are:

  1. Untar the distribution file in your home dpage/doc/irectory. You can probably use 'tar zxvf pageXXX.tgz. This will put all the distribution in /page.
  2. Be sure to set the necessary environmental variables like PATH and HOME.
  3. Check the variables in the pg script.

Installation on Win32

Installing under Win32 is a bit easier than in previous releases. See Installation on Win32 for the gory details. The main points are to install recent copies of the programs upon which PAGE depends, execute page-2.2.exe and to set a couple of variables in winpg.bat.

Testing the installation

In the examples subdirectory of PAGE there are two files which can give you some confidence about the installation of the various necessary software components. After you have everything, Python, Tcl, Tix, and PAGE, go to the examples subdirectory in the page installation directory.

In the examples/tix subdirectory try running tixwidgets.tcl. The command is:

      tixwish8.1.8.4 tixwidgets.tcl

under linux or

      tix8184 tixwidgets.tcl

under Win32.
If it works, you will see a very nice Tix window with a notebook widget which demonstrates most of the Tix widgets in the different pages. If it doesn't work you better check your Tcl/Tk and Tix installation and path specification. No point in going on if it doesn't work.

The next test is in examples/PyTix. Here try:

      python tixwidgets.py
The result should be very similar to the previous test example. Your are now ready to go on and seriously try PAGE.

Using Page

There are two ways to use page. One is to use PAGE to build a more or less complete application. That is, to use PAGE to build the whole structure including imports, global variables, supporting functions as well as laying out a window. This usage is described directly below.

The second way is use PAGE just to build a top-level window and then use your favorite editor to pull out the necessary bits and pieces from the generated code for inclusion in another Python program. This usage is discussed below and a fairly extensive example is shown (A digital photograph album program). I am doing things this way most of the time now.

Using PAGE to build a Single Top-Level Application.

This section attempts to describe the full functionality of PAGE when building an application with a single root window. It will yield a Python module which implements the application.

One uses PAGE to generate a GUI as follows:

Using Page

Start PAGE by executing pg on Linux, winpg.bat in a DOS window, or activating the PAGE icon on the windows desktop. The scripts may specify a filename. That file is usually has an extension of '.tcl'.

The way I use PAGE is to build a GUI module in Python that is very sparse and import that module from my main application module. The structure of the GUI module is very highly stylized. The PAGE package contains a fairly extravagant example, t1.tcl, in the example subdirectory. As I was building PAGE, I added all the widgets to a single window. There are weird colors and font sizes to verify that I was able to change attributes of those widgets. Bare in mind that later and for good reasons, I will recommend that one not play much with custom colors or fonts. A sanitized version of t1.py is t3.py. t1.py is still interesting because it shows a bit more that can be done even if that is not always a good idea. t1 and t3 can be found in the examples subdirectory. t3 is really the gee whiz example that you should see because sanitation included taking full advantage of WmDefaults.

To try t1, start PAGE, File->Open and select t1.tcl. Select the top level window and depress Button-3 over the window and select Generate Python from the popup menu. You will get the generated code which should be the same as t1.py. Caution: I suggest that you move the examples from the examples subdirectory of the distribution to another directory before playing with them because PAGE and p2t are likely to rewrite them.

To execute the generated Python module, just click on the Run button of the 'Python console'. If you click on many of the widgets and menus things will happen to show that there is a connection between the widget and the program. If you look at the code generated you will see various things to try. One to try is typing 'q' into the plum colored entry box. Stop the cursor over the scrolled listbox (the green one) to see an example of balloon help. Go through the pages of the notebook and press the demo button in the last page.

Python console

When the user generates the python module as described above the Python console window appears with the generated code inside the upper text box. It has the following appearance:


The upper text box contains the generated text with a bit of colorization applied. The lower text box contains any generated output from running the source program. The Python Console is a tixPanedWindow which allows the separator between the text boxes can be moved with the mouse.

There are several button along the bottom of the window. They behave as follows:

The upper text box can be modifying by selecting a character position with the mouse and typing. Such changes will be save with either the Save or Run buttons. You can click in either text box and then use the PageUp and PageDown.

As you can imagine the colorization is not fancy. Keywords, comments, and strings are set to distinct colors. The defaults are set to reasonable colors for light backgrounds. If you add some stuff that goof up the colorization then try pushing the 'Colorize Python' button. One place you may have trouble is with quotes that bracketed by ''' or """. You should start a line with one of these delimiters and end a following line with one.

PAGE Preferences

Originally, the file menu from Visual Tcl had a Preferences option which allows you to change a number of options. After using the system for some time, I decided that the preferences really do very little that I found useful and a lot that I found quite confusing or which applied to Tcl/Tk only. So I removed the whole Preferences capability. Comments welcome!

Selecting and Placing Widgets

The starting operation is to select the toplevel button from the Widget Toolbar or select File-<.. This is the window that you are building for your GUI. Use Button-1 to position it on the screen and to size it appropriately. (Most documentation talks about left-mouse-buttons and right-mouse-buttons but I am left-handed and Button-1 is really on the right and right-handed people expect it to be the left button.)

Select the top-level window with Button-1 and then select the widget you would like to put in the top-level window. It will magically appear in the upper left hand corner of the top-level window in miniature form. Again, use Button-1 to resize it and to move it to the desired location. When you select a widget, the Attribute Editor window will display all configuration options available for that widget and display the default values of those widgets. I think it is rather clear how to modify most of the options.

Things get more interesting when it comes to placing Tix megawidgets because they are collections of widgets and clicking Button-1 in the area of the widget may select any one of the contained widgets. To move or resize a Megawidget it is necessary to grab the outer or main widget. This can be done by pressing Control-Button-1.

The final point about placing widgets is that some widgets are container widgets such as frame widgets and TixNamedFrame. You are allowed to place widgets inside of these widgets. That is done by selecting the container widget with Button-1 and then selecting the desired widget from the Widget Toolbar.

Again, when you want to drag a Tix widget around or resize it, you have to be careful to get the top of the Tix widget. For instance, if the mouse is over the center of a TixScrolledListBox when you press Button-1 you don't select the TixScrolledListBox you select the listbox subwidget. Now that's fine if you want to change one of its attributes that you cannot change from the TixScrolledListBox attribute list. But it is not what you need to have selected to move or resize the TixScrolledListBox. To select the TixScrolledListBox move the mouse over any part of the TixScrolledListBox and press Control-Button-1.

Specifying and modifying the the attributes with PAGE is very easy. All one has to do is to select the widget with Button-1 and then 'go to' the Attribute Editor and modify the attributes as desired. One can easily change the colors, fonts, labels, text, relief, and many that I don't even recognize. Obviously, all of the attributes are described in either the Tix or Tcl documentation.

There are different ways to change attributes. Where there is limited list of choices, the change is selected from a drop down list. For colors one can type in the X color name, RGB in hex, or hit the little button next to the field and a color selection window appears. Labels and text fields can just be typed in. Where a command is specified, it can just be typed in or the button with ellipsis can be selected and a text window opens where the command can be entered. More on specifying commands later.

Keep in mind: The generated Python module does respect the TkoptionDB file. So the executing Python program may appear slightly different from the PAGE template. In Tk many dimensions are in units of average character widths and thanks to Tix the PAGE fonts are of limited variation in size. So if you have specified a large font size in TkoptionDB, some of the widgets will be larger. Also, Tix will limit the color scheme so the window background will change from the Page template to generated GUI execution. Again, by using my TkoptionDB for both PAGE and the application encapsulating the finished GUI, I avoid both problems. As will be mentioned several places in this guide, the recent addition of WmDefaults to Tix is great and avoids many of these problems; I have gotten rid of my TkoptionDB file.

Widget Aliases

It is sometimes desirable to use the name of a widget in a bind command or an procedure. The names assigned to a widget placed by PAGE can be rather complicated. I don't like going thru the listing looking for the generated name for the object such as, self.tix29_listbox, so that I can type it elsewhere. I use an alias which may be simpler to remember. So, as I am specifying attributes for the TixScrolledListBox, I also specify the alias should be 'list_x', the following line of code is generated:

self.list_x = self.tix29_listbox

allowing me to write the event handler as:

    def list_handler_2(self):
      index = self.list_x.curselection()
      greeting(index)

Visual TCL as I got it allowed you to 'go to' the Options menu and select Alias. At that point a window opens up to receive the alias. I changed the Attribute editor so that you can enter or change an alias there as well. Partly, I did things like that to convince myself that I was following what was going on.

I believe that the alias function is subsumed by the '%me' construct that is included. One can specify the current object as '%me' and that constructed will be converted to the name of the current object. This is exemplified in a later section.

Currently, a widget object can have only one alias.

Adding Balloon Help to Widgets

Balloon help can be very useful and Tix supports balloon help. To specify balloon help for a widget or a menu button merely select that widget or button, hit Control-Button-2 and a window will pop up allowing you to add the help phrase. PAGE does not support putting balloon help into a status bar, nor to attaching balloon help to a menubar entry.

Linking Events to Actions

The point of building a GUI is to link actions (the execution of specific code) to some event within the GUI like selecting a button with a mouse key, typing a particular character into a text field, or creation of a widget. While Visual Tcl and Tcl allows one to specify a block of code, one should stick with a one liner in Python. One liners can be the name of a function. Just the name without a parameter list. This will effect the function call will a null parameter list. The other possibility that I frequently is use a lambda expression to call a function and pass a parameter list.

As mentioned a couple of times, there are two special attributes, command and loadcommand related to event-action interactions. Tcl/Tk supports the first and I added the second. Command is the attribute that defines the code to be executed when a widget is selected with Button-1. To specify that command, one selects the widget and then goes to the attribute editor and locates the entry filed labeled 'command'. Now you can type in the command or select the button with the ellipsis. Doing the latter brings up a window where you can type in the command. Visual Tcl allows you to enter a block of code, but since we are generating Python, you better stick to one-liners. An example, is 'quit' which invokes the quit function which contains 'root.destroy()' in the example t1. If you want to invoke a function and pass parameters to it, you use a lambda expression. Please see section 6.4 of Grayson's book for a fine explanation of the use of lambda expressions in this context. Let say that if you want to call the function foo and pass it 3 as an argument, what you enter as command is

lambda : foo(3)

not 

foo(3)

Please see the examples for command=... arguments.

The loadcommand event handling is similar to the command event. In the above example, I used it to load the two list boxes with the same data. This is executed as the widget is created. It was very convenient to pass to load_listbox the list to be loaded. In loading the first command, I actually put in the name of the object, 'self.lis17'. By the time I got to the TixScrolledListBox, I smartened up and implemented '%me' as shorthand for the object name and the code generator converted that to the actual name of the object. As mentioned above, I now consider the loadcommand to be deprecated.

So much for the 'special' events.

Bindings Window

There are many events that can be linked to code. See the Tk man pages and Chapter 6 of Grayson. They include responding to the different mouse button pressings or releases, a window getting or loosing focus, etc.. If you want to link code to one of those events, select the widget with Button-1, 'go to' 1 the Options Menu and select Bindings. A Widget Binding window


will open. The top line which contains an entry box and several menu buttons lets you specify the event. (As I got Visual TCL, things always blew up when you selected one of the last buttons because there were no entries behind them. I added several.) I believe the purpose of the Type entry field to the left is to enter the keys to which linkage is desired. (For instance in my t1 example, look at the entry field with the plum background. It responds to pressing the key 'q'). The multi-line scrolled list below is where you enter the command. As planned for Tcl you can enter a block of code, but for PAGE, you want a one-line lambda expression. That is, you want to link to a Python function. There is a difference here from what was discussed above, because when the event happens an argument 'e' is passed to the callback specifying the event. This means that the command is something like:

lambda e: foo_bar(1,3,5)

The parameter e above is the event object which contains much information about the event. The object has the following attributes (from the documentation found in Tkinter.py):

    serial - serial number of event
    num - mouse button pressed (ButtonPress, ButtonRelease)
    focus - whether the window has the focus (Enter, Leave)
    height - height of the exposed window (Configure, Expose)
    width - width of the exposed window (Configure, Expose)
    keycode - keycode of the pressed key (KeyPress, KeyRelease)
    state - state of the event as a number (ButtonPress, ButtonRelease,
                            Enter, KeyPress, KeyRelease,
                            Leave, Motion)
    state - state as a string (Visibility)
    time - when the event occurred
    x - x-position of the mouse
    y - y-position of the mouse
    x_root - x-position of the mouse on the screen
             (ButtonPress, ButtonRelease, KeyPress, KeyRelease, Motion)
    y_root - y-position of the mouse on the screen
             (ButtonPress, ButtonRelease, KeyPress, KeyRelease, Motion)
    char - pressed character (KeyPress, KeyRelease)
    send_event - see X/Windows documentation
    keysym - keysym of the the event as a string (KeyPress, KeyRelease)
    keysym_num - keysym of the event as a number (KeyPress, KeyRelease)
    type - type of the event as a number
    widget - widget in which the event occurred
    delta - delta of wheel movement (MouseWheel)
The vrex example uses the following code vrex_help.py:
lambda e: load_vrex_help(e.widget)
Thus, the above command could be used to load the contents of a text widget when the widget is mapped, i. e., made visible. The bound event in that case is <Map>.

The Event entry field is generated for you from the selections that you made from the top line of entry and menubuttons. I don't think that you want to do anything with it. Similarly the Tags entry are filled in from the selection of the widget from the top window under construction. Binding tags seems like a fairly complex part of TCL and I have not had occasion to use them in any of the GUI's that I have constructed yet. If you need to use them, then see tcl documentation. They confuse me.

A nontrivial example of event specification. Suppose that you want to bind an event to a double click of the middle button. First select Mouse->Press->2. Then select Mod->Double. That specifies the event <Double-Button-2>.

The following row of buttons: Add, Update, Delete, and Done, work as follows: Add creates a new binding. When experimenting with this window, it appeared that the requirement was (a) to select the event from the top row, and (b) fill in the command before selecting Add. Then the new binding will take place and be added as a new line to the scrolled list box at the bottom of the window.

If you have some bindings displayed in the bottom scrolled list box, then you can select one of them with Button-1 and that will make possible the use of the Delete button which does the obvious thing.

Also having selected an entry in the bottom box, you can double click on it and the command box and the entry boxes in the middle will be updated. You can then change the values of those boxes and select the update box to commit them.

The Done button closes the window.

Previously, I had a shortcut, the bindcommand attribute, which would easily bind <Double-Button-1> to a command. I have now removed that attribute in favor of the Widget Bindings window. To get that binding do the following steps within that window:

  1. Select Mouse->Press->1
  2. Select Mod->Double
  3. Enter command in the Command List Field
  4. Select Add

Keep in mind: To add binding one works with the 'Widget Bindings' window. Consider the Tags entry. It defaults to contain the word 'all'. I think that that means that all the widgets get the binding? I am not sure. Anyway, I habitually remove the all when associating bindings with widgets. Oh why couldn't Allen add some documentation once in a while.

One can quickly go beyond the common usages of bindings and that may run into trouble with PAGE. However, it has been possible for me to build a number of useful applications using the facilities provide by PAGE. I think it fulfills the goal of being a helpful tool while not providing all possible facilities.

Defining Functions

If you name a function in a menu or an event binding or something similar the you will need to define that function. Do that by means of the function list window which is usually open or can be made visible from the Window menu. To create a function hit add. Yet another window will appear. The first step is to type the function name in the top field. If you want a global function named fun_x just type fun_x there. Forget the arguments entry. In the main window type the Python function you want to create using Python syntax including the def statement complete with arguments. The rule is that you enter the function just as you want to see it in the Python code.

In the case that the function you want is intended to be a class method of the GUI class, e.g., method_a(a,b,c = None), you enter into the top entry field 'self.' followed by the name, in this case you enter self.method_a. Again, ignore the Arguments entry field. In the main field enter the complete method as you want to see it. Here it would be

def method(self, a, b, c = None):
   print a
   print b
   if c != None:
      print c

Special Widget Processing

Toplevel Widget

Toplevel widgets don't really have a title property. It was added in Visual Tcl and the Attribute Editor originally displayed it as one of the geometry attributes. That didn't seem like a good idea so I moved it to the Attributes section.

Among the attributes listed for a toplevel widget is 'menu' which in Visual Tcl allows one to easily create a menu in a menubar at the top of the widget. To create a menubar for a toplevel widget select the window with Button-1, 'go to' the attribute editor and click in the menu entry field. That will bring up the menu editing window. See the next section for some information on using that editor. An important thing to keep in mind is that all entries at the first level of the menu should be cascade items to submenu. For instance, the items at the first level might be File, Edit, View and Help. Then File might be a cascade item to the submenu containing New, Open, Close, and Exit. You can use a command but then a dropdown menu will not appear.

Menu Specification

Menu specification can be associated with a Menu bar at the top of the window (see previous section) or with a menubutton. A menubutton is a button which when selected cause a menu to be displayed.

There is a special window for creating menus. It can be entered by double clicking on the menubutton or from the attribute editor. Thus, to create a menu associated with a menubutton, select a menu button from the tool bar and put it on the window just like any other widget. Then 'go to' the Attribute editor and modify the attributes as desired. From there, you can click on the menu attribute near the bottom of the attribute window or double click on the widget itself. If you want to create a menubar at the top of a toplevel window, select the window and from the Attribute editor click on the menu option.

The menu editing window


allows you to completely specify a menu associated with a menu button. Near the top of the window is a field containing 'Tearoff Menu', if it is colored then the generated menu will be a tear off menu. To change the entry select it with Button-1. The 'Entry Type' field allows you to select various types of entries like cascade, command, radio button, check box, or separator. A command is a simple command such as 'Copy' and a separator is just a mark between two entries. Cascade creates a separate submenu and radio button and check box are obvious types of menu entries. The Entry Label is the label displayed for the item and the Accelerator is the keyboard accelerator associated with the menu item. For example, Alt+S might be the keyboard accelerator for Save. I advise caution with keyboard accelerators because they do not appear to work at the Tcl/Tk level for other than commands. With checkbutton and radiobutton items the keyboard accelerators appear in the menu but do not seem to work. This is at variance with my reading of the Tk documentation. The Underline is the index of a character in the name which acts like a shortcut. For instance, if the 'F' in File is underlined, then Alt-f will select that item. If it is a cascade item then the submenu will be displayed. The Underline is a zero based index with zero indicating the first character. A minus one means no underlined character. I find the underline feature more useful than the Keyboard acceleration feature.

Select Add to put them in the list. If you select command, then it will say something like <command> command.

Put the mouse on that line and Double-Button-1 and yet another window opens up where you can type the command. Again, you want to restrain your self to a one liner, generally a function name or a lambda expression.

If the menu item type is cascade then Double-Button-1 will open up a whole new menu specification window where you then specify the cascade menu.

The Add button at the bottom of the menu editing window places the new item at the bottom of the menu. You can use the Up arrow and Down arrow keys to move items in a menu up or down.

I think that the Update key allows one to change the selected menu item and the Delete key removes it. Done quits the menu editing window.

TixNoteBook Specification

One can create a TixNoteBook one on the GUI by (1) selecting the root window, and (2)selecting the notebook icon on the tool bar. Then drag it to it its destination on the GUI and resize it by (1) selecting the widget with Control-Button-1, (2) holding down the buttons while dragging it to its home, and (3) select it again with Control-Button-1 and drag a handle (one of those 8 black globs around the edge).

The widget as placed will have 2 blank pages in which other widgets can be placed. Page 1 is on top and widgets can be added. To add other pages to the note book or to bring up other pages for manipulation select the note book with Button-3, a pop up menu allows you to add a page or select one of the existing pages.

I have added many different widgets to a page of a note book, but I ran into trouble putting a TixNoteBook in a page of a TixNoteBook. I want to get this out rather than fix up that pathological case.

Checkbutton Widget

The usage of the checkbutton is straight forward except for accessing the state of the checkbutton. In other words, how does one determine if the checkbutton has been selected or not using, tkinter facilities? The answer is to associate a Tkinter variable with the Checkbutton. Then one can access the state of the Checkbutton with the get method of the Tkinter variable. I am including a simple but working example:


#! /usr/bin/env python
# -*- python -*-
# Time-stamp: <2005-07-17 10:37:39 rozen>

from Tkinter import *
import Tix, sys


def init(): pass

def vp_start_gui():
    global w
    global root
    root = Tix.Tk()
    root.title('New_Toplevel_1')
    root.geometry('200x200+3+564')
    w = New_Toplevel_1 (root)
    init()
    root.mainloop()

def greeting(str):
    ''' This is a small routine that tells the uner
        which button was pushed.'''
    import Dialog
    Dialog.Dialog(title="greetings",
                  text=str+"  "+w.var.get(),
                  bitmap="",default=0,strings=("cont",))

    # w.var.get() retrieves the state of the checkbutton.
    print 'greeting: w.var.get() =', w.var.get()    # Rozen pyp


''' One assigns a to the 'variable' c onfiguration item and a
    Tkvariable is an object which is instantiated by a call to
    StringVar() in this example or more commonly IntVar(). To see the
    state of the check button one then does a get() on that variable.

    Of course, if one used self.var = IntVar(), and left out the 'offvalue'
    and 'invalue' settings the state would just be 0 or 1.

    I found an excellent reference -
            http://effbot.org/tkinterbook/checkbutton.htm

    I intend to keep http://effbot.org/ as an active reference.
'''

class New_Toplevel_1:
    def __init__(self, master=None):
        self.che26 = Checkbutton (master)
        self.che26.place(in_=master,x=60,y=60)
        self.che26.configure(command=lambda :greeting("Check Button 1"))
        self.che26.configure(offvalue="off")
        self.che26.configure(onvalue="on")
        self.che26.configure(selectcolor="#ff0c00")
        self.che26.configure(text="check")
        self.che26.configure(textvariable="cb")
        self.var = StringVar()
        self.che26.configure(variable=self.var)


if __name__ == '__main__':
    vp_start_gui()

In the above example, the PAGE utilized the attribute setting for 'variable', 'on setting', and 'off setting' in the Attribute Editor when the checkbutton was selected. The function 'greeting' and the comments were added after the python code was generated.

Radiobutton Widget

The discussion above for getting the state of a Checkbutton applies as well to the Radiobutton widget

Running the Generated Gui

The generated GUI is written in such a way that it will run stand alone as a script provided that you have all the necessary stuff included. For instance, many of the widgets will have references to command functions that need to be present at least in skeletal form for the generated Python to run stand alone. Import statements may also cause problem when trying to immediately run the GUI.

Realistically, what I generally use this facility for is to see how the GUI looks. To that end, the code is built with very minimal functions in order to check the appearance of the GUI by running from the Python Window. The final lines of the module are:

if __name__ == '__main__':
    vp_start_gui()

which will call vp_start_gui when the the script is exercised. The key is vp_start_gui which is generated automatically. This is the function which your main application module should call to create the root. I recommend that you do not modify it. It contains:

def vp_start_gui():
    global w
    global root
    root = Tix.Tk()
    root.title('New_Toplevel_1')
    root.geometry('934x975+86+69')
    w = New_Toplevel_1 (root)
    init()
    root.mainloop()

The title above can be different depending on the specified attribute of the Toplevel and, of course, the geometry will reflect the location and sizes you specify by dragging the widget.

When you select the top level widget and Generate Python from the popup menu, the Python Window will appear be filled with the generated code. You can push the run button and execution will be attempted. This will automatically save the generated code in a py file where the root name matches that of the tcl file. Another thing you can do is save the python module, 'go to' a terminal window, and run the program there.

When running in Python Window, line output from the GUI is directed to the lower window of the Python Window.

A final word about running in the Python Window is that the tcl file will be automatically saved as well as the python file. If no name has been selected for the project, then a file output window will open and you will be presented with a way to specify the tcl file name, the root of which will be used for the python file name.

The init() function

PAGE generates several functions for you automatically. the function init is generated conditionally. Since the startup function vp_start_gui calls init, init better exist. If you don't define one, then PAGE will generate one for you containing only the pass statement. You can use the same mechanism for defining init as any other function. PAGE will supply the following null init function in the absence of one you define.

def init():
    pass
To get some real function in 'init' you can either define the init function in PAGE or edit the null init after GUI generation. The init function is called after the GUI is drawn.

Using PAGE to Build Applications with Multiple Top-Level Windows

In building a multi, top-level window example, I recommend that one use PAGE to build the 'root' top-level window and then use the generated Python module as the main routine of the application. When one needs additional top-level windows, use PAGE to specify them and cut and paste code for the window object as well as the templates for creating and destroying them in either the main module or new modules which are then accessed through the 'import' statement. When PAGE generates Python code, it also includes a bunch of boiler plate for a header which invokes Python, imports Tkinter and Tix, initializes and creates the main window. This boiler plate in not needed for other top-level windows in the application.

When one feels confident in using PAGE, he looks at the code patterns generated by PAGE and feels confident in modifying the code for effects not anticipated in PAGE. The philosophy of PAGE is that there is a large and intimidating body of information needed to start building a GUI. PAGE encapsulates that information and produces working code. At that point the user can see what direction the low is taking and can easily customize the code. You can see some of that in the Photograph Album example included.

Python Digital Picture Album

I decided to build my own program to manage a digital photograph album which would let me view incoming photographs and move them to an archive and tie the whole thing together with MySQL. Also, I wanted to be able to display reasonable sized thumbnails, be able to select a thumbnail and send the photograph to gimp or xv for manipulation or display. The main requirement was to have several top level windows for (1) displaying thumbnails, (2) browsing through the directory structure of the incoming photographs and the archive, (3) for manipulating and querying the data base attributes, (4) displaying the exif data for the selected picture, and (5) hosting a progress bar. Also the windows would have different behavior depending on the function of the window. Also, it was desirable to have dynamic popup menus.

This section will describe how I did that as well as some of the tricks I figured out for manipulating the widgets. The code may be seen at Digital Picture Album. I urge looking at the code, a few examples go a long way. I don't claim that the code is particularly good but it is good enough to see what I was trying to do and to serve as a starting point for your learning more.

I am rather pleased with this application. I will try releasing it as separate project PyAlbum at SourceForge.

General Scheme

The basic strategy is to use PAGE to create top level windows and then to extract pieces of code for the window classes and for creating them in the modules of your program.

Again, I used PAGE to create the main window of the application and then to generated and saved the Python code in PAGE. Then I used that generated python file as the starting for the main program file p.py. As other windows were needed I used PAGE to create them and to generate Python files for each. I then cut and pasted the relevant code sections from the generated Python files into the main module. The main pieces that are needed were (1) the class that was generated for the window and (2) the create function that is generated in a comment. You may also want to pick up the close function. I have recently changed PAGE to include templates of the 'create' and 'close' functions.

Starting with those pieces as templates you can then rather easily build your application around those pieces. Once you see how the code is laid out, you can then change it to do the various things you need. The hard thing with building programs with GUI's is knowing how to start. PAGE get you over that hurdle. If you then look at a working program that does a bunch of different things you will be in pretty good shape.

I have included my Picture Album program. It is not to be confused with a really well written program the style of which is worthy of emulation. It is evolving. Just view it as a bunch of examples from which you can get some ideas. It is p.py and is in the same directory as this document. The full program can be found in the examples subdirectory of the installation.

I borrow as much code as I possibly can and search for code to be borrowed. In the words of Tom Lehrer "Don't hide your eyes, plagiarize". The external pieces that I used here include: MySqldb, PIL, dparser, exif, and I also used the sets module for do algebra on the results of the sql queries. Sets is a module incouded in the Python2.3 distribution, but I had to download and install MySQLdb, PIL, dparser, and exif.

Looking at p.py

The first step was to generate the main GUI using PAGE. I decided that I wanted to use a TixScrolledWindow but PAGE does not support that widget so I selected the TixScrolledText widget and generated the Python code for that widget. That code was then the beginning of my main module, p.py. The only other module that I wrote was sparse.py which is used to parse user specified attributes when looking for pictures in the archive. Looking at my documentation for Tix in 'Tcl/Tk Power Tools', Mark Harrison, O'Reilly, Sabastopol, CA, 1997, I saw that I could easily change the code in the top window class from TixScrolledText to TixScrolledWindow. It was just one lines of code. I also needed access to the window part of the scrolled window into which I would insert my photos. This was just one line of code. I was off and running. Once having done that, it was easy to add TixScrolledWindow to PAGE; it's there now.

My next problem was to put thumbnail images into the scrolled window. The method for doing that came from looking at the program, 'imageEditor.py' in Grayson. He not only showed how to created the thumbnail image using the PIL package for Python, he shows how to place them in a canvas using the grid manager. I saw that it was easy to modify his approach to the internal frame of the scrolled window. I decided that I would use the grid manager rather than the placer manager. That was also a one line change and I was off and running.

I decided that I did not want a drop-down menu; rather I wanted all the menu choices as buttons across the top of the main window. You can see the code there for doing that. I did however want to be able to select a thumbnail image and have a popup menu appear. Further I wanted the popup menu to be dynamic in the sense that the choices would change depending on where my thumbnail image came from - either the directory where images are deposited from the camera or from the archive. See 'def popup' to see how I create and post the menu when an image is selected.

An interesting aspect of the program is the creation and display of the thumbnail images. See the function 'display_image_list'. A directory of images is displayed by this function. PIL is used to create the thumbnail image from the full size image and is displayed by putting a button down and specifying its image to be the created thumbnail image. Here I used the grid manager rather than the placer manager and use up to three columns for the images. Below each button is a label containing the date and time when the photograph was taken. The date and time information came from the exif data supplied by the camera as part of the original image. A binding is specified for each button such that Button-1 invokes 'xv' to display the image in all its glory and Button-3 causes the popup menu to be created and posted. Note the line of code:

self.id[self.pict[i].winfo_id()] = i
which generates a dictionary entry with the correspondence between the widget (button) and the position, i. The point is that PAGE could never have built these functions but it certainly gave me the framework for writing it.

I would never have figured out how to create and display the thumbnail images without the extensive imageEditor example in section 5.3 of Grayson's book.

The top level window classes are taken from python code generated by PAGE but then changed. Consider first New_Toplevel_1. The class was changed in the ways mentioned above. That class was changed into the main part of the program with a fairly large number of methods. The top level menu and the popup menu form the main dispatcher of the whole program. Several functions within New_Toplevel_1 create the other toplevel windows of the application. They include 'create_attribute_window', 'create_directory_display', 'create_Progress_Bar' and 'create_exif_display'.

Newer modifications to PAGE allow one to generate a top level window in python and include 'create' and 'close' routine templates in an extended comment. The create routines were lifted out of the generated python and used to construct 'create_attribute_window', 'create_directory_display', and 'create_exif_display' in a pretty obvious way. The simplest window of the three is 'Vrex_Info'.

In the case of 'Vrex_Info' the exif text string to be displayed is sent to the __init__ function as a parameter.

It turns out that the attribute window and the directory window are used for more than one purpose each. For that reason, and additional parameter is added to the '__init__' function of each to differentiate the different usage. If you look at the '__init__' function Select_Dir, you will see that the additional parameter 'which' is used to fix the configuration variable 'value' so that the proper directory is displayed. Also, the 'close' routine from the PAGE generated code is inserted so that when a directory is selected and displayed the window can be removed. The parameter 'which' is a keyword parameter since the second parameter is a keyword parameter. By the time I got to Vrex_Info, I tried experimenting with moving the string parameter before the keyword parameter and it worked just fine. Choose the style you feel comfortable. In any case, remember that computer science is an experimental science.

Similarly, the attribute window is used for several purposes which are differentiated by the variable, 'action'. Here things, get more interesting. I use a switch based on 'action' to change the text in buttons and indeed the number of buttons and the functions called when buttons are selected.

Rework

Rework refers to the ability to test the GUI and debug the Python functions under Python and then to return to PAGE under Tcl/Tk and modify the GUI. Rework is essential for this type of tool, is rarely available, but is supported in PAGE.

Originally, rework was supported by requiring the user to keep two files in sync and by including a utility, inc.py, which would move the contents of Python functions from the python version to the tcl version. More recently, I have written p2t.py which will generate a loadable tcl module from the python version. It is included in the distribution. There is a later section on p2t.py.

When you are generating a GUI you are working with two files having the same root name and extensions of tcl and py. For instance, if you are using PAGE to build 'gui.py', then the file generated by PAGE is 'gui.tcl'. When you depress button 3 over the top level window, a popup menu will give you the option to 'Generate Python' at which point the file 'gui.py' will be generated and displayed in a new window. That window will allow you save the py file or execute it, etc. You must exercise care to keep the two files in sync to allow future changes. One way to do that, is to use the menu item File->Save. Currently, PAGE is doing this for you. The first time a save is attempted, the user will be asked to specify a name and directory for storing the 'gui.tcl' file. When the tcl file is saved, the previous version is renamed with the 'tcl.bak' suffix.

Often, you are not satisfied with the first Python file generated by PAGE. As I am debugging a GUI or extending the application, I will change the functions that appear in the py file and then I discover that I need an additional widget. At that point, I want to go back to PAGE to spruce up the GUI. I open PAGE and in the File menu I select 'Open Py', which invokes a utility, written in Python which merges any function or method changes from 'gui.py' into 'gui.tcl' before PAGE sources 'gui.tcl'. I then change the appearance of the GUI. When I then 'Generate Python' the new program 'gui.py' will have the new version of the GUI plus all of the debugging changes that had been made to the previous version of 'gui.py'.

The alternative to 'Open Py' for rework is to run

p2t.py  file_name.py
which will convert the python file to a tcl file that can be opened by PAGE. When using p2t.py to convert a 'tcl' file from a PAGE generated Python file, one does not have to worry about keeping an up-to-date 'tcl' file. Instead, you worry about whether p2t.py works properly.

Loading Python Files - More on Rework

As mentioned above, as you debug, in python, the generated GUI you will likely make changes to the functions in it and later you may want to modify the GUI. It is very important that you do not have to manually reenter information about the procedures into the tcl file.

Obviously, you can't load just any python file; but PAGE will take the function information from a PAGE generated python file and put it into the corresponding tcl file which can be loaded.

You will see in the File menu the 'Load Python' selection. How can a Tcl program manipulate Python code?, you ask. Of course, it can't. What happens is that a program 'inc.py' is invoked which reads the tcl file and replaces all of the user defined procedures in it with corresponding functions in the corresponding py file. inc.py does not analyze the generated program to look for things like global imports or global variables. It relies on your putting that sort of thing in the init function. It really keeps things clean and allows you greater flexibility because I do not have to anticipate them.

I would like to replace 'inc.py' with a program which will take the generated Python program and generate from it a corresponding tcl module that could be sourced. That would be good because it would obviate the need to keep the python and tcl versions of the program in step and would preserver manual changes to the widget invocation and configuration. The down side is that it would have to be very stylized and thus might be a bit fragile.

p2t

I have now coded a translator in Python which will take a python module generated by PAGE and generate a 'tcl' file suitable for sourcing by PAGE; it is p2t.py and I have included it in the distribution. It probably still has problems. It is up to you to decide whether you would prefer to keep the tcl version around or to try the regenerated version. One invokes p2t with:

python p2t.py python_file_name
Previously, I had included a version of p2t in the distribution. I have now rewritten it and feel that it is more nearly ready for general usage. It may generate a file with the 'tcl' extension but that does not mean that the module will execute. The trick to building p2t was to realize that the resulting tcl program did not have to execute. All it has to do is set up a few data structures when it is sourced. There may be tcl syntax all over the place but there may also be procs and bind statements which have Python syntax. It is only useful for sourcing from PAGE. p2t.py has the advantage over inc.py that it recognizes global imports and other global statements and handles them properly. For now run p2t stand alone with the command above.

Remember that p2t.py expects the highly stylized format of the generated GUI. If you alter that format, there may be problems with p2t.py. One important assumption is that the only class in the GUI py file is the one for generating the GUI itself. You may put in functions to support that generation, but you better not add classes. Another assumption is that the function 'vp_start_gui' must appear before the class definition, so that the class name will agree with the title for the toplevel window.

I would be very surprised if there were not errors in p2t. So, my recommendation is to continue to keep the 'tcl' companion file around. But if you try p2t, please let me know about the problems that you face. When I get more confidence in p2t, I will incorporate it replacing inc. p2t.py is to be found in the main page installation directory. I welcome feedback on any problems or suggestions. Please send me any problems with p2t.py, i.e., please sent the python file which could not be correctly translated with p2t.py.

As of this release 2.3d of PAGE, the comments above concerning p2t hold. But it is my intention to review p2t.py in light of my current thinking that I want to use PAGE just to create top level windows and to incorporate more than one of them in my application Python module. So look for something different in the future that may even serve better as a vehicle for rework.

Default Considerations

In earlier versions of PAGE, I recommended use of the TkoptionsDB as a way of getting uniform non standard fonts and background colors. However, this came with a lot of problems when trying to move among different operating environments. This occurs because some of the widget sizes are based on character widths and they will differ between operating systems. Also, people will choose different color schemes and a nice looking GUI in one scheme may become very strange in another.

Thanks to Mike Clarkson, I am now convinced that the best thing to do if you want to have a reasonable GUI amomg different environments is to stick to the new WmDefaults approach and not try to override it with TkoptionsDB. The t3 example in the examples subdirectory is a sanitized version of t1 where all attributes refering to color or fonts have been expunged.. It looks reasonable under windows and several KDE color schemes. See snapshot1, snapshot2, or snapshot3.

Real Examples

The examples t1 and t3 mentioned above are primarily gee-whiz examples that includes many of the widgets that I am able to handle with PAGE. Below are simpler working examples with fewer widgets that perform useful tasks and illustrate particular capabilities of interest.

Vrex.py

During the writing of p2t.py, I required a number of regular expressions and used to great advantage Visual Regexp in composing and testing those regular expressions and thought that rewriting the program in Python would be interesting because it would illustrate important other capabilities while being a useful program in its own right. In particular, it shows how one GUI calls another and also binds the <Map> event to a funtions which loads the Scroll box. It only supports a subset of the functions that were included in Visual Tcl. See vrex.html for more information.

vrex is composed of two modules vrex.py and vrex_help.py. Both were generated with PAGE and actually contains things that I recommend against. It may be interesting to look at. I find it useful for composing and testing regular expressions.

Final Recommendations

Save often.
Spring for Grayson's book.
Save often.

Acknowledgements

First thanks to HP for assigning the rights of PAGE to me which allows me to make this available to the Python community.

Again, let me acknowledge that PAGE is built on top of Visual Tcl. Without that work I probably would not have known how to get started.

I had difficulty understanding the new WmDefault package and how to exploit it in PAGE. Mike Clarkson gave me much help. Mike also helped me enormously in getting a reasonable installation package together for Win32.

Finally, I got a much essential help and very fast response from Ioi Lam, the author of Tix.