I have a GUI with a column of buttons on the left side, text entry box on the right and a footer with some generic GUI buttons (new/delete/save/exit). The buttons on the left side each feed a set of words to the text entry widget on the right. I'd like to be able to use control-tab or control-shift-tab when in the text entry widget and return focus to the button feeding the text entry widget. I've googled, searched and experimented but I'm not able to find a way to force focus on a specific button after using a tab key combination.
from tkinter import *
import decimal
class category_rule():
def __init__(self,hi_range,low_range,word_list,rule,row):
self.hi_range = decimal.Decimal(hi_range)
self.low_range = decimal.Decimal(low_range)
self.word_list = sorted(word_list)
self.rule = rule
self.button_row = row
self.rule_button = None
self.rule_entry_window=None
def build_rules():
rule_dict = {}
rule_index = {}
words = []
rule='Fruit'
words=['Apple','Banana','Strawberry']
row=0
rule_dict[rule] = category_rule('9999.99','0.00',words,rule,row)
rule_index[row] = rule
rule = 'Vegetable'
words=['Asparagus','Brussel Sprouts','Kale','Tomato','Spinach']
row=1
rule_dict[rule] = category_rule('9999.99','0.00',words,rule,row)
rule_index[row] = rule
rule = 'Dessert'
words=['Cake','Pudding','Cookies','Pie']
row=2
rule_dict[rule] = category_rule('9999.99','0.00',words,rule,row)
rule_index[row] = rule
return rule_dict, rule_index
class rule_entry_window:
active_index = 0
active_rule = None
first_rule=None
button_row=0
rule_dict = {}
rule_index = {}
''' methods to create the window, build the frames add the widgets '''
def init_window_and_frames(self):
self.win = Tk()
self.bg_color = '#134E9C'
self.text_entry_frame = Frame(self.win, width=150, height=380, bg='#DDDECE', padx=1, pady=1, borderwidth=1)
self.button_frame_outer = Frame(self.win, width=150, height=300, bg=self.bg_color, padx=3, pady=3, borderwidth=1)
self.button_frame = Frame(self.button_frame_outer, width=150, height=300, bg = self.bg_color, padx=3, pady=3, borderwidth=1)
self.range_entry_frame = Frame(self.win, width=150, height=40, bg=self.bg_color, padx=3, pady=3, borderwidth=1)
self.footer_frame = Frame(self.win, width=480, height=30, bg=self.bg_color, padx=3, pady=3, borderwidth=1)
self.button_frame.grid(row=0,column=0,sticky='nesw')
self.button_frame_outer.grid(rowspan=2,column=0,sticky='nsew')
self.text_entry_frame.grid(row=1,column=1,sticky='s')
self.range_entry_frame.grid(row=0,column=1,sticky='new')
self.footer_frame.grid(row=2,columnspan=2,sticky='ew')
self.button_frame_outer.columnconfigure(0, minsize=200, weight=1)
self.button_frame_outer.rowconfigure(0, weight=1)
def init_rule_widgets(self):
self.scrollbar = Scrollbar(self.text_entry_frame)
self.text_entry = Text(self.text_entry_frame, width=40, height=33, wrap="word",
yscrollcommand=self.scrollbar.set, bg='#DDDECE',
borderwidth=0, highlightthickness=0)
self.text_entry.delete('1.0', END)
self.text_entry.grid(row=0,column=0,padx=2,pady=2)
self.text_entry.bind('<Control-Tab>',self.text_tab)
self.text_entry.bind('<Control-Shift-Tab>',self.text_tab)
self.scrollbar.grid(row=0,column=1,sticky='ns')
self.low_range_label = Label(self.range_entry_frame, text='Low Range',background=self.bg_color,fg='#ffffff')
self.low_range_label.grid(row=0, column=0,sticky='e')
self.low_range_entry = Entry(self.range_entry_frame, background='#DDDECE')
self.low_range_entry.grid(row=0,column=1)
self.hi_range_label = Label(self.range_entry_frame, text='High Range',background=self.bg_color,fg='#ffffff')
self.hi_range_label.grid(row=1, column=0,sticky='e')
self.hi_range_entry = Entry(self.range_entry_frame, background='#DDDECE')
self.hi_range_entry.grid(row=1,column=1)
def init_footer_buttons(self):
self.new_rule_button = Button(self.footer_frame, width=10, height=1, padx=2, pady=1, name='new button',
bg=self.bg_color, fg='#ffffff', bd=1, text='New', command=self.new_rule)
self.footer_frame.grid_columnconfigure(2, weight=0)
self.new_rule_button.grid(row=0,column=0,sticky='w')
self.delete_rule_button = Button(self.footer_frame, width=10, height=1, padx=2, pady=1, name='delete button',
bg=self.bg_color, fg='#ffffff', bd=1, text='Delete', command=self.delete_rule)
self.footer_frame.grid_columnconfigure(2, weight=0)
self.delete_rule_button.grid(row=0,column=1,sticky='w')
self.exit_button = Button(self.footer_frame, width=10, height=1, padx=2, pady=1, name='exit_button',
bg=self.bg_color, fg='#ffffff', bd=1, text='Exit', command=self.exit)
self.footer_frame.grid_columnconfigure(2, weight=1)
self.exit_button.grid(row=0,column=3,sticky='e')
self.save_button = Button(self.footer_frame, width=10, height=1, padx=2, pady=1, name='save button',
bg=self.bg_color, fg='#ffffff', bd=1, text='Save', command=self.save_rule)
self.footer_frame.grid_columnconfigure(2, weight=1)
self.save_button.grid(row=0,column=4,sticky='e')
def add_rule_button(self,rule):
self.rule_dict[rule].rule_button = Button(self.button_frame, name='b'+rule, width=30, height=1, justify=LEFT, padx=0, pady=2,
font=('Arial', 9), fg='#ffffff',bg='#134E9C', bd=0, anchor='nw',text=rule,
highlightcolor='#134E9C',command=lambda r=rule: self.fill_text_entry_window(r))
self.rule_dict[rule].rule_button.grid(row=self.rule_dict[rule].button_row,column=0,sticky='n')
self.rule_dict[rule].rule_button.bind('<Tab>',self.button_tab)
self.rule_dict[rule].rule_button.bind('<Shift-Tab>',self.button_tab)
self.rule_dict[rule].rule_button.bind('<Return>',self.focus_text_entry)
self.rule_dict[rule].rule_button.bind('<Right>',self.focus_text_entry)
self.rule_dict[rule].rule_button.bind('<Up>',self.button_uparrow)
self.rule_dict[rule].rule_button.bind('<Down>',self.button_downarrow)
''' init. call the methods to build the window, set the active button and call mainloop '''
def __init__(self):
self.init_window_and_frames()
self.init_rule_widgets()
self.init_footer_buttons()
self.rule_dict,self.rule_index = build_rules()
for rule in self.rule_dict:
self.add_rule_button(rule)
self.active_index = 0
self.active_rule = None
self.fill_text_entry_window(self.rule_index[self.active_index])
self.win.mainloop()
''' methods to process activity from the widgets '''
@classmethod
def new_rule(cls):
print('new rule - Not written yet')
@classmethod
def delete_rule(cls):
print('delete rule - Not written yet')
@classmethod
def exit(cls):
print('exit - Not written yet')
@classmethod
def save_rule(cls):
print('save - Not written yet')
''' process keyboard events'''
def button_uparrow(self,event):
if self.active_index==0:
next_rule=self.rule_index[len(self.rule_index)-1]
else:
next_rule=self.rule_index[self.active_index-1]
self.fill_text_entry_window(next_rule)
def button_downarrow(self,event):
if self.active_index+1==len(self.rule_index):
next_rule=self.rule_index[0]
else:
next_rule=self.rule_index[self.active_index+1]
self.fill_text_entry_window(next_rule)
def button_tab(self,event):
'''
make the next button active
event.state == 9 means the <shift> key is pressed. 8 is shift key is not pressed
'''
if event.state == 9:
if self.active_index==0:
next_rule=self.rule_index[len(self.rule_index)-1]
else:
next_rule=self.rule_index[self.active_index-1]
else:
if self.active_index==self.active_index==len(self.rule_index)-1:
next_rule=self.rule_index[0]
else:
next_rule=self.rule_index[self.active_index+1]
self.fill_text_entry_window(next_rule)
def focus_text_entry(self,event):
self.text_entry_frame.focus_set()
self.text_entry.focus()
def text_tab(self,event):
'''
process control-tab, control-shift-tab key combinations from text_entry widget.
Attempts to set focus back to the active button
*** doesn't work
'''
self.update_rule()
self.win.focus_set()
active_rule=self.rule_index[self.active_index]
self.footer_frame.tkraise()
self.range_entry_frame.tkraise()
''' self.button_frame_outer.tkraise() '''
self.button_frame.focus_force()
self.button_frame.tkraise()
self.button_frame_outer.tkraise()
self.button_frame.focus_force()
self.rule_dict[active_rule].rule_button.focus_force()
self.rule_dict[active_rule].rule_button.focus_set()
self.rule_dict[active_rule].rule_button.focus()
self.fill_text_entry_window(active_rule)
def fill_text_entry_window(self,selected_rule):
if self.active_rule is None:
self.active_rule = selected_rule
elif self.active_rule is not selected_rule:
self.update_rule()
''' remove highlight from the old button. Add highlight to the new one '''
self.rule_dict[self.active_rule].rule_button.config(bg='#134E9C',fg='#ffffff')
self.active_rule = self.rule_dict[selected_rule].rule
self.active_index = self.rule_dict[selected_rule].button_row
self.rule_dict[self.active_rule].rule_button.config(bg='#ffffff',fg='#134E9C')
''' clear the entry widgets and add the information for the new button '''
self.low_range_entry.delete(0,END)
self.low_range_entry.insert(0,self.rule_dict[self.active_rule].low_range)
self.hi_range_entry.delete(0,END)
self.text_entry.delete('1.0', END)
self.hi_range_entry.insert(0,self.rule_dict[self.active_rule].hi_range)
for word in self.rule_dict[self.active_rule].word_list:
self.text_entry.insert('insert', word+'\n')
''' *** attempt to set the focus on the new button. '''
self.rule_dict[self.active_rule].rule_button.focus()
self.rule_dict[self.active_rule].rule_button.focus_set()
self.rule_dict[self.active_rule].rule_button.focus_force()
def update_rule(self):
''' save any changes made to the active rule before changing to the next one '''
self.rule_dict[self.active_rule].word_list = sorted(self.text_entry.get(1.0,END).rstrip().split('\n'))
self.rule_dict[self.active_rule].hi_range = decimal.Decimal(self.hi_range_entry.get())
self.rule_dict[self.active_rule].low_range = decimal.Decimal(self.low_range_entry.get())
if __name__ == '__main__':
rule_entry_window()
text_tab and fill_text_entry_window are the methods I'm using to try and set the focus back on the selected button. I haven't found a combination of tkraise and focus/focus_force/focus_set that sets the focus back to the button I'd like it to be on. What can I do to force the focus back onto the button?