Using PAGE

PAGE is built upon the program Visual Tcl, but is different because of differing objectives of the two programs. Visual Tcl was conceived as complete build environment for Tcl. It included facilities for managing projects with multiple windows, creating of GUI’s, binding actions with callback procedures, creating menus, writing functions, and testing the application, all the while supporting many different widget sets and several geometry managers. PAGE is limited to defining a single GUI window using Tk and ttk widgets and the placer geometry manager; there are better environments for building and debugging Python programs once you have code for the GUI.

PAGE makes use of the Virtual Tcl facilities for creating a single GUI window, assigning attributes to widgets, binding events to callback procedures, and creating menus. PAGE also automatically generates skeletal callback functions and supplies much of the boilerplate code for running Tkinter.

When PAGE generates the code for a window, all of the code for causing Python to create and map that window is generated - a Python class for the toplevel window with all the code necessary to instantiate the class (i. e., display the window), import statements, Tk mainloop and initialization, a main procedure, etc. Therefore, if you have provided the skeletal callbacks the generated code is executable and you can see just how the GUI will look in a Python environment. PAGE even attempts to generate skeletal callbacks.

The generated code can be used as the skeleton for your application. However, the generated GUI may be a secondary GUI and all that you really want to do is instantiate the toplevel class, use support functions implied within the GUI and possibly to destroy that window. You can do that by importing the code as a module and invoking functions within that module including automatically generated functions for creation and destruction of the window. This will be discussed in more detail later.

I often use the generated GUI classes as starting points for customization. When you are not sure how to start or want to quickly try out some GUI ideas, PAGE is very useful because it generates all of the boilerplate necessary.

PAGE is invoked by executing the “page” script located in the page directory. I go to that directory and issue the command:

./page [filename]

I preface the command with ”./” to be sure I am executing the page script in the page directory. By including “page” in the PATH environmental variable, one can work in any directory.

PAGE can be invoked with zero or one file names. If supplied the file name should have an extension of ”.tcl”. If another extension is supplied that is interpreted as an error and PAGE terminates. If no extension is given in the file name or the file name ends in ”.” , an extension of ”.tcl” is assumed by PAGE. If the file cannot be found PAGE terminates. If a file name is specified, the file should be a tcl file saved during a previous PAGE session; it will be opened as PAGE begins executing as an alternative to File->Open for proceeding from a saved PAGE session. Near the top of a ”.tcl” design file created in PAGE there is a comment containing version information and a timestamp. If that version information is not present then PAGE will refuse to open the file.

As you use PAGE to build a GUI, you can save the current state of the GUI at any time from the File->Save of File->Save As menu. In fact, it is a good idea to save your status often in case PAGE fails or dies.


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

One uses PAGE to generate a GUI as follows:

  • Start PAGE by executing “page” or activating the PAGE icon on the Windows desktop. In windows one may also start PAGE from the command line by going to the installation directory and executing winpage.bat.
  • Create the new top window by selecting the top left button in the widget toolbar.
  • Drag the toplevel window to where you want it.
  • Resize the toplevel window by dragging a handle at the corners or edges.
  • Change the title by changing the title attribute in the Attribute Editor.
  • Add a menu to the top level using the Menu Editor entered from Widget->Edit Menu in the main menu.
  • Drag appropriate widgets from the tool bar to the toplevel window or other previously placed container widgets.
  • As desired to make the generated code more readable, specify an unique alias for the widget.
  • Adjust properties of the widgets and the toplevel window as desired. Some of those properties will include specification of functions to support the GUI such as to load list box, or to respond to mouse selection, etc. Included can be the specifications of event bindings.
  • Use the function definition facilities to specify the necessary functions and use the bind specification facilities to bind events to functions.
  • When the window has the appearance that you want, select Gen_Python->Generate Import Module. A new window will appear and fill with the skeleton functions and the definitions of the Tkinter variables. Save the source window of the python console.
  • Then select Gen_Python->Generate Python GUI from the main menu. A new window, a Python Console will open and fill with the Python code, which I call the GUI module. To generate the support module select Gen_Python->Generate Support Module, which creates and fills another Python Console with the generated skeleton support module.
  • You can save the code in one or both of the Python modules and then try executing the GUI module to see if you like what you have designed.

Placing and Modifying Widgets

Toplevel Geometry

PAGE generates a single Toplevel Tk window which is the users GUI for his application. (One can have multiple GUI windows; see Applications with Multiple Top-Level Windows.) As elsewhere with Tk and PAGE, there is more than one way to specify where the GUI window will appear on the application user’s screen.

Prior to release 4.5, the PAGE user would select the Toplevel button in the Widget Toolbar and a toplevel window would appear on the screen. As well as filling in subwidgets the user could drag the toplevel widget to the desired screen location and also could adjust its size. When the Python code was generated it would contain code that would place the GUI at the exact spot specified. If you had placed it at pixel specification +1000+300, it would end up 1000 pixels from the upper left corner and 300 pixels down.

With version 4.5, the Python code generation addresses the toplevel placement with a new attribute - “default origin”.

The “default origin” preference, if selected will cause the Python GUI window to placed on the screen at the default location as determined by the system window manager. If false, the location will be determined from where the toplevel window is placed in PAGE. The default for the this attribute is false, but can be changed by means of the Preferences mechanism. A default of false will cause PAGE to behave as before. Unfortunately, I was faced with an unclear mnemonic in the Attribute Editor or a double negative; I chose the latter

