What am I doing wrong in python focus for binding?
I'm trying to bind the button widget to keyboard enter and nothing happens, or the focus ends on the last instance of the class to be loaded.
I added self.bind('<Return>', lambda e: self.convert)
to the base code after the button.nothing.
Tried container.bind('<Return>', lambda e: self.convert)
and nothing.
I'm at a loss as to why the code isn't binding the key event other than the focus is ending up in the wrong location (focus always ends up on the last instance of the class).
This is the code I'm working from https://github.com/photodude/Tkconverter
Link in the readme to the source non-OOP tutorial and all files to the source tutorial before I modified the code to be more OOP
How do I get the focus on the "active" user visible instance of the class?
Minimal, Reproducible Example a link to the code is provided above.
When you are on "F to C" and you press enter you get a popup error "could not convert string to float" but when you are on "C to F" the correct operation (temperature conversion) occurs. The "error" that pops up on "F to C" is the same as when you click the "convert" button and the entry field is blank or has something other than a numeric value. This popup error occurs on "C to F" and press enter key with a blank entry or non-numeric value. If you are on "F to C" or "C to F", enter a numeric value (i.e. like 98 or 100) in the entry box, and click the "convert" button everything works as expected.
Expected binding behavior is for the active instance visible to the user to have the active focus for the key binding like the "convert" button has.
Code posted per community request. See Github for current code and history of attempted fixes. App.py
# source https://www.pythontutorial.net/tkinter/tkraise/
import tkinter as tk
from tkinter import ttk
from tkinter.messagebox import showerror
from ControlFrame import ControlFrame
class App(tk.Tk):
def __init__(self):
super().__init__()
self.title('Temperature Converter')
self.geometry('300x120')
self.resizable(False, False)
if __name__ == "__main__":
app = App()
ControlFrame(app)
app.mainloop()
ControFrame.py
# source https://www.pythontutorial.net/tkinter/tkraise/
import tkinter as tk
from tkinter import ttk
from ConverterFrame import ConverterFrame
from TemperatureConverter import TemperatureConverter
class ControlFrame(ttk.LabelFrame):
def __init__(self, container):
super().__init__(container)
self['text'] = 'Options'
# radio buttons
self.selected_value = tk.IntVar()
ttk.Radiobutton(
self,
text='F to C',
value=0,
variable=self.selected_value,
command=self.change_frame).grid(column=0, row=0, padx=5, pady=5)
ttk.Radiobutton(
self,
text='C to F',
value=1,
variable=self.selected_value,
command=self.change_frame).grid(column=1, row=0, padx=5, pady=5)
self.grid(column=0, row=1, padx=5, pady=5, sticky='ew')
# initialize frames
self.frames = {}
self.frames[0] = ConverterFrame(
container,
'Fahrenheit',
TemperatureConverter.fahrenheit_to_celsius
)
self.frames[1] = ConverterFrame(
container,
'Celsius',
TemperatureConverter.celsius_to_fahrenheit
)
self.change_frame()
def change_frame(self):
for frame in self.frames.values():
frame.reset()
frame.grid_remove()
frame = self.frames[self.selected_value.get()]
frame.reset()
frame.tkraise()
frame.grid()
frame.focus_set()
ConverterFrame.py
# source https://www.pythontutorial.net/tkinter/tkraise/
import tkinter as tk
from tkinter import ttk
from tkinter.messagebox import showerror
class ConverterFrame(ttk.Frame):
def __init__(self, container, unit_from, converter):
super().__init__(container)
self.unit_from = unit_from
self.converter = converter
self.master.bind('<Return>', lambda event=None: self.enter_key())
# field options
options = {'padx': 5, 'pady': 0}
# temperature label
self.temperature_label = ttk.Label(self, text=self.unit_from)
self.temperature_label.grid(column=0, row=0, sticky='w', **options)
# temperature entry
self.temperature = tk.StringVar()
self.temperature_entry = ttk.Entry(self, textvariable=self.temperature)
self.temperature_entry.grid(column=1, row=0, sticky='w', **options)
self.temperature_entry.focus()
# button
self.convert_button = ttk.Button(self, text='Convert')
self.convert_button.grid(column=2, row=0, sticky='w', **options)
self.convert_button.configure(command=self.convert)
# self.convert_button.bind('<Return>', lambda event=None: self.convert)
# result label
self.result_label = ttk.Label(self)
self.result_label.grid(row=1, columnspan=3, **options)
# add padding to the frame and show it
self.grid(column=0, row=0, padx=5, pady=5, sticky="nsew")
def convert(self, event=None):
""" Handle button click event
"""
try:
input_value = float(self.temperature.get())
result = self.converter(input_value)
self.result_label.config(text=result)
except ValueError as error:
showerror(title='Error', message=error)
def reset(self):
self.temperature_entry.delete(0, "end")
self.result_label.text = ''
def enter_key(self):
self.convert()
TemperatureConverter.py
# source https://www.pythontutorial.net/tkinter/tkraise/
class TemperatureConverter:
@staticmethod
def fahrenheit_to_celsius(f, format=True):
result = (f - 32) * 5/9
if format:
return f'{f} Fahrenheit = {result:.2f} Celsius'
return result
@staticmethod
def celsius_to_fahrenheit(c, format=True):
result = c * 9/5 + 32
if format:
return f'{c} Celsius = {result:.2f} Fahrenheit'
return result
Tried solution from Python Tkinter: Binding Keypress Event to Active Tab in ttk.Notebook as below simplified example but was unsuccessful
class ConverterFrame(ttk.Frame):
def __init__(...):
...
tag = str(self)
self._add_bindtag(self, tag)
self.bind_class(tag, '<Return>', lambda event=None: self.convert())
def _add_bindtag(self, widget, tag):
bindtags = widget.bindtags()
if tag not in bindtags:
widget.bindtags((tag,) + bindtags)
for child in widget.winfo_children():
self._add_bindtag(child, tag)
The solution here was to correct ControlFrame.py to initialize each instance of the frames on change_frame()
. the original error in the source tutorial instantiated the ConverterFrame()
class in a way that the last one would replace all previous ones inrelationship to focus and bindings.
by moving self.frames[0] = ConverterFrame(...)
... into specific functions and calling those on change_frame()
the frame replacement issue was corrected.
Full Modified code is on GitHub https://github.com/photodude/Tkconverter