Module Structure

PAGE generates two modules, The GUI module, and the Support Module. The first contains all of the Python code necessary to plop the GUI window onto the computer screen. In my vision of the PAGE world, the GUI module is not to be edited. It contains all of the required linkage to the Support module. It is generally the main module of the application.

The Support module is generated in skeletal form and is the framework upon which the application is built. It is where the user written code resides. As such it is infrequently generated or replaced by PAGE. In fact, the separation is the secret of rework; it allows changes to the content and appearance of the GUI window while preserving the user’s code.

When the modules are generated they are displayed in the Python Console but not saved automatically. This gives the user an opportunity to inspect the code and decide whether to save. When PAGE generates a module, it uses four spaces for indentation. When PAGE saves a module, spaces are saved as spaces.

Project Directory

The directory containing the various files associated with PAGE is called the project directory. It contains the project file, the generate Python modules, any image files used with widgets, and any application support modules. It should also be the current working directory.

Let me call the project directory “proj_dir”, because it is precise and corresponds to an internal PAGE variable.

This is important when widgets have image attributes. To keep such cases reasonable sane, PAGE requires that all image files associated with such widgets reside within the proj_dir or a subdirectory of it prior to the specification of the widget. The generated code for images contain file names and file locations. The file location are expressed as relative to the current working directory.

I recommend that before executing PAGE, a directory be created and that directory be made the current working directory. Image files need not be included before beginning the GUI development, but need to included before widgets specify non-blank image attributes. Again, you can add a widget that might include an image but the image file must be included before specified the image attribute.

GUI Module

The main features of the GUI module are the class definitions, which defines a GUI windows. It defines the top level windows and all of the contained widgets. Note that it refers to all callback functions as functions in the support module and that Tk variables such as textvariables are referred to Tk variables in the support module.

A particularly simple GUI module, example.py, which has a popup menu and two toplevel windows follows:

  1 #! /usr/bin/env python
  2 #  -*- coding: utf-8 -*-
  3 #
  4 # GUI module generated by PAGE version 7.5d
  5 #  in conjunction with Tcl version 8.6
  6 #    May 26, 2022 07:05:58 AM PDT  platform: Linux
  7
  8 import sys
  9 import tkinter as tk
 10 import tkinter.ttk as ttk
 11 from tkinter.constants import *
 12 import os.path
 13
 14 _script = sys.argv[0]
 15 _location = os.path.dirname(_script)
 16
 17 import b_support
 18
 19 _bgcolor = 'wheat'  # X11 color: #f5deb3
 20 _fgcolor = '#000000'  # X11 color: 'black'
 21 _compcolor = '#b2c9f4' # Closest X11 color: 'SlateGray2'
 22 _ana1color = '#eaf4b2' # Closest X11 color: '{pale goldenrod}'
 23 _ana2color = 'beige' # X11 color: #f5f5dc
 24 _tabfg1 = 'black'
 25 _tabfg2 = 'black'
 26 _tabbg1 = 'grey75'
 27 _tabbg2 = 'grey89'
 28 _bgmode = 'light'
 29 font12 = "-family {DejaVu Sans} -size 12"
 30 font15 = "-family {Nimbus Sans L} -size 14"
 31
 32 _style_code_ran = 0
 33 def _style_code():
 34     global _style_code_ran
 35     if _style_code_ran:
 36        return
 37     style = ttk.Style()
 38     if sys.platform == "win32":
 39        style.theme_use('winnative')
 40     style.configure('.',background=_bgcolor)
 41     style.configure('.',foreground=_fgcolor)
 42     style.configure('.',font='-family {DejaVu Sans} -size 12')
 43     style.map('.',background =
 44        [('selected', _compcolor), ('active',_ana2color)])
 45     if _bgmode == 'black':
 46        style.map('.',foreground =
 47          [('selected', 'white'), ('active','white')])
 48     else:
 49        style.map('.',foreground =
 50          [('selected', 'black'), ('active','black')])
 51     style.map('TNotebook.Tab', background =
 52             [('selected', _bgcolor), ('active', _tabbg1),
 53             ('!active', _tabbg2)], foreground =
 54             [('selected', _fgcolor), ('active', _tabfg1), ('!active',  _tabfg2)])
 55
 56     global _images
 57     _images = (
 58          tk.PhotoImage("img_close", data='''R0lGODlhDAAMAIQUADIyMjc3Nzk5OT09PT
 59                  8/P0JCQkVFRU1NTU5OTlFRUVZWVmBgYGF hYWlpaXt7e6CgoLm5ucLCwszMzNbW
 60                  1v//////////////////////////////////// ///////////yH5BAEKAB8ALA
 61                  AAAAAMAAwAAAUt4CeOZGmaA5mSyQCIwhCUSwEIxHHW+ fkxBgPiBDwshCWHQfc5
 62                   KkoNUtRHpYYAADs= '''),
 63          tk.PhotoImage("img_close_white", data='''R0lGODlhDAAMAPQfAM3NzcjI
 64                 yMbGxsLCwsDAwL29vbq6urKysrGxsa6urqmpqZ+fn56enpaWloSEhF9fX0ZGR
 65                 j09PTMzMykpKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///yH
 66                 5BAEKAB8ALAAAAAAMAAwAAAUt4CeOZGmaA5mSyQCIwhCUSwEIxHHW+fkxBgPi
 67                 BDwshCWHQfc5KkoNUtRHpYYAADs='''),
 68          tk.PhotoImage("img_closeactive", data='''R0lGODlhDAAMAIQcALwuEtIzFL46
 69                  INY0Fdk2FsQ8IdhAI9pAIttCJNlKLtpLL9pMMMNTP cVTPdpZQOBbQd60rN+1rf
 70                  Czp+zLxPbMxPLX0vHY0/fY0/rm4vvx8Pvy8fzy8P//////// ///////yH5BAEK
 71                  AB8ALAAAAAAMAAwAAAVHYLQQZEkukWKuxEgg1EPCcilx24NcHGYWFhx P0zANBE
 72                  GOhhFYGSocTsax2imDOdNtiez9JszjpEg4EAaA5jlNUEASLFICEgIAOw== '''),
 73          tk.PhotoImage("img_closepressed", data='''R0lGODlhDAAMAIQeAJ8nD64qELE
 74                  rELMsEqIyG6cyG7U1HLY2HrY3HrhBKrlCK6pGM7lD LKtHM7pKNL5MNtiViNaon
 75                   +GqoNSyq9WzrNyyqtuzq+O0que/t+bIwubJw+vJw+vTz+zT z////////yH5BAE
 76                  KAB8ALAAAAAAMAAwAAAVJIMUMZEkylGKuwzgc0kPCcgl123NcHWYW Fs6Gp2mYB
 77                  IRgR7MIrAwVDifjWO2WwZzpxkxyfKVCpImMGAeIgQDgVLMHikmCRUpMQgA7 ''')
 78         )
 79     if _bgmode == "dark":
 80         style.element_create("close", "image", "img_close_white",
 81            ('active', 'pressed',  'img_closepressed'),
 82            ('active', 'alternate', 'img_closeactive'), border=8, sticky='')
 83     else:
 84         style.element_create("close", "image", "img_close",
 85            ('active', 'pressed',  'img_closepressed'),
 86            ('active', 'alternate', 'img_closeactive'), border=8, sticky='')
 87
 88     style.layout("ClosetabNotebook", [("ClosetabNotebook.client",
 89                                  {"sticky": "nswe"})])
 90     style.layout("ClosetabNotebook.Tab", [
 91         ("ClosetabNotebook.tab",
 92           { "sticky": "nswe",
 93             "children": [
 94                 ("ClosetabNotebook.padding", {
 95                     "side": "top",
 96                     "sticky": "nswe",
 97                     "children": [
 98                         ("ClosetabNotebook.focus", {
 99                             "side": "top",
100                             "sticky": "nswe",
101                             "children": [
102                                 ("ClosetabNotebook.label", {"side":
103                                   "left", "sticky": ''}),
104                                 ("ClosetabNotebook.close", {"side":
105                                     "left", "sticky": ''}),]})]})]})])
106
107     style.map('ClosetabNotebook.Tab', background =
108         [('selected', _bgcolor), ('active', _tabbg1),
109         ('!active', _tabbg2)], foreground =
110         [('selected', _fgcolor), ('active', _tabfg1), ('!active', _tabfg2)])
111     _style_code_ran = 1
112
113 def popup1(event, *args, **kwargs):
114         font15 = "-family {Nimbus Sans L} -size 14"
115         Popupmenu1 = tk.Menu(b_support.root, tearoff=0)
116         Popupmenu1.configure(activebackground="beige")
117         Popupmenu1.configure(disabledforeground="#b8a786")
118         Popupmenu1.configure(font="-family {Nimbus Sans L} -size 14")
119         sub_menu = tk.Menu(Popupmenu1,
120                 activebackground="beige",
121                 disabledforeground="#b8a786",
122                 font="-family {Nimbus Sans L} -size 14",
123                 tearoff=0)
124         Popupmenu1.add_cascade(menu=sub_menu,
125                 compound="left",
126                 label="File")
127         sub_menu.add_command(
128                 command=b_support.quit,
129                 compound="left",
130                 label="Quit")
131         Popupmenu1.post(event.x_root, event.y_root)
132
133 class Toplevel1:
134     def __init__(self, top=None):
135         '''This class configures and populates the toplevel window.
136            top is the toplevel containing window.'''
137
138         top.geometry("758x734+660+210")
139         top.minsize(1, 1)
140         top.maxsize(1905, 1050)
141         top.resizable(0,  0)
142         top.title("Toplevel 0")
143         top.configure(background="wheat")
144         top.configure(highlightbackground="wheat")
145         top.configure(highlightcolor="black")
146
147         self.top = top
148
149         self.Button1 = tk.Button(self.top)
150         self.Button1.place(x=190, y=200, height=48, width=126)
151         self.Button1.configure(activebackground="beige")
152         self.Button1.configure(background="wheat")
153         self.Button1.configure(borderwidth="2")
154         self.Button1.configure(compound='left')
155         self.Button1.configure(disabledforeground="#b8a786")
156         self.Button1.configure(font="-family {DejaVu Sans} -size 12")
157         self.Button1.configure(highlightbackground="wheat")
158         photo_location = os.path.join(_location,"./stop.png")
159         global _img0
160         _img0 = tk.PhotoImage(file=photo_location)
161         self.Button1.configure(image=_img0)
162         self.Button1.configure(text='''Button''')
163
164         self.Button2 = tk.Button(self.top)
165         self.Button2.place(x=310, y=110, height=35, width=83)
166         self.Button2.configure(activebackground="beige")
167         self.Button2.configure(background="wheat")
168         self.Button2.configure(borderwidth="2")
169         self.Button2.configure(compound='left')
170         self.Button2.configure(disabledforeground="#b8a786")
171         self.Button2.configure(font="-family {DejaVu Sans} -size 12")
172         self.Button2.configure(highlightbackground="wheat")
173         self.Button2.configure(text='''Button''')
174
175         self.Button3 = tk.Button(self.top)
176         self.Button3.place(x=280, y=310, height=35, width=83)
177         self.Button3.configure(activebackground="beige")
178         self.Button3.configure(background="wheat")
179         self.Button3.configure(borderwidth="2")
180         self.Button3.configure(compound='left')
181         self.Button3.configure(disabledforeground="#b8a786")
182         self.Button3.configure(font="-family {DejaVu Sans} -size 12")
183         self.Button3.configure(highlightbackground="wheat")
184         self.Button3.configure(text='''Button''')
185
186         self.menubar = tk.Menu(top, font="-family {Nimbus Sans L} -size 14"
187                 ,bg='#d9d9d9', fg=_fgcolor)
188         top.configure(menu = self.menubar)
189
190         self.sub_menu = tk.Menu(top,
191                 activebackground="beige",
192                 disabledforeground="#b8a786",
193                 font="-family {Nimbus Sans L} -size 14",
194                 tearoff=0)
195         self.menubar.add_cascade(menu=self.sub_menu,
196                 label="NewCascade")
197         photo_location = os.path.join(_location,"./stop.png")
198         global _img1
199         _img1 = tk.PhotoImage(file=photo_location)
200         self.sub_menu.add_command(
201                 image=_img1,
202                 label="NewCommand")
203
204         _style_code()
205         self.TNotebook1 = ttk.Notebook(self.top)
206         self.TNotebook1.place(x=430, y=180, height=228, width=302)
207         self.TNotebook1.configure(takefocus="")
208         self.TNotebook1_t1 = tk.Frame(self.TNotebook1)
209         self.TNotebook1.add(self.TNotebook1_t1, padding=3)
210         self.TNotebook1.tab(0, text='''Page 1''', compound="left"
211                 ,underline='''-1''', )
212         self.TNotebook1_t1.configure(background="wheat")
213         self.TNotebook1_t1.configure(highlightbackground="wheat")
214         self.TNotebook1_t2 = tk.Frame(self.TNotebook1)
215         self.TNotebook1.add(self.TNotebook1_t2, padding=3)
216         self.TNotebook1.tab(1, text='''Page 2''', compound="left"
217                 ,underline='''-1''', )
218         self.TNotebook1_t2.configure(background="wheat")
219         self.TNotebook1_t2.configure(highlightbackground="wheat")
220
221         PNOTEBOOK="ClosetabNotebook"
222         self.PNotebook1 = ttk.Notebook(self.top)
223         self.PNotebook1.place(x=430, y=460, height=228, width=302)
224         self.PNotebook1.configure(style=PNOTEBOOK)
225         self.PNotebook1_t1 = tk.Frame(self.PNotebook1)
226         self.PNotebook1.add(self.PNotebook1_t1, padding=3)
227         self.PNotebook1.tab(0, text='''Page 1''', compound="left"
228                 ,underline='''-1''', )
229         self.PNotebook1_t1.configure(background="wheat")
230         self.PNotebook1_t1.configure(highlightbackground="wheat")
231         self.PNotebook1_t2 = tk.Frame(self.PNotebook1)
232         self.PNotebook1.add(self.PNotebook1_t2, padding=3)
233         self.PNotebook1.tab(1, text='''Page 2''', compound="left"
234                 ,underline='''-1''', )
235         self.PNotebook1_t2.configure(background="wheat")
236         self.PNotebook1_t2.configure(highlightbackground="wheat")
237         self.PNotebook1.bind('<Button-1>',_button_press)
238         self.PNotebook1.bind('<ButtonRelease-1>',_button_release)
239         self.PNotebook1.bind('<Motion>',_mouse_over)
240
241 # The following code is add to handle mouse events with the close icons
242 # in PNotebooks widgets.
243 def _button_press(event):
244     widget = event.widget
245     element = widget.identify(event.x, event.y)
246     if "close" in element:
247         index = widget.index("@%d,%d" % (event.x, event.y))
248         widget.state(['pressed'])
249         widget._active = index
250
251 def _button_release(event):
252     widget = event.widget
253     if not widget.instate(['pressed']):
254             return
255     element = widget.identify(event.x, event.y)
256     try:
257         index = widget.index("@%d,%d" % (event.x, event.y))
258     except tk.TclError:
259         pass
260     if "close" in element and widget._active == index:
261         widget.forget(index)
262         widget.event_generate("<<NotebookTabClosed>>")
263
264     widget.state(['!pressed'])
265     widget._active = None
266
267 def _mouse_over(event):
268     widget = event.widget
269     element = widget.identify(event.x, event.y)
270     if "close" in element:
271         widget.state(['alternate'])
272     else:
273         widget.state(['!alternate'])
274
275 def start_up():
276     b_support.main()
277
278 if __name__ == '__main__':
279     b_support.main()
280

