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
Destroying the the root window would terminate the Tkinter, destroying the others would not.
Toplevel windows already have a destroy method. So coding “_top3.destroy()” is as simple as “destroy_window3()”.
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.
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: