Using PAGE

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 puts out 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.

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 there is no extension given in the file name or the file name ends in ”.” , an extension of ”.tcl” is supplied 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” 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.

Overview

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.
  • Bring up 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 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.
  • 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. Then select Gen_Python->Gen_Python from the main menu. A new window will open and fill with the Python code.
  • Save the code and/or try running it.

Placing and Modifying Widgets

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.

Aliases

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 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.

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. There are some tk binding issues that confuse me, so let me say that some of these steps may result in moving the complex widget rather than resizing it; the steps may have to be repeated.
  • 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. Obviously, all of the attributes are described in the 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 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.

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. Go to your Top Level Window, pop up the menu with Button-3 then 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

_images/widget_menu.png

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)

not

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.. 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.

Lets look again at

_images/binding.jpg

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 (really it is choose one of the actions listed in the lower part). It is done by clicking Insert Menu and selecting one of the actions there. 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 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)

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.

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.

The functionality described below is now, with version 4.2, considered a deprecated function.

If you wish to have a more complete function then go to 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 filled with a dummy function, py:xxx. See below.

_images/function-editor.jpg

To get a skeleton function just change the ‘xxx’s to the name of your function and fill in the parameter list. If the function is to be a class method be sure to insert “self,” including the comma as the first parameter even if you have no other parameters. Obviously, you can write as much of function here as you want. For instance, I sometimes will include the statement sys.exit() in a quit function.

def quit():
    sys.exit()

An aside:

Since Tcl will tolerate a procedure containing anything until it executed, PAGE will save your function as:

## Procedure:  py:wet

proc ::py:wet {} {
def wet(self,) :
    pass
}

That means that later you can come back to PAGE to modify the GUI and the functions that you saved will still be there!

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. If I want to go back to PAGE to change my window, I save the functions that I have added into a separate file which I later insert into my program after the GUI changes. Lately, I have been using the program Meld to facilitate the copying of functions from one version of the application to another.

Since writing the above, I have implemented the rework strategy below and am less inclined than ever to use the function definition discussed above. In other words, I just accept the skeleton generated for the imported support module and go from there. See the section on Rework.

Previously, the automatically generated functions contained only a single “pass” statement. Unfortunately, that tell you nothing when you try executing the generated Python code in the Python Console. So I changed the “pass” statement to 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 menu in 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, I decided to use 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.

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 effbot.org 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:

self.che26.configure(variable=self.var)

When val is changed

val.set(14)

the TScale will move to that value.

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

val.get()

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.

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 “word”.

When the scrolled widgets are pasted into a container window, the image shown does not display the scroll bars for two reasons. 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. The other is that I had a lot of trouble with background colors in the ttk::scrollbars used in the Python GUI. One can distinguish between the text boxes and scrolled text boxes because I load the PAGE version with the word Scrolled.

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 test inside a Scrolledtext widet, one seems to treat it like a text widget. For example one uses code like

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

TNotebook

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

_images/notebook.jpg

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.

_images/edit-pages.jpg

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.

TPanedwindow

The TPanedwindow can be added to the GUI by selecting it from the Widget Toolbar and then Button-1 in the container. 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.

_images/edit-panes.jpg

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.

Treeview

I have had real difficulty providing reasonably good support for the Treeview widget.

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. 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.

When the widget is created or when a column is added the configuration option “stretch” is set to 0 (not stretchable). This means that the widget will not behave as I expect when using relative placement and the window size is changed when executing the Python code. 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.

Combobox

The combobox requires a list of selectable values. These are most 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.

_images/SetValues.png

Radiobuttons

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 widets including Button, Message, Label, Checkbutton, and Radiobutton behave much the same way.

Listbox

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()
    rrr.set(('a','b','c','d','e'))

results in a scrolled list box looking like:

_images/Scrolled_List_Box.png

Spinbox

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.

Sizegrip

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.

Generating and Running the Python GUI

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 a hopefully executable Python module and display it in the Python console. Starting in version 4.0, the generated code will contain comments with PAGE and Tcl version information and a timestamp.

Beginning with version 4.2, the Gen_Python submenu is used to generate two Python modules. <name>.py and <name_support>.py, called the support module, as discussed in the section on Rework. The intention is that all or almost all hand generated code will be in the support 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.

As defined above, running the GUI means executing the GUI inside PAGE to see what it will look like in the Python environment. Remember you have not build a complete application, just the 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. They may also reference Tkinter variable classes which must be defined for the Python to execute.

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 “self.George”, the skeletal module would created as a class function within the GUI class. Finally, if another module were specified as in “app.George” PAGE would not create a skeleton function at all; your on your own. 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 spec. If specified in the support module, code is included to insure that the class is created before the GUI execution references the class.

I generally use this facility to see how the Python version of GUI looks. To that end, the code is built with very minimal skeletal functions in order to check the appearance of the GUI by running from the Python console. 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. It contains some code like:

import unknown_support

def vp_start_gui():
    '''Starting point when module is the main routine.'''
    global val, w, root
    root = Tk()
    root.title('New_Toplevel_1')
    root.geometry('600x450+650+150')
    unknown_support.set_Tk_var()
    w = New_Toplevel_1 (root)
    unknown_support.init(root, w)
    root.mainloop()

The title above will be different depending on 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>_support.py”. This file will contain skeleton functions and Tkinter variables needed. This is the file to update to 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 in the Python Window, line output from the GUI is directed to the lower window of the Python Console.

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 window with which to specify the tcl file name, the root of which will be used for the python file name. The tcl file can be used as an argument to PAGE allowing one to later pickup where he left off.

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.

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

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 main routine import them using import statements. The windows can then be created using the create routine 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():
    vrex_help.create_Vrex_Help(root)

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 main.py and progress_bar.py. In main.py we have

import progress_bar

self.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_Progress_Bar (root):
    '''Starting point when module is imported by another program.'''
    global w, w_win
    if w: # So we have only one instance of window.
        return
    w = Toplevel (root)
    w.title('Progress_Bar')
    w.geometry('301x129+472+154')
    w_win = Progress_Bar (w)
    unknown_support.init(w, w_win, param)
    return w_win

def destroy_Progress_Bar ():
    global w
    w.destroy()
    w = None

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 __init__ 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.

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 user who may think that the application is hung, not doing anything.

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

# 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']
        component.configure(cursor=newcursor)
        component.update_idletasks()
    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:
        return
    oldPreBusyCursors = preBusyCursors[0]
    preBusyCursors = preBusyCursors[1]
    for component in busyWidgets:
        try:
            component.configure(cursor=oldPreBusyCursors[component])
        except KeyError:
            pass
        component.update_idletasks()
# End of busy cursor code.

and the following lines of code are inserted in vp_start_gui:

global busyWidgets

busyWidgets = (root, )

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 vp_start_gui looks like:

def vp_start_gui():
    '''Starting point for the program. It opens the main top level window.'''
    global w
    global root
    global busyWidgets
    root = Tk()
    root.title('FotoAlbum')
    root.geometry('%dx%d+175+29' % (DISPLAY_WIDTH, DISPLAY_HEIGHT))
    set_Tk_var()
    busyWidgets = (root, )
    w = Main_Window (root)
    init()
    root.mainloop()

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

busyStart()

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

busyEnd()

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

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.