Of course, this is a very simple example and does not show all the possibilities.

Lines 1 through 12 are imports needed to execute Python. There are imports for Python 3 but no longer are imports provided for Python 2.

 1 #! /usr/bin/env python
 2 #  -*- coding: utf-8 -*-
 3 #
 4 # GUI module generated by PAGE version 7l
 5 #  in conjunction with Tcl version 8.6
 6 #    Jul 24, 2021 07:23:57 PM PDT  platform: Linux
 7
 8 import sys
 9 import tkinter as tk
10 import tkinter.ttk as ttk
11 from tkinter.constants import *
12 import os.path

Lines 14 and 15 calculate the location of the program in the users file system.

14 _script = sys.argv[0]
15 _location = os.path.dirname(_script)

_location is used for the creation of tkinter images used in the GUI module. This was added to allow the application to run from within an arbitrary directory. It is available to the support module for the creation of tkinter images.

Line 17 is the linkage to the support module, m_support.exe.

17 import m_support

Lines 19 through 30 defines various colors and fonts that may be used in the class definitions.

15 _bgcolor = 'wheat'  # X11 color: #f5deb3
16 _fgcolor = '#000000'  # X11 color: 'black'
17 _compcolor = '#b2c9f4' # Closest X11 color: 'SlateGray2'
18 _ana1color = '#eaf4b2' # Closest X11 color: '{pale goldenrod}'
19 _ana2color = 'beige' # X11 color: #f5f5dc
20 _tabfg1 = 'black'
21 _tabfg2 = 'black'
22 _tabbg1 = 'grey75'
23 _tabbg2 = 'grey89'
24 _bgmode = 'light'
25 font12 = "-family {DejaVu Sans} -size 12"

