Skip to content

Commit 306d8a7

Browse files
committed
Chapter 8 updates per feedback
1 parent 59fa91d commit 306d8a7

File tree

7 files changed

+118
-56
lines changed

7 files changed

+118
-56
lines changed

Chapter08/ABQ_Data_Entry/abq_data_entry/application.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -199,19 +199,19 @@ def _populate_recordlist(self):
199199

200200
def _new_record(self, *_):
201201
"""Open the record form with a blank record"""
202-
self.recordform.load_record(None, None)
202+
self.recordform.load_record(None)
203203
self.notebook.select(self.recordform)
204204

205205

206206
def _open_record(self, *_):
207-
"""Open the Record selected recordlist id in the recordform"""
207+
"""Open the selected id from recordlist in the recordform"""
208208
rowkey = self.recordlist.selected_id
209209
try:
210210
record = self.model.get_record(rowkey)
211211
except Exception as e:
212212
messagebox.showerror(
213213
title='Error', message='Problem reading file', detail=str(e)
214214
)
215-
return
216-
self.recordform.load_record(rowkey, record)
217-
self.notebook.select(self.recordform)
215+
else:
216+
self.recordform.load_record(rowkey, record)
217+
self.notebook.select(self.recordform)

Chapter08/ABQ_Data_Entry/abq_data_entry/models.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,23 +62,20 @@ def __init__(self, filename=None):
6262
def save_record(self, data, rownum=None):
6363
"""Save a dict of data to the CSV file"""
6464

65-
print(f"save row: {data}")
6665
if rownum is None:
67-
print('save new record')
6866
# This is a new record
6967
newfile = not self.file.exists()
7068

71-
with open(self.file, 'a') as fh:
69+
with open(self.file, 'a', newline='') as fh:
7270
csvwriter = csv.DictWriter(fh, fieldnames=self.fields.keys())
7371
if newfile:
7472
csvwriter.writeheader()
7573
csvwriter.writerow(data)
7674
else:
77-
print(f'update record {rownum}')
7875
# This is an update
7976
records = self.get_all_records()
8077
records[rownum] = data
81-
with open(self.file, 'w', encoding='utf-8') as fh:
78+
with open(self.file, 'w', encoding='utf-8', newline='') as fh:
8279
csvwriter = csv.DictWriter(fh, fieldnames=self.fields.keys())
8380
csvwriter.writeheader()
8481
csvwriter.writerows(records)

Chapter08/ABQ_Data_Entry/abq_data_entry/views.py

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -66,21 +66,21 @@ def __init__(self, parent, model, settings, *args, **kwargs):
6666
field_spec=fields['Time'],
6767
var=self._vars['Time'],
6868
).grid(row=0, column=1)
69+
w.LabelInput(
70+
r_info, "Technician",
71+
field_spec=fields['Technician'],
72+
var=self._vars['Technician'],
73+
).grid(row=0, column=2)
74+
# line 2
6975
w.LabelInput(
7076
r_info, "Lab",
7177
field_spec=fields['Lab'],
7278
var=self._vars['Lab'],
73-
).grid(row=0, column=2)
74-
# line 2
79+
).grid(row=1, column=0)
7580
w.LabelInput(
7681
r_info, "Plot",
7782
field_spec=fields['Plot'],
7883
var=self._vars['Plot'],
79-
).grid(row=1, column=0)
80-
w.LabelInput(
81-
r_info, "Technician",
82-
field_spec=fields['Technician'],
83-
var=self._vars['Technician'],
8484
).grid(row=1, column=1)
8585
w.LabelInput(
8686
r_info, "Seed Sample",
@@ -170,7 +170,7 @@ def __init__(self, parent, model, settings, *args, **kwargs):
170170
# Notes section -- Update grid row value for ch8
171171
w.LabelInput(
172172
self, "Notes", field_spec=fields['Notes'],
173-
var=self._vars['Notes'], input_args={"width": 85, "height": 10}
173+
var=self._vars['Notes'], input_args={"width": 85, "height": 6}
174174
).grid(sticky="nsew", row=4, column=0, padx=10, pady=10)
175175

176176
# buttons
@@ -369,7 +369,7 @@ def __init__(self, parent, *args, **kwargs):
369369
self.treeview.column(
370370
name, anchor=anchor, minwidth=minwidth,
371371
width=width, stretch=stretch
372-
)
372+
)
373373

374374
self.treeview.bind('<Double-1>', self._on_open_record)
375375
self.treeview.bind('<Return>', self._on_open_record)
@@ -389,7 +389,7 @@ def populate(self, rows):
389389
for row in self.treeview.get_children():
390390
self.treeview.delete(row)
391391