Adding Widgets

All that is necessary to add a widget to the GUI is to select it with Button-1 from the Widget Toolbar, position the cursor inside the destination container and again press the Button-1. A small version of the widget will appear with its upper left-hand corner at the point of the Button-1 click.


Often algorithmically generated names can be difficult to understand in computer generated code. To reduce the problem in PAGE, users can specify more easily understood names called aliases.

An alias is a user specified identifier of a widget in the generated code. Obviously, aliases must be unique within the class which will be generated in the python code to realize the GUI window. An easy way to specify an alias is to select the target widget with a Button-3 click and then select “Set Alias ...”. Another small window will appear and one can add the alias. Finally close that window by selecting the “check” or with the Enter key.

Another way to enter an alias is to select a widget with Button-1 and then select “Set Alias ...” from the Option menu in the main PAGE window.

Also, one may specify an alias by selecting a widget and editing the “Alias” field in the top section of the Attribute Editor.

An alias must be a legal python identifier. PAGE does allow blanks in an alias but they will be changed to “_”. Also Alias has no meaning for top level windows. For top level windows the the variable names are generated from the title attribute. There are numerous schemes for generating such names but one that has been suggested is based on CamelCase. It is:

  • Buttons could start with ‘btn’, i.e. btnQuit, btnNew, etc.
  • Entry boxes could start with ‘txt’, i.e. txtFirstName, etc.
  • Check boxes could start with ‘chk’ I.E. chkDoThis, etc.
  • Radio Buttons could start with ‘rdo’ or ‘rbtn’.

Note that PAGE has an option so that PAGE will automatically generate an alias. This too is an algorithmically generated name but one that is easier to live with rather that the default algorithmically generated name. This is controlled by a new field in the Preference window. So you should be sure that it is correctly set and is not overridden by a value in your ”.pagerc” file. I recommend that you go into Preferences, check the value and save the preferences. Why the original Visual Tcl program had two methods of generating names, one comprehensible and one not and still chose the less readable one as the default is baffling.

Selecting a Widget for Modification

For simple widgets like buttons or text boxes you can select the widget either by selecting the widget with Button-1 in the GUI or in the Widget Tree. Then it is merely a matter of dragging one of the attached handles to resize the widget or dragging the mouse inside the widget to change it placement within its container or parent widget. In version 4.8, the handles were enlarged and change color when the mouse enters. This was done to make it easier to select a handle for resizing.

With more complex widgets like notebooks, paned windows, or scrolled widgets, clicking Button-1 inside the widget will select a child widget rather than the whole widget. There are several cases with the complex widgets - moving the widget or resizing the widget, or changing attributes of the widget.

  • If you want to move the widget select it with Control-Button-1.
  • To resize the widget select it from the Widget Tree with Button-1 and then grab one of the eight handles to resize the widget. I experienced issues with selecting a handle, so I made them larger and caused the one under the mouse to turn red. If the handle of a complex widget under the mouse does not change to red, try doing repeating Control-Button-1.
  • To change an attribute, select the widget from the Widget Tree and modify the attribute in the Attribute Editor.
  • To modify the pages in a TNotebook or panes in a TPanedwindow invoke the menu with Button-2 in the Widget Tree, or the widget itself then select the Widget submenu.

When you select a widget, the Attribute Editor window will display all configuration options available for that widget and the default values of those widgets. I think it is clear how to modify most of the options. One can easily change the colors, fonts, labels, text, relief, and some that I don’t even recognize. All of the attributes are described in the Tcl documentation.

Obviously, if you use a custom widget, Attributes are unavailable to PAGE and the Attribute Editor. Custom widgets are shown as a text widget and so the Attributes are that of a test widget and should be ignored.

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 to be specified, it can just be typed in.

If you are working with standard Tcl widgets, there are many options that can be modified whereas the ttk widgets have very few options, their appearance being governed principally by the specified theme.

Fill Container

“Fill Container” is a feature that was added in version 4.8 and causes the selected widget to expand to fill its container provided that there are no other widgets already in the container. This function is restricted to those widgets for which I think it makes sense like frames, notebooks, scrolled widgets, canvases as well as text and list boxes.

To use this feature activate the Widget popup menu with Button-3 over the widget itself or an entry in the Widget Tree and select “Fill Container” appears in the widget submenu.

Cut, Copy, and Paste

After much work I think that there is now a useful cut, copy, and paste feature in PAGE. The basic way it works:

  1. Select the widget you want to paste in a new spot or container by selecting it as you would for just plain moving it. If it is a simple widget like a button you can just select it with Button-1 or with a more complex widget try selecting it from the Widget Tree.
  2. With Button-3 pop up the Widget Menu and select with Button-1 Cut or Copy as you want.
  3. Again pop up the menu with Button-3 and select Paste or use Control-V or select Paste from the Main menu -> Edit.
  4. Move the mouse to the desired insertion point and click Button-1.

Copy and Paste are important features because one cannot drag a widget from one container widget into another but one can cut or copy and paste to get that effect. For instance if one creates a button in a top level window and then decides that it should be moved to a frame or a notebook tab that cannot be accomplished that just by selecting the button and dragging it to the frame. However it can be done with cut and paste.