Lines 32 through 111 define the function “_style_code” which is executed exactly once to define ttk styles as required for the ttk widgets used in the class definitions. It is a function because the code can only be executed after tkinter is initiated. The content of “_style_code” depends depends on the repertory of ttk widgets actually used in the GUI. In this case, considerable code is required to satisfy the needs of the PNotebook widget used in this example.

 32 _style_code_ran = 0
 33 def _style_code():
 34     global _style_code_ran
 35     if _style_code_ran:
 36        return
 37     style = ttk.Style()
 38     if sys.platform == "win32":
 39        style.theme_use('winnative')
 40     style.configure('.',background=_bgcolor)
 41     style.configure('.',foreground=_fgcolor)
 42     style.configure('.',font='-family {DejaVu Sans} -size 12')
 43     style.map('.',background =
 44        [('selected', _compcolor), ('active',_ana2color)])
 45     if _bgmode == 'black':
 46        style.map('.',foreground =
 47          [('selected', 'white'), ('active','white')])
 48     else:
 49        style.map('.',foreground =
 50          [('selected', 'black'), ('active','black')])
 51     style.map('TNotebook.Tab', background =
 52             [('selected', _bgcolor), ('active', _tabbg1),
 53             ('!active', _tabbg2)], foreground =
 54             [('selected', _fgcolor), ('active', _tabfg1), ('!active',  _tabfg2)])
 55
 56     global _images
 57     _images = (
 58          tk.PhotoImage("img_close", data='''R0lGODlhDAAMAIQUADIyMjc3Nzk5OT09PT
 59                  8/P0JCQkVFRU1NTU5OTlFRUVZWVmBgYGF hYWlpaXt7e6CgoLm5ucLCwszMzNbW
 60                  1v//////////////////////////////////// ///////////yH5BAEKAB8ALA
 61                  AAAAAMAAwAAAUt4CeOZGmaA5mSyQCIwhCUSwEIxHHW+ fkxBgPiBDwshCWHQfc5
 62                   KkoNUtRHpYYAADs= '''),
 63          tk.PhotoImage("img_close_white", data='''R0lGODlhDAAMAPQfAM3NzcjI
 64                 yMbGxsLCwsDAwL29vbq6urKysrGxsa6urqmpqZ+fn56enpaWloSEhF9fX0ZGR
 65                 j09PTMzMykpKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///yH
 66                 5BAEKAB8ALAAAAAAMAAwAAAUt4CeOZGmaA5mSyQCIwhCUSwEIxHHW+fkxBgPi
 67                 BDwshCWHQfc5KkoNUtRHpYYAADs='''),
 68          tk.PhotoImage("img_closeactive", data='''R0lGODlhDAAMAIQcALwuEtIzFL46
 69                  INY0Fdk2FsQ8IdhAI9pAIttCJNlKLtpLL9pMMMNTP cVTPdpZQOBbQd60rN+1rf
 70                  Czp+zLxPbMxPLX0vHY0/fY0/rm4vvx8Pvy8fzy8P//////// ///////yH5BAEK
 71                  AB8ALAAAAAAMAAwAAAVHYLQQZEkukWKuxEgg1EPCcilx24NcHGYWFhx P0zANBE
 72                  GOhhFYGSocTsax2imDOdNtiez9JszjpEg4EAaA5jlNUEASLFICEgIAOw== '''),
 73          tk.PhotoImage("img_closepressed", data='''R0lGODlhDAAMAIQeAJ8nD64qELE
 74                  rELMsEqIyG6cyG7U1HLY2HrY3HrhBKrlCK6pGM7lD LKtHM7pKNL5MNtiViNaon
 75                   +GqoNSyq9WzrNyyqtuzq+O0que/t+bIwubJw+vJw+vTz+zT z////////yH5BAE
 76                  KAB8ALAAAAAAMAAwAAAVJIMUMZEkylGKuwzgc0kPCcgl123NcHWYW Fs6Gp2mYB
 77                  IRgR7MIrAwVDifjWO2WwZzpxkxyfKVCpImMGAeIgQDgVLMHikmCRUpMQgA7 ''')
 78         )
 79     if _bgmode == "dark":
 80         style.element_create("close", "image", "img_close_white",
 81            ('active', 'pressed',  'img_closepressed'),
 82            ('active', 'alternate', 'img_closeactive'), border=8, sticky='')
 83     else:
 84         style.element_create("close", "image", "img_close",
 85            ('active', 'pressed',  'img_closepressed'),
 86            ('active', 'alternate', 'img_closeactive'), border=8, sticky='')
 87
 88     style.layout("ClosetabNotebook", [("ClosetabNotebook.client",
 89                                  {"sticky": "nswe"})])
 90     style.layout("ClosetabNotebook.Tab", [
 91         ("ClosetabNotebook.tab",
 92           { "sticky": "nswe",
 93             "children": [
 94                 ("ClosetabNotebook.padding", {
 95                     "side": "top",
 96                     "sticky": "nswe",
 97                     "children": [
 98                         ("ClosetabNotebook.focus", {
 99                             "side": "top",
100                             "sticky": "nswe",
101                             "children": [
102                                 ("ClosetabNotebook.label", {"side":
103                                   "left", "sticky": ''}),
104                                 ("ClosetabNotebook.close", {"side":
105                                     "left", "sticky": ''}),]})]})]})])
106
107     style.map('ClosetabNotebook.Tab', background =
108         [('selected', _bgcolor), ('active', _tabbg1),
109         ('!active', _tabbg2)], foreground =
110         [('selected', _fgcolor), ('active', _tabfg1), ('!active', _tabfg2)])
111     _style_code_ran = 1