392-
cids = list(self.column_defs.keys())[1:]
392+
cids = self.treeview.cget('columns')
393393
for rownum, rowdata in enumerate(rows):
394394
values = [rowdata[cid] for cid in cids]
395395
self.treeview.insert('', 'end', iid=str(rownum),
@@ -401,6 +401,9 @@ def populate(self, rows):
401401
self.treeview.focus('0')
402402

403403
def _on_open_record(self, *args):
404-
405-
self.selected_id = int(self.treeview.selection()[0])
406404
self.event_generate('<<OpenRecord>>')
405+
406+
@property
407+
def selected_id(self):
408+
selection = self.treeview.selection()
409+
return int(selection[0]) if selection else None

Chapter08/ABQ_Data_Entry/abq_data_entry/widgets.py

Lines changed: 48 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ def _set_focus_update_var(self, event):
190190
if self.focus_update_var and not self.error.get():
191191
self.focus_update_var.set(value)
192192

193-
def _set_minimum(self, *args):
193+
def _set_minimum(self, *_):
194194
current = self.get()
195195
try:
196196
new_min = self.min_var.get()
@@ -203,7 +203,7 @@ def _set_minimum(self, *args):
203203
self.variable.set(current)
204204
self.trigger_focusout_validation()
205205

206-
def _set_maximum(self, *args):
206+
def _set_maximum(self, *_):
207207
current = self.get()
208208
try:
209209
new_max = self.max_var.get()
@@ -219,13 +219,13 @@ def _set_maximum(self, *args):
219219
def _key_validate(
220220
self, char, index, current, proposed, action, **kwargs
221221
):
222+
if action == '0':
223+
return True
222224
valid = True
223225
min_val = self.cget('from')
224226
max_val = self.cget('to')
225227
no_negative = min_val >= 0
226228
no_decimal = self.precision >= 0
227-
if action == '0':
228-
return True
229229

230230
# First, filter out obviously invalid keystrokes
231231
if any([
@@ -260,55 +260,69 @@ def _focusout_validate(self, **kwargs):
260260
max_val = self.cget('to')
261261

262262
try:
263-
value = Decimal(value)
263+
d_value = Decimal(value)
264264
except InvalidOperation:
265-
self.error.set('Invalid number string: {}'.format(value))
265+
self.error.set(f'Invalid number string: {value}')
266266
return False
267267

268-
if value < min_val:
269-
self.error.set('Value is too low (min {})'.format(min_val))
268+
if d_value < min_val:
269+
self.error.set(f'Value is too low (min {min_val})')
270+
valid = False
271+
if d_value > max_val:
272+
self.error.set(f'Value is too high (max {max_val})')
270273
valid = False
271-
if value > max_val:
272-
self.error.set('Value is too high (max {})'.format(max_val))
273274

274275
return valid
275276

277+
class ValidatedRadioGroup(ttk.Frame):
278+
"""A validated radio button group"""
279+
280+
def __init__(
281+
self, *args, variable=None, error_var=None,
282+
values=None, button_args=None, **kwargs
283+
):
284+
super().__init__(*args, **kwargs)
285+
self.variable = variable or tk.StringVar()
286+
self.error = error_var or tk.StringVar()
287+
self.values = values or list()
288+
button_args = button_args or dict()
289+
290+
for v in self.values:
291+
button = ttk.Radiobutton(
292+
self, value=v, text=v, variable=self.variable, **button_args
293+
)
294+
button.pack(side=tk.LEFT, ipadx=10, ipady=2, expand=True, fill='x')
295+
self.bind('<FocusOut>', self.trigger_focusout_validation)
296+
297+
def trigger_focusout_validation(self, *_):
298+
self.error.set('')
299+
if not self.variable.get():
300+
self.error.set('A value is required')
301+
302+
276303
class BoundText(tk.Text):
277304
"""A Text widget with a bound variable."""
278305

279306
def __init__(self, *args, textvariable=None, **kwargs):
280307
super().__init__(*args, **kwargs)
281308
self._variable = textvariable
282-
self._modifying = False
283309
if self._variable:
284310
# insert any default value
285311
self.insert('1.0', self._variable.get())
286312
self._variable.trace_add('write', self._set_content)
287313
self.bind('<<Modified>>', self._set_var)
288314

289-
def _clear_modified_flag(self):
290-
# This also triggers a '<<Modified>>' Event
291-
self.tk.call(self._w, 'edit', 'modified', 0)
292-
293315
def _set_var(self, *_):
294316
"""Set the variable to the text contents"""
295-
if self._modifying:
296-
return
297-
self._modifying = True
298-
# remove trailing newline from content
299-
content = self.get('1.0', tk.END)[:-1]
300-
self._variable.set(content)
301-
self._clear_modified_flag()
302-
self._modifying = False
317+
if self.edit_modified():
318+
content = self.get('1.0', 'end-1chars')
319+
self._variable.set(content)
320+
self.edit_modified(False)
303321

304322
def _set_content(self, *_):
305323
"""Set the text contents to the variable"""
306-
if self._modifying:
307-
return
308-
self._modifying = True
309324
self.delete('1.0', tk.END)
310325
self.insert('1.0', self._variable.get())
311-
self._modifying = False
312326

313327

314328
###########################
@@ -322,7 +336,7 @@ class LabelInput(ttk.Frame):
322336
field_types = {
323337
FT.string: RequiredEntry,
324338
FT.string_list: ValidatedCombobox,
325-
FT.short_string_list: ttk.Radiobutton,
339+
FT.short_string_list: ValidatedRadioGroup,
326340
FT.iso_date_string: DateEntry,
327341
FT.long_string: BoundText,
328342
FT.decimal: ValidatedSpinbox,
@@ -365,7 +379,9 @@ def __init__(
365379
self.label.grid(row=0, column=0, sticky=(tk.W + tk.E))
366380

367381
# setup the variable
368-
if input_class in (ttk.Checkbutton, ttk.Button, ttk.Radiobutton):
382+
if input_class in (
383+
ttk.Checkbutton, ttk.Button, ttk.Radiobutton, ValidatedRadioGroup
384+
):
369385
input_args["variable"] = self.variable
370386
else:
371387
input_args["textvariable"] = self.variable
@@ -375,12 +391,12 @@ def __init__(
375391
# for Radiobutton, create one input per value
376392
self.input = tk.Frame(self)
377393
for v in input_args.pop('values', []):
378-
button = ttk.Radiobutton(
379-
self.input, value=v, text=v, **input_args)
394+
button = input_class(
395+
self.input, value=v, text=v, **input_args
396+
)
380397
button.pack(side=tk.LEFT, ipadx=10, ipady=2, expand=True, fill='x')
381398
else:
382399
self.input = input_class(self, **input_args)
383-
384400
self.input.grid(row=1, column=0, sticky=(tk.W + tk.E))
385401
self.columnconfigure(0, weight=1)
386402

Chapter08/hierarchy_example.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import tkinter as tk
2+
from tkinter import ttk
3+
4+
berries = [
5+
{'number': '1', 'parent': '', 'value': 'Raspberry'},
6+
{'number': '4', 'parent': '1', 'value': 'Red Raspberry'},
7+
{'number': '5', 'parent': '1', 'value': 'Blackberry'},
8+
{'number': '2', 'parent': '', 'value': 'Banana'},
9+
{'number': '3', 'parent': '', 'value': 'Strawberry'}
10+
]
11+
12+
root = tk.Tk()
13+
14+
tv = ttk.Treeview(root, columns=['value'])
15+
tv.heading('#0', text='Node')
16+
tv.heading('value', text='Value')
17+
tv.grid(sticky='news')
18+
19+
for berry in berries:
20+
tv.insert(
21+
berry['parent'],
22+
'end',
23+
iid=berry['number'],
24+
text=berry['number'],
25+
values=[berry['value']]
26+
)
27+
28+
29+
30+
31+
32+
root.mainloop()

Chapter08/notebook_demo.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
banana_facts = [
1515
'Banana trees are of the genus Musa.',
1616
'Bananas are technically berries.',
17-
'All bananas contain small amounts of radioactive postssium.'
17+
'All bananas contain small amounts of radioactive potassium.'
1818
'Bananas are used in paper and textile manufacturing.'
1919
]
2020

Chapter08/treeview_demo.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
# Create and configure treeview
1313
tv = ttk.Treeview(
14-
root, columns=['size', 'modified'], selectmode='none'
14+
root, columns=['size', 'modified'], selectmode='none'
1515
)
1616
tv.heading('#0', text='Name')
1717
tv.heading('size', text='Size', anchor='center')
@@ -65,4 +65,18 @@ def sort(tv, col, parent='', reverse=False):
6565
tv.heading(cid, command=lambda col=cid: sort(tv, col))
6666

6767

68+
status = tk.StringVar()
69+
tk.Label(root, textvariable=status).pack(side=tk.BOTTOM)
70+
71+
def show_directory_stats(*_):
72+
clicked_path = Path(tv.focus())
73+
num_children = len(list(clicked_path.iterdir()))
74+
status.set(
75+
f'Directory: {clicked_path.name}, {num_children} children'
76+
)
77+
78+
79+
tv.bind('<<TreeviewOpen>>', show_directory_stats)
80+
tv.bind('<<TreeviewClose>>', lambda _: status.set(''))
81+
6882
root.mainloop()

0 commit comments

Comments
 (0)