There is a Frame which contains number of Label and Text widgets. Those are generated dynamically from JSON document.
What I want is to set the focus on the first Text widget in the Frame. The name of that Text widget depends on JSON document, so I can't just get it with Frame.children['name']
.
Here's a function that makes the work. But I wonder if there's more convenient way to do so. I thought about getting the first widget in keyboard traversal hierarchy, but couldn't find any example. Please come up with any suggestion. Thanks.
def focus_to_child(event):
for k, v in event.widget.children.items():
if type(v) is Text:
break
try:
event.widget.children[k].focus()
except UnboundLocalError:
pass
Minimal reproducible example. There're far less widgets in the example below than I have in my project. So a first Text widget is the next after Treeview in a keyboard traversal hierarchy. Don't let this fact confuse you =)
from random import randint as ri
from tkinter import Tk, ttk, Text
# here's example struct and values, in project MAP is initialized from JSON documents
MAP = {'_a_A': {'_id': '_a_A', 'name': chr(ri(97, 122)), 'quit_prob': 0.1, 'changed': False},
'_a_B': {'_id': '_a_B', 'name': chr(ri(97, 122)), 'quit_prob': 0.25, 'changed': False},
'_x_Z': {'_id': '_x_Z', 'name': chr(ri(97, 122)), 'quit_prob': 0.75, 'changed': True}}
# Treeview items selection event handle
def tv_sel_handler(event):
try:
# item id of the document that user has selected
sel_iid = event.widget.selection()[0]
except IndexError:
sel_iid = ''
try:
generate_entries(MAP[sel_iid])
# here we should set focus to the first Text in the data_entries Frame
focus_to_child()
except KeyError:
pass
# this is my option at the moment, but I doubt that it's the most convenient method
def focus_to_child():
for k, v in data_entries.children.items():
if type(v) is Text:
break
try:
data_entries.children[k].focus()
except UnboundLocalError:
pass
# generate Entries for selected document fields
def generate_entries(document):
def focus_widget(event):
if event.state == 8:
event.widget.tk_focusNext().focus()
elif event.state == 9:
event.widget.tk_focusPrev().focus()
return 'break'
row = 0
for field in document.keys():
# label
ttk.Label(data_entries, text=field).grid(row=row, column=0, sticky='nw')
# widget to edit document fields
tf = Text(data_entries, name=field, wrap='word', width=20, height=2)
tf.insert('1.0', document[field])
tf.grid(row=row 1, sticky='nwe', padx=0, pady=5)
tf.bind('<Tab>', focus_widget)
tf.bind('<Shift-Tab>', focus_widget)
tf.bind('<FocusIn>', lambda event: event.widget.tag_add('sel', '1.0', 'end'))
# separator
ttk.Label(data_entries).grid(row=row 2)
row = 3
root = Tk()
# list of documents
data_docs = ttk.Treeview(root, selectmode='browse', show=('tree',))
data_docs.column('#0', width=220)
data_docs.grid(row=0, column=0, sticky='nsw')
data_docs.bind('<<TreeviewSelect>>', tv_sel_handler)
for doc in MAP.values():
data_docs.insert('', 'end', doc['_id'], text=doc['_id'][3:])
# frame for dynamic entries
data_entries = ttk.Frame(root, width=1000, height=450, padding=(15,0))
data_entries.grid(row=0, column=1, sticky='nsew')
root.mainloop()
CodePudding user response:
I don't know about convenient, but a more direct and efficient way to do it would be by simply remembering what the first text widget is when they're created — which is very easy to do (see the ALL CAPS COMMENTS below):
# generate Entries for selected document fields
def generate_entries(document):
def focus_widget(event):
if event.state == 8:
event.widget.tk_focusNext().focus()
elif event.state == 9:
event.widget.tk_focusPrev().focus()
return 'break'
row = 0
for field in document.keys():
# label
ttk.Label(data_entries, text=field).grid(row=row, column=0, sticky='nw')
# widget to edit document fields
tf = Text(data_entries, name=field, wrap='word', width=20, height=2)
if row == 0: # FIRST TEXT?
data_entries.first_text = tf # REMEMBER IT.
tf.insert('1.0', document[field])
tf.grid(row=row 1, sticky='nwe', padx=0, pady=5)
tf.bind('<Tab>', focus_widget)
tf.bind('<Shift-Tab>', focus_widget)
tf.bind('<FocusIn>', lambda event: event.widget.tag_add('sel', '1.0', 'end'))
# separator
ttk.Label(data_entries).grid(row=row 2)
row = 3
Then there will no longer by a need to search for it via a for
loop:
def focus_to_child():
try:
data_entries.first_text.focus()
except AttributeError:
pass