Lines 113 through 131 define the popup menu.

113 def popup1(event, *args, **kwargs):
114         font15 = "-family {Nimbus Sans L} -size 14"
115         Popupmenu1 = tk.Menu(b_support.root, tearoff=0)
116         Popupmenu1.configure(activebackground="beige")
117         Popupmenu1.configure(disabledforeground="#b8a786")
118         Popupmenu1.configure(font="-family {Nimbus Sans L} -size 14")
119         sub_menu = tk.Menu(Popupmenu1,
120                 activebackground="beige",
121                 disabledforeground="#b8a786",
122                 font="-family {Nimbus Sans L} -size 14",
123                 tearoff=0)
124         Popupmenu1.add_cascade(menu=sub_menu,
125                 compound="left",
126                 label="File")
127         sub_menu.add_command(
128                 command=b_support.quit,
129                 compound="left",
130                 label="Quit")
131         Popupmenu1.post(event.x_root, event.y_root)
132

Lines 133 through 239 define the toplevel window as the class Toplevel1.

Lines 241 through 273 is boiler plate code generated for the PNotebook widget.

Finally lines 275 through 279 launch the application.

275 def start_up():
276     b_support.main()
277
278 if __name__ == '__main__':
279     b_support.main()

Support Module

Below is the support module, example_support.py, created by PAGE and corresponds to the GUI module, example.py, above. It is a skeleton module for the for the application that you are building.

 1 #! /usr/bin/env python
 2 #  -*- coding: utf-8 -*-
 3 #
 4 # Support module generated by PAGE version 7.5d
 5 #  in conjunction with Tcl version 8.6
 6 #    May 26, 2022 07:30:34 AM PDT  platform: Linux
 7
 8 import sys
 9 import tkinter as tk