When debugging the this feature that I made fewer mistakes by selecting the widget to be copied by selecting it in the Widget Tree than trying to grad it in the top level window. This is most important when trying to select and copy nested widgets which one can do.

I have implemented cut and it appears to work; however, I never use it. There is a sequence of several steps between the selecting copy from the menu and selecting the location of the paste. If you get it wrong following a cut, you can’t go back and retry the operation; the source for the copying is gone. So, I stick to copy and paste and then finish with a delete.

One thing that cut, copy, and paste cannot do is copy from one module’s GUI and paste in another module’s GUI.

Widget Menu

The Widget Menu, which appears in more than one place including the main menu as well as the drop menu in the Widget Tree or a top level window, provides a convenient way to modify some of the widget attributes. The drop menu from the Widget Tree is seen below


In the case above the menu allows one to easily specify the text attribute and for specifying multiple line text labels.

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. These functions are termed callbacks. 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 which will effect the function call with a null parameter list. The other possibility is a lambda expression to call a function and pass a parameter list.

Many widgets have a command attribute which specifies the code to executed when the widget is selected with Button-1. To specify that command, one selects the widget and then shifts attention to the widget menu or the attribute editor and specifically 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.

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)



Grayson talks about invoking callbacks directly or indirectly. Specifying a command attribute is an indirect invocation while specifying a binding to an event causes a direct invocation. The difference is that the direct invocation passes an event object to the callback. The callback function must be an argument list which includes a parameter for the event. To see examples of direct and indirect invocation of call backs see the vrex example.

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. as well as virtual events. If you want to link code to one of those events, open the Bindings Window either from the popup menus in response to selecting the widget with Button-3 or selecting the widget with Button-1 and typing Alt-b. Then select Insert from the menu to put the event in to the left pane. Finally insert the desired code in the right pane where a template will appear.

Some virtual events are defined within the Tcl/Tk for things like cut, copy, and paste as well a special virtual events for different Tk widgets. The bindings mechanism will handle bindings for those virtual events but there is no supported mechanism for creating such events. For instance, with the Scrolledtext widget one can select the text widget, type Alt-b to enter the binding editor, choose Insert then Advanced, scroll down to show <<Cut>> and select it, then the Add button and it will appear in the right window of the binding editor where one specifies the name of a routine say xxx, then select the check to commit it. When on then run the program, selects some characters in the text box and then Ctrl-C, the built in trigger for Cut, the routine xxx in the support module will be called.

Let’s look again at


On the left you will see top in blue the Tcl name of the widget and below the name a list of the event to which the widget responds. The key action is to insert an action (in effect it is to choose one of the actions listed in the lower part). It is done by clicking Insert Menu and selecting one of the actions there.


If you do not find the action you can select Advanced ...


You may select an action from the right panel. A key strokes may be specified in the top entry box. A modifier may be selected from the right panel.

As shown the action was <Button-1>. Having done that, you are then allowed to type in the code you want executed. Once again stick with a one-line lambda expression. There is a difference here from what was discussed above in connection with the command attribute, because when the event happens an argument is passed to the callback which is the event object. This means that the command is something like:

lambda e: foo_bar(e,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

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)

By using the Insert->Advanced you can specify specific keys, press or release, and double or triple clicking, use of Alt or Control Keys. It is a pretty extensive list.

One can quickly go beyond the common usages of bindings and that may run into untested usage of PAGE. However, it has been possible for me to build a number of useful applications using the facilities provide by PAGE without encountering difficulties.

With version 4.8, Custom Widgets were introduced. Since PAGE knows nothing of those widgets including binding possibilities, the mechanism just described cannot be used to specified bindings for such widgets.

Defining Functions

If you name a function in a menu, an event binding, command attribute, etc., then definition of that function is required before trying to execute the GUI in PAGE.

Executing the GUI means to execute the generated Python code within PAGE to demonstrate how the GUI will appear in the completed application by selecting the Run button on the Python Console. To satisfy requirements of the Python interpreter named variables have to be defined and if they refer to functions, those functions have to be defined and of proper syntax. Skeleton functions will be satisfactory since at that state of development you really don’t expect the application to usefully function.

One path for the user is to do nothing, letting PAGE create a skeleton for you by just noticing that the a name is specified in a binding or an attribute in the support module as described in Rework. This is the recommended approach as of version 4.2.

Previously, I did a makeover of the Visual Tcl function definition facility. While satisfyingly clever, I now have abandoned it for the simpler creation of skeletal functions.

I usually just let PAGE generate the skeleton functions, run the resulting code to see what my window will look like and then do the rest of my programming in emacs. When PAGE stores the python modules, several layers of backup files are retained. If I need to regress to one of them, I find that Meld is a wonderful utility for managing the differences between versions.

Previously, the automatically generated functions contained only a single “pass” statement. Unfortunately, that tells you little when you try executing the generated Python code in the Python Console. So I preceded the “pass” statement with a print statement which prints the function name when the function is called. I have found that to be helpful.

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. The title is displayed at top of the window and is used with “_“‘s replacing blanks, as the name of the generated Python class.

Among the attributes listed for a toplevel widget is ‘menu’ which in Visual Tcl allows one to easily create a menubar at the top of the widget. Click on the attribute and follow the procedure below.

An improvement in version 3.5 was the ability to change other properties such as the colors and the cursor of Toplevel widgets. One property of Toplevel widgets is relief. However, I was totally unable to change that property at either the Python or Tcl/Tk level. I simply don’t know how.