10 import tkinter.ttk as ttk
11 from tkinter.constants import *
12
13 import b
14
15 def main(*args):
16     '''Main entry point for the application.'''
17     global root
18     root = tk.Tk()
19     root.protocol( 'WM_DELETE_WINDOW' , root.destroy)
20     # Creates a toplevel widget.
21     global _top1, _w1
22     _top1 = root
23     _w1 = b.Toplevel1(_top1)
24     root.mainloop()
25
26 def quit(*args):
27     print('b_support.quit')
28     for arg in args:
29         print ('  another arg:', arg)
30     sys.stdout.flush()
31
32 if __name__ == '__main__':
33     b.start_up()
34

This module will be the home of the user coded portion of the application. Obviously, PAGE can only prepare a framework for the application. What PAGE knows about are, (1) the linkage between the GUI module and the support module, (2) the callback functions to be located in the support module, and (3) the class names of the toplevel windows.

The purpose of the support module skeleton is to allow the GUI to execute and demonstrate the generated GUI. Let me discuss the generated code.

Lines 1 through 11 are imports needed to execute Python.

 1 #! /usr/bin/env python
 2 #  -*- coding: utf-8 -*-
 3 #
 4 # Support module generated by PAGE version 7l
 5 #  in conjunction with Tcl version 8.6
 6 #    Jul 24, 2021 07:24:31 PM PDT  platform: Linux
 7
 8 import sys
 9 import tkinter as tk
10 import tkinter.ttk as ttk
11 from tkinter.constants import *

Line 11 was added because the Tk constants, such as END and NW as frequently used and this import statement makes it a bit easier.

Line 13 imports the class definitions of GUI classes defining the toplevel windows. In this case b.py, the corresponding GUI file is imported.

Lines 15 through 28 is the function main which is main entry point for the application.

15 def main(*args):
16     '''Main entry point for the application.'''
17     global root
18     root = tk.Tk()
19     root.protocol( 'WM_DELETE_WINDOW' , root.destroy)
20     # Creates a toplevel widget.
21     global _top1, _w1
22     _top1 = root
23     _w1 = b.Toplevel1(_top1)
24     root.mainloop()

Lines 17 through 20 and line 24 initialize Tkinter. Any application code required before the GUI is created should placed before line 17. Line 18 creates the toplevel window which may be thought of as the root window of the application.

Line 19

19     root.protocol( 'WM_DELETE_WINDOW' , root.destroy)

causes the function named as the second parameter to be called when the ‘x’ in the corner of the main window title bar is selected. PAGE specifies “root.destroy” which terminates Tkinter. You should change it to any termination or cleanup function your application requires upon GUI termination. Alternatively, cleanup code could be placed after line 24. Other application windows will not have a similar protocol statement; hence selecting ‘x’ in one of those windows merely destroys that window. Line 19 is present because I did not want the application user to be able to close all the GUI windows and still have the program running with no obvious way to stop it.

Lines 18 and 22 through 24 actually creates the root toplevel window with the first class defined in the GUI module at the same time as initiating Tkinter. It is treated as the main window of the GUI. As such the main window cannot be destroyed without terminating Tkinter. Terminating Tkinter does not terminate the application. That occurs when main is finished. Therefore, required termination code can be placed after in main after line

24     root.mainloop()

If you need initialization code after the GUI is created but before the application user interacts with the GUI, place it before line 24 which completes the GUI creation and the application execution is then controlled by the GUI.

The generated code for a skeletal callback functions is very simple:

30 def quit(*args):
31     print('m_support.quit')
32     for arg in args:
33         print ('another arg:', arg)
34     sys.stdout.flush()

The code generated as above is so generated that the GUI module and the support module will be an executable pair. That is, you can execute the GUI module and see what it will look like even though you have put in no additional application code. If the GUI invokes a callback, say by a button select, the program will tell you that it was invoked and will print out any arguments passed to the routine. Now you have a leg up, go program.

As a convenience, the following code is added at the bottom of the support module to aid in debugging. If you are debugging the support module and want to test its execution, you can just execute it. I found it particularly useful since I do my development in emacs where I have a key binding which will invoke python to execute the current buffer. Often that is the support module.

if __name__ == '__main__':
     example.start_up()

Some of these features are further explored in Applications with Multiple Toplevel Windows.

Updating the Support Module

You have written a substantial body of application code, and discover that you need an additional widget in the GUI module; what to do?

First invoke PAGE with the project name, add the widget with all its configuration including callbacks and Tk variables. Then generate Python code for the GUI module just like before. You sure do not want to rebuild the support module anew and erase all of your hand code. So when you tell PAGE to generate your support module, it gives you the option of updating the existing support module. If selected, PAGE will merely add skeletons for the new callbacks and add the new Tk variables. In addition, it will backup the previous version of the modules in case of failure or PAGE bugs. etc.. PAGE will keep backups to a depth specified in the preferences. See backup preferences.

For version 7, the automatic update function has been expanded to consider the addition and deletions of toplevel windows except for the root window, and the changing of the class names of the toplevel windows.

The update function no longer has anything to do with Tk variables other than those required for popup (context) menus. There is no longer a function named “set_Tk_var”.

Skeletal callback functions are simply added near the bottom of the support module. Do with them what you will.

Updating the support module prior to version 7 only added code to the module. Doing so did not change any existing code. With version 7 and the capability of adding, deleting, and renaming of toplevel windows it becomes necessary to change code in the support module when executing the update function. Remember that a goal of that function is to allow the support module to execute showing the layout and content of the GUI windows.

Creation of a new window is easy, updating just adds a little code near the bottom of main.

Changing the name of the toplevel window class leads to changing the code that creates the object to a comment and following it with a new creation line of code like:

_top3 = tk.Toplevel(root)
# _w3 = unknown.Toplevel3(_top3)
_w3 = unknown.Hello(_top3)

In this example the alias of the toplevel widget was changed from Toplevel3 to Hello. If this change were not made then the module would not execute because the toplevel class “Toplevel3” no longer exists. Note this substitution only applies to toplevel creation code that adheres to exact format of the automatically generated code. If you manually insert creation code in the support module you will have to fix it manually.

With deleting toplevel windows, things are a little stickier. A general rule I follow is to never remove code from the support module when applying updates. My first thought was to comment out the statement in the support module which creates the toplevel window. However, that statement also creates the variable _w<int> which may occur throughout the module and I am really unable to deal which that. There are just too many possibilities to handle. So I have decided that it is best if I try doing nothing. That is further justified by the expectation that deleting an existing top level will be a rare event. However, if you attempt to execute the application from a Python console by selecting the run button, you will may get a window indicating that manual changes to the support module are needed.

The toplevel window changes above rely on finding and modifying specific code patterns as generated by PAGE. It is possible that user code cleverly uses modified code sequences for manipulating toplevel windows. In such cases, all bets are off. The “clone” example is an example where a toplevel window is created by a non-standard code sequence.

Major Compatibility Issues

Version 7 of PAGE is compatible with respect to project files, mostly. This version does accept and can modify existing project files with only one problem observed. When the project file contains popup menus, it is necessary to run PAGE on the project file, select the popup file from the Widget Tree, and add the name of the popup function to the “proc” attribute in the Attribute Editor. With a legacy project those names are likely to be popup1, popup2, etc..

Generated GUI modules are quite different from the old format but that should not be a problem because they are generated automatically and should not be changed by the user. The major difference is that the GUI modules may now contain more than one class definition each of which defines a separate toplevel widget allowing a simpler way to have multiple toplevel GUI’s. Note also that all of the Tk variables required by a toplevel widget are defined as instance variables inside the class statement.

As seen above, there are significant changes to the support module which will affect the user. Let me elaborate.