Relative Placement

Following the paradigm I am familiar with from VB, PAGE uses the place window manager to fix location within the generated GUI. Thanks to George Tellalov, who suggested using relative placement for widgets within the GUI thus allowing one to build stretchable GUI’s. They allow one to grab an edge or corner of the executing Python GUI and change its size and while maintaining relative positions and sizes of the internal widgets. This is the default behavior. Keep in mind that with relative placement, widgets may change size as the toplevel window is resized, but fonts do not change size.

Since I don’t think buttons or labels should change size in step with the change in size of the window, relative placement for buttons does not change the size of buttons but the relative placement is maintained.

Tkinter Variable Classes

With several of the widgets, it is necessary to have linkage between tkinter variables and Python variables. For instance, when one moves a slider of a scale in the GUI window, he wants the value to be reflected in a Python variable or conversely changing that variable should change the position of the slider. This is done by means of the Tkinter variable classes: BooleanVar, DoubleVar, IntVar, and StringVar.

You need an instance of one of these classes. PAGE guesses the variable type which you may need. You can then use the get method in python to determine the value of the variable in tk and use the set method to set the value of the tk variable. There are examples below. For more information see the Tkinter Variable Class on the web page.

For instance if you are using a TScale widget to be coupled with the tk variable variable “val”:

def set_Tk_var():
    # These are Tkinter variables used passed to Tkinter and must be
    # defined before the widgets using them are created.
    global val
    val = DoubleVar()
    val.set(5)   # Initial value to be displayed.

then you need to set the TScale attribute to “val”. The rule is that the Tkinter variable must exist before it appears in a configuration statement like:


When val is changed


the TScale will move to that value.

If the TScale is changed in the GUI, you can read it with


PAGE tries to help out by generating an instance of the appropriate global class variables as needed. Again, this is skeletal code to help the generated Python code run from within the Python Console. It recognizes that a variable starting with “self.” is a class attribute.

If you forget to specify the variable then try to execute, the code may give an error saying that a variable with a strange name is a problem because a funny name is put into the variable attribute in the Attribute Editor. PAGE tries to detect this situation and give a cryptic error message.

For more discussion of Tkinter Variable Classes see Tkinter 8.5 reference.

Ttk Widgets

There are some aspects of the ttk widget set that have presented me with some significant difficulties mainly due to “styles”, it may be merely that I don’t understand the ttk widgets well enough or to problems with their implementation and documentations. I will try to explain my problems below. I would welcome any suggestions.

Scrolled Widgets

For some reason that I don’t understand, the Tk folks have never seen fit to implement scrolled widgets such as a Scrolled Text widget. I certainly don’t want to fuss around with all the separate programming tasks required for build a scrolled text widget when building a GUI.

I am especially pleased that Guilherme Polo in his Pyttk-samples package shows how to build Scrolledtext and Scrolledtreeview widgets. Borrowing that code, I was able to include such widgets as well as a Scrolledlistbox in PAGE.

One can select a scrolled widget from the Widget Toolbar and place it in the GUI and PAGE will include all of the Python support coded necessary to realize the scrolled widget. The scrolled widgets that I added are not named with an T because they are not official ttk widgets. The Scrolledtextbox has a normal text subwidget in which I have chosen to make the “wrap” default “none”.

Although Polo implemented his code with ttk widgets, I have used his ideas to implement scrolled tk widgets as well. In fact, where possible I have used tk widgets in preference to ttk widgets. For instance, I have based only the ScrolledTreeview and Scrolledcombobox on ttk widgets. I am indebted to Polo for his ideas but he bears no responsibility for any errors I make interpreting his ideas.

The scrolled widgets are compound widgets containing as-needed scrollbars and an elementary widget. As such, to set attributes or apply bindings, first select the elementary internal widget.

When the scrolled widgets are placed into a container window, the image shown displays vertical scroll bars to facilitate identification. The best is that the appearance is similar to that which is shown when the GUI is executed because as implemented in the Python code the scroll bars appear only when required. I had a lot of trouble with background colors in the ttk::scrollbars used in the Python GUI.

These widgets are complex widgets, so to move or resize them, use control with Button-1. The scrollbars are ttk::scrollbar widgets which appear only when needed, i. e., when an item extends beyond the allotted space.

When inserting text inside a Scrolledtext widget, treat it like a text widget. For example, uses code like

obj = self.Scrolledtext1
obj.insert(END, "This is text to be inserted")

When cutting, or copying scrolled widgets, I use the widget tree for easily selecting the whole widget rather than just the interior widget.

Ttk Notebook

If you select, place, and resize a TNotebook in your GUI window it have display two pages and look like:


To change the attributes of the TNotebook, select the notebook editor either by invoking Control-Button-1 in the widget and then selecting edit page from the Widget menu or by selecting it in the Widget Tree with Button-3 and then the Widget entry in the popup menu.


Here you can do all sorts of interesting things like change the text in the tab and select the tab you want to activate for adding widgets or changing attributes of widgets already added to that page. If you got to the menu Item->Add, it will create a new page to the notebook, in the above example, between Page 1 and Page 2. The Move menu as well as the up and down buttons will change the order of the pages. The move operation will move the selected page one position up or down. The move is circular in the sense that moving the bottom item down will move it to the top, etc..

Another way of navigating the pages is the pages attribute in the Attribute Editor.

Ttk Panedwindow

The TPanedwindow can be added to the GUI by selecting it from the Widget Toolbar and then Button-1 in the container. There are two entries for the TPainedwindow , one for vertical separators and another for horizontal, in the Widget Toolbar. It can be moved around by Control-Button-1 and resized by dragging one of the handles. It is sometimes a bit difficult grabbing a handle unless you select the TPanedwindow widget from the Widget Tree window. Each pane of the paned window contains a filling TLableframe. Using the label frame was the only way I could find to actually get the a TPanedwindow to appear on my screen. Again, I had few examples to work from.

The paned window is configured by invoking Widget->Edit Panes menu item bringing up the following editor.


This editor allows users to move among the panes, change the text in the label frame, and to manipulate the position of the sash between the current frame and the next one. You can set the sash position as a percentage of the total size of paned window. Again, one saves the changes by clicking on the green check.

With version 3.2, one can select the TLableframe defining the pane and drag the edge of it to change the sash position. In other words, select a pane with Button-1 and drag one of the interior handle; that will move the sash between the selected pane and the adjacent pane.

The Edit panes window allows one to add additional panes to the window via the Item menu. The implementation of paned windows sets the initial size of the paned window to 200x200 pixels and the the first pane size to 75 pixels. Adding a new pane adds one at the end (the right end of a horizontal TPanedwindow or the bottom of a vertical TPanedwindow, in either case, taking space from prior end pane. Fortunately you can resize the whole TPanedwindow which changes the size of the end pane. Then resize the others by changing the sash positions as described above.

The editor, by including the Move menu and the little up and down arrows allows one can move the a pane to a new position. Again, the move operation is circular.

I am still having a problem with resizing TPanedwindows. If you select the paned window, by say selecting it in the Widget Tree window, move the paned window, and then select and move one of the handles, the TPanedwindow will move rather than resize. However, if you again select a handle it will resize the window. Obviously, something has been set but not reset. As soon as I figure out how to correct the problem I will release a new version.

Ttk Treeview

I have had real difficulty providing reasonably good support for the Treeview widget. I actually do not support the ttk::treeview widget, rather I have gone directly to the Scrolledtreeview which embeds the ttk::treeview widget in a ttk::frame with auto-scrolling scrollbars.

What I have been able to do is to support the placement of the widget in a window with a default of one column in addition to the tree column both columns are stretchable. By invoking the column editor you may change many of the characteristics of the widget such as the column size and heading, as well as the number of columns. It also allows one to reorder the columns. Note that the column that contains the tree has the index of “#0” and must remain the first (left-most) column. The column editor is invoked with Button-3 in the widget and going to Widget->Edit Columns ... as shown below:


If the widget were created or a column is added with the configuration option “stretch” is set to 0 (not stretchable), then when the resulting GUI is stretched then the column width will not change. The enclosing widget with the scrollbars will either have a blank area to the right of the last column if the window is enlarged or the last column will not fully show. To get the more desirable behavior, go into the Column Editor in the Widget menu and make at least one of the columns stretchable.

Ttk Entry

While I do support the ttk entry widget, I don’t see any reason to recommend using it because I am unable to change the widgets font and I don’t like being stuck with the TkDefaultFont. Strange to say the ttk Combobox, below, is rumored to be based on the TEntry widget and I am able to manipulate the font size using the style facility.

Ttk Combobox

The combobox requires a list of selectable values to display in the drop-down listbox. These are easily specified from the Set Values entry of the Widget menu. When invoked from the menu a scrolled text box appears and the values are specified by entering them one per line in the text box and then selecting the check mark. The user should include strings as they should appear in the Python code. That means if they are string literals, then they should be enclosed in quotes as shown in the example below. If the quoting is not present it is assumed that they are variables. I do not want to get into the morass of entry parsing literal strings.


Values can also be set in the “init” function in support module using code like:

w.TCombobox1['values'] = ('USA', 'Canada', 'Australia')

My style problem with the TCombobox is that while I can use the style mechanism to change the font of the entry field in the combobox I have not found a way to change the font of the drop down area containing the values. In addition, I have not found a mechanism for changing the values in the drop down list after initialization.


TRadiobuttons and Radiobuttons act pretty much the same. One specifies several of the widgets which are linked by specifying the same variable for all. It is necessary to specify a different value for each radiobutton. One can also specify an initial value for the group. For TRadiobuttons the values and variables can be specified from the Widget menu as well as in the Attribute editor.

Strangeness with Text and Variables

I was surprised to discover a couple of strange things about the way text is handled with ttk widgets. There are several ttk widgets, among them TButton, TMenubutton, Tlabel, TCheckbuttom, and Tradiobutton, which have both properties of “-text” and “-variable”. Using the Attribute Editor you can specify the value of the Text to be displayed in PAGE when you are laying out the GUI. However, if you then specify the variable attribute, the text attribute is changed to the zero length string. I guess that that is because the variable you named is undefined. But also the width of the field containing the text may be set to zero length. So, for instance, Tlables appear to have zero width unless you have changed the width prior to specifying the variable. A Tbutton merely displays blank text and TRadiobuttons squeezes down to just the button. TCheckbuttons behaves similarly to TRadiobuttons.

When you generate the Python code the widgets will again appear to have no text and maybe have no width unless you have set a non blank value into the Tkinter variable. Tk widgets including Button, Message, Label, Checkbutton, and Radiobutton behave much the same way.


Label widgets also treat text in an unexpected way. The justify attribute applies how multiple lines of text are aligned relative to each other, it does not set how text lines are placed relative to the Label widget boundaries. Use the anchor attribute to specify if the text block is up against the left of the widget (anchor ‘e’) or the right (anchor ‘w’).


The Listbox widget has the option “listvariable” which one would expect to behave exactly like one of the Tkinter variable classes. However, the Tcl documentation specifies that the listvariable must contain a list of values to be displayed in the listbox and the possible Tkinter variable classes are BooleanVar, StringVar, IntVar, and DoubleVar. By experimentation I have found that by specifying the var to be StringVar and setting its value to a tuple of strings will cause each member of the tuple to appear as an entry in the listbox. I am rather surprised at this but glad to find something that works.

The following code

def set_Tk_var():
    global rrr
    rrr = StringVar()

results in a scrolled list box looking like:



The Spinbox widget has the option values which contains a list of values presented in the widget as the arrows manipulated. They are set using the Widget menu item “Set Values”. When invoked from the menu a scrolled text box appears and the values are specified by entering them one per line in the text box and then selecting the check mark.

Scale and TScale

Tk does strange things when one tries to modify the narrow dimension of a scale widget (the height of a horizontal scale or the width of a vertical one). So PAGE does not allow one to modify the narrow dimension during the design phase and restricts the Relative Placement in the Python code to prevent changes in the narrow dimension.


Support for the ttk::sizegrip widget was included in Version 4.0. Merely select the widget from the Widget Toolbar and drop it anywhere within the Toplevel frame but not on top of another widget; it will bounce to the lower right corner. Were you to drop it on say a notebook widget, a weird result would occur like landing in the wrong place but doing the right thing.

I had difficulties with using Sizegrip with PAGE windows. It works fine with the Python Console and the Menu Editor, but I never got a truly satisfactory result with Widget Tree or or the Widget Toolbar. I left it with the Widget Toolbar but not with the Widget tree. To be revisited.

Custom Widget

In writing a photo manager, I needed a variation of a scrolled canvas to display photos. I could not find a general purpose candidate for a scrolled canvas widget, but a found several variations on the web that might work. So I implemented a Custom widget that can be manipulate in PAGE but requires the user to supply the Python implementation. So if I have left something out and you can conjure a tkinter implementation of it, page can handle it. PAGE shows that widget as Text widget which can be placed and resized like any other widget. However since the widget has not been defined within PAGE, it is meaningless to talk about modifying attributes. (It is shown as a Text widget with the caption “Custom widget”.) The generated Python refers to it as a class defined in the support module. To allow execution of the GUI before the support module is completed, there is included in the support modle the line:

Custom = Frame

which is followed with user code.

The user then inserts his code for the custom widgets as a class with the <class_name> of his choosing and follows that code with the line:

Custom = <class_name>

This is the magic that links the widget that you placed with the code in the support modules. See Custom Widget example.

Generating, Inspecting, and Running the Python GUI

Generating the Python Modules

Once the GUI has been defined, the next step is to generate the Python code which will realize the GUI. This is done from the Gen_Python submenu of the main menu. This will generate hopefully executable Python modules and display them in Python consoles. Beginning with version 4.2, the Gen_Python submenu is used to generate two Python modules, the GUI module <name>.py and support module <name_support>.py, as discussed in the section on Rework. The intention is that all or almost all of the algorithmic code of the application will be in the support module and the code for building the GUI will be in the GUI module. PAGE generates a complete and hopefully working GUI module and a mere skeleton of the support module with just enough code to allow the execution of the GUI module.

What happens on execution is that the GUI class will be instantiated which means that the GUI will appear on the screen and then control will pass to the “init” function in the support module where the necessary code is written to support the function desired with the GUI, i.e., the application. So the workflow envisioned is:

  • One will initially use page to generate the GUI module and a skeletal support module.
  • The user will then iterate modification of design and execution of the GUI module until it contains the desired widgets with PAGE generating both the GUI and the support module at each step.
  • At that point, attention shifts to adding to the support module the code to actually implement the application. At that point, the PAGE user does not have PAGE regenerate the support module because that would lose his hand written code.
  • At some point in the development of the application, the user is likely to want modifications to the GUI. Make them but generate only the GUI module and make manual changes to the support module changes dictated by the GUI changes. Mostly those changes will be to add to the support module new functions for callbacks or new Tk variables, etc.. Then iterate this last step until one is finished with the application.

Generation of the modules is accomplished by selecting Gen_Python->Generate GUI Module from the main menu. When selected, a Python Console will open and display the generated module. Similarly, Gen_Python->Generate Support Module will open a different Python Console displaying the support module. Too keep the project file in sync with the GUI file, the project file is saved when the GUI file is generated.

Similarly, when you select Generate Support Module from the menu, a Python Console will appear, filled with the generated code for the supporting module named “<name>”. Note: The support module is generated by analyzing the saved GUI module. That means that the GUI module must be save before the corresponding support module can be generated. This file will contain skeleton functions and Tkinter variables needed. This file will contain the principal code for the application. This, ideally, is generated automatically once per application.

It is important that the support file is not automatically saved when run is invoked. Since most of the handwritten code of the application will reside in the support module, it is only saved when explicitly requested. I don’t want PAGE to inadvertently trash your handwritten application code. The Python Console has a new label next to the save console which will indicate when the code window has been modified. That flag is turned on any key is released over the window and that can indicate changes which may not actually change the text, i. e., a false positive.

Inspecting the Generated Code

Often the user will want to look at the code that exists for a project. To do that, execute page with the project name or open the project and the select Gen_Python->Load Python Consoles. This will open two Python Consoles; one with the GUI module and the other with the support module. The loading of the consoles is from appropriate modules saved to disk. If a Python Console exists with either the GUI or support module, it will not be overwritten. If one or the other has not been saved, then nothing is done with the corresponding Python Console.

My guess that this is most interesting when the user has modified the GUI and generated a new GUI module and wants to see what any existing support module looks like. Again, in this situation, the new GUI code is not automatically saved.

Executing the Project

To see what the GUI looks like, the user can run or execute the GUI module. That can occur in two contexts, one is to execute the code from one of the Python Consoles and the other is to load the modules into an IDE and carry on development from there. For execution within PAGE there has to be a Python Console; the user can generate either the GUI or the support module or load the project into the consoles from the Gen_Python submenu.

To execute the GUI, select the Run button in a Python Console or using the shortcut Control-R when the cursor is over a Python Console.

The generated GUI module is written in such a way that it will run stand alone as a script provided that all the necessary stuff is present. For instance, many of the widgets will have references to command functions and callbacks that need to be present at least in skeletal form for the generated Python to run stand alone. They may also reference Tkinter variable classes which must be defined for the Python to execute. That code will be included in the support module along with the code to link the GUI module and the support module. This means that one can expect the generated module to execute in the sense that the GUI will appear.

Let me discuss the skeletal functions first. Function references may be referenced in several ways. If the function name is given the skeletal function will be created in the support module. An example would be to specify the command attribute in PAGE as “george”. In that case, the skeletal support function “george” would be created in the support module. If the specification were given as “”, the skeletal module would created as a class function within the GUI class. Finally, if another module were specified as in “” PAGE would not create a skeleton function at all; your on your own to create and import the “app” module. From this you can see the need to create the support module before trying to execute the GUI module.

Similarly, Tkinter variable classes are defined or the GUI class or in the support module depending on the presence or absence of “self.” at the beginning of the specification. If specified in the support module, code is included to insure that the class is created before the GUI execution references the class.

Because the use of “self.” in specifying functions and Tk variables will require use code to be added to the GUI module, I avoid them in my usage of PAGE. Such specifications work against the benefits of the rework facilities.

I frequently execute the GUI module to see how the Python version of GUI looks. To that end, the support module is generated with very minimal skeletal functions in order to check the appearance of the GUI by running from the Python console. The final lines of both the GUI module are:

if __name__ == '__main__':

and the final lines of the support module are:

if __name__ == '__main__':
   import name

which will call vp_start_gui when the either module is executed. The key is vp_start_gui which is generated automatically. It contains some code like the following, where “unknown” is the default project name within PAGE:

import unknown_support

def vp_start_gui():
    '''Starting point when module is the main routine.'''
    global val, w, root
    root = Tk()
    top = New_Toplevel_1 (root)
    unknown_support.init(root, top)

The title above reflects the title attribute of the Toplevel window and, of course, the geometry will reflect the location and sizes you specify in placing the toplevel widget.

When you select the toplevel widget and Generate Import Module from the menu, the Python Console will appear, filled with the generated code for the supporting module named “<name>”. This file will contain skeleton functions and Tkinter variables needed. This file will contain the principal code for the application. This is generated automatically once per application.

When you select the toplevel widget and Generate Python from the menu, the Python Console will appear, filled with the generated code. You can push the run button and execution will be attempted. This will automatically save the generated code into a ”.py” file where the root name matches that of the tcl file which is also automatically saved. When running from the Python Window, line output from the GUI is directed to the lower window of the Python Console.

A final word about running in a 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 window with which to specify the tcl file name, the root of which will be used for the python file name; it is a project name. The tcl file can be used as an argument to PAGE allowing one to later pickup where he left off.the script

Execution of the Python GUI is caused by either selecting the “Run” button at the bottom of the Python GUI or by typing Control-R. It can also be run directly by the Python interpreter.

The function “init” is the place to initial things after the GUI is mapped.

Loading PAGE files into an IDE

While it is possible to edit PAGE generated files Python Consoles and to execute them from there, they don’t really constitute a particularly good IDE (Interactive Design Environment) for creating the necessary application code around the GUI. For that, one should move into a well concieved IDE or editor like IdleX, emacs, or any of a host of similar programs.

PAGE can start up an IDE with the GUI module and the support module for the expansion and testing of the application. This is done by selecting Gen_Python->Load Project into IDE.

The IDE is set in the Basics page of the Preferences. PAGE tries to execute a the IDE with two the two file names, <name>.py and <name>, as arguments. If your favorite IDE can be so invoked then it should work. When running under Linux or OSX, one can enter either the full path name of the IDE or a command name in the path. If you are on Windows, then the full path name takes the a form with double backslashes like “C:\Python27\Lib\idlelibidle” I am not an expert in Python IDE’s, but I have successfully tested this facility with emacs, vim, idle, and idlex on Linux; idlex in OSX, and idle in Windows. For instance, on Linux in the IDE command field I enter the full path of the IDE. Since I want to use IdleX under Linux I enter /usr/local/bin/idlex. I also found that I could enter idlex for Linux.

The user sets the preferred IDE in Basics page of the preferences.

Applications with Multiple Top-Level Windows

Often the user will want to build applications which have more than one top level window. Since PAGE is built for the specification of only one such window how does the user proceed? I think that the best approach is to create separate modules for each top level window and have the support module for the main GUI import them using import statements. The windows can then be created using the create function in the modules. This approach was used for the Vrex example above. Here, I used PAGE to build skeletons for two separate program modules, the main vrex program and the vrex_help module, and coupled the two by having vrex, the main module, import vrex_help and invoke the help window when the “Help” button was selected with the statement:

import vrex_help

def help():

Of course, there is very little interaction between the two programs which simplifies things in this case. As in the example below, the argument “root” ties the help window to the main window.

The same technique was used in the progress_bar example which shows more interaction between the windows including the control of the progress bar window from inside the main routine. Here the two modules are and In we have

import progress_bar = progress_bar.create_Progress_Bar(root)

Here pass to the create routine the parameter root which ties the two windows together and the create routine returns the Progress_Bar object which allows the main GUI to access all of the functions and attributes of the Progress_Bar object, in turn allowing the main GUI to advance the bar and to close the progress bar.

Special functions are created in the python code to facilitate the creation and destruction of the window.

w = None
def create_New_Toplevel_1(root, *args, **kwargs):
    '''Starting point when module is imported by another program.'''
    global w, w_win, rt
    rt = root
    w = Toplevel (root)
    top = New_Toplevel_1 (w)
    unknown_support.init(w, top, *args, **kwargs)
    return (w, top)

def destroy_New_Toplevel_1():
    global w
    w = None

One can pass a variable number of parameters to the init function of the support module using standard Python techniques built around the “*args, **kwargs” usage.

Obviously, if one wants to pass parameters to the init function of the window, then one must modify the parameter list to the create function above and to the call to ini in the above:

def create_Progress_Bar (root, other_args, ...)

     w_win = Progress_Bar (w, other_args, ...)

Also, one may pass a parameter to the “init” function in the support module. This parameter may be any Python data type - ,integer, float, etc., as well as lists, tuple, and dictionary. So it is really a variable length parameter list. For instance, you might have code like the following which I used in the Clone example :

p_dict_1 = {'geom': "+200+650", 'instance': 1, 'color' : 'firebrick'}
p_dict_2 = {'geom': "+1000+650", 'instance': 2, 'color' : 'plum'}

def open_two():
         print "open_two starts"
     firebrick = called.create_Called(root,param=p_dict_1)
     plum = called.create_Called(root,param=p_dict_2)

I suggest comparing the directories, progress_bar and rework_progress_bar to see how I modified the program Progress Bar to conform with the new rework scheme.

A common question is how to share global variables across module. A good reference is How do I share global variables across modules?.

Busy Cursors

This section describes how to change the cursor when in long running sections of an application. Changing to a busy cursor gives some feedback to the application user who otherwise may think that the application is hung, not doing anything.

One includes the following code at the module level of the support module:

# Code added to allow one to change default cursor to a busy cursor.
# Variables added manually to indicate long processes based on code by
# Greg Walters in his python programming examples.  These routines
# also can be seen in the Greyson book pg. 158.
busyCursor = 'watch'
preBusyCursors = None

def busyStart(newcursor=None):
    '''We first check to see if a value was passed to newcursor. If
       not, we default to the busyCursor. Then we walk through the
       busyWidgets tuple and set the cursor to whatever we want.'''
    global preBusyCursors
    if not newcursor:
        newcursor = busyCursor
    newPreBusyCursors = {}
    for component in busyWidgets:
        newPreBusyCursors[component] = component['cursor']
    preBusyCursors = (newPreBusyCursors, preBusyCursors)

def busyEnd():
    '''In this routine, we basically reset the cursor for the widgets
       in our busyWidget tuple back to our default cursor.'''
    global preBusyCursors
    if not preBusyCursors:
    oldPreBusyCursors = preBusyCursors[0]
    preBusyCursors = preBusyCursors[1]
    for component in busyWidgets:
        except KeyError:
# End of busy cursor code.

and the following lines of code are inserted in “init” in the support module:

global busyWidgets

busyWidgets = (top, )

The first line goes in near the top of the function and the assignment to busyWidgets is inserted after the root object is created. In one of my applications the function “init” looks like:

def init(top, gui):
    ''' Function to create the thread for periodically updating the GUI.'''
    global t
    global w, top_level
    global busyWidgets
    w = gui
    top_level = top
    t = threading.Thread(target=fill_window,)
    busyWidgets = (top, w.Scrolledtext1)

The final line above sets the global variable busyWidgets to be a tuple of those widgets I wish to display the busy cursor. This is from the example WCPE where I want the busy cursor to appear in the top level window as well as the Scrolledtextbox.

When starting a section of code which is likely to be long running, a busy section, insert the following at the start:


When leaving a busy section make the following the last statement():


Obviously, an application could have numerous busy sections and they might coincide with particular functions or not.

This code can be generalized for usage of any of the Tkinter cursors.

Final Thought on Usage

When one feels confident in using PAGE, he looks at the code patterns generated by PAGE and modifies 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 the direction the flow is taking and can easily customize the code at the python level.