First, the support module begins execution in the “main” function of the support module. This allows the user to call application code before GUI toplevel windows are created. That is, the application can now execute initialization code before doing anything with the GUI or Tkinter. A lack of this has been a shortcoming of earlier versions. Make such calls before Tkinter is initialized at the line:

root = tk.TK()

Similarly, application code can be executed after Tkinter exits at the line:

root.mainloop()

Of course, that would require Tkinter be ended with “root.destroy()” rather than “sys.exit().

Next, you will notice that the support module does not contain a skeletal init function. In earlier versions of PAGE, execution began in the GUI module and a call to the init function passed control to user generated code in the support module as well as reference variables to the toplevel widget and the class object which populate the toplevel widget. Since execution begins in “main” in the support module and references to toplevels and populating classes are already there, there is no point to build init functions.

Earlier versions used the variables “top_level” and “w”. To warn users that the PAGE variables should be used with caution, version 7 prepends “_” and appends an integer to these variables, so “top” and “w” became “_top1” and “_w1” for example. The integer is derived from the naming scheme used in the project module. GUI’s created with Version 7 will have numbers like 1, 2, 3, etc., earlier versions used a less transparent naming scheme.

The first toplevel class encountered in the GUI becomes “root” and is special in the sense that the creation of the root window initiates Tkinter and its destruction terminates Tkinter.

Earlier versions of PAGE generated a “destroy_window” function but version 7 does not. The reasons include

  1. Destroying the the root window would terminate the Tkinter, destroying the others would not.

  2. Toplevel windows already have a destroy method. So coding “_top3.destroy()” is as simple as “destroy_window3()”.

  3. Toplevel windows have a number of useful methods such as withdraw, deiconify, and a dozen more. Clearly one does not want a host of such functions gratuitously added.

  4. Consistency would require functions for each toplevel and again, that seems excessive.

The final compatibility issue: the facility for upgrading the support module will not convert an earlier support module to a version 7 support module.

The support module no longer contains the function “set_Tk_var”. Its former contents have been placed in the class function of the toplevel needing the variable. This has two benefits: + one does not have name collisions from using the same names across

toplevel widgets.

  • the Tk variables are instance variables meaning that it is possible to multiple copies of the same toplevel widget each with its own value of the Tk variables.

Migrating Existing Projects

To approach the subject of migration, let me discuss my migration of some of the examples. I will start with the simplest examples where the project contained just one toplevel window and then a multi-widget example.

With an example like “calendar”, I first opened the project file “cal.tcl” in PAGE and generated and saved the GUI module and the support module. Next I opened both the support module and the previous support module in my favorite editor and copied the various hand modified routines from the old support module into the new support module, placing them just above the line:

if __name__ == '__main__':

Since I had used the variable “w” to reference the top level window, I included the init function as well. To link between the “main” function and the init function, I added the function call:

init(_top1, _w1)

as the penultimate line of “main”. After that I only had to copy a couple of import statements:

import datetime
import calendar

and the application ran correctly. I used the generated skeleton to tell me which functions to copy from the old support module. Obviously, I had to include the whole call tree of function referred to by the newly included functions. To serve as a guide, I left in the generated skeleton functions. They are replaced by later functions of the same name.

Consider next the vrex example. Here I opened ‘vrex.tcl’ in PAGE and then used the borrow facility to bring in ‘help.tcl’ from which I did a copy and paste of the help toplevel unto the vrex toplevel. I then saved the vrex project, quit PAGE, and then reopened ‘vrex.tcl’ with PAGE and then generated and saved the GUI module and support module.

Opening the support module in an editor, I then added the functions from the old support module including init which does significant post-creation initialization and so I added the line of code

init(root, _w1)

to “main” as in the previous example.

Since I do not wish to have the help window to open until the help function is invoked, I moved the three creation statements for the help window from the main function to the help function. I then copied the support functions from help_support.py to vrex_support.py. The init function in help_support.py has only one operative statement:

load_vrex_help(w.TScrolledtext1)

I placed that line of code at the bottom of the help function changing ‘w’ to ‘_w2’. Similarly, I changed ‘top’ to ‘_top2’ in the close function.

One of the results of executing the init function is to establish global variables ‘w’ and ‘top’ as references to ‘_w1’ and ‘_top1’. This works fine for the vrex support routines and saved me from having search and change those variables in the vrex support routines. In the help support routines the variables ‘w’ and ‘top’ occurred only once so they were manually changed.

Of course, I needed to add the following import statement:

from tkinter import filedialog

In addition to the considerations above, it may be that one encounters a call back function which starts with the line:

def startup(p1, p2):

in an earlier support module where the new callback would be:

def startup(*args):

So modify the code to be of the form: