0

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.

  1. Run the python code from the App.py file
  2. In the GUI select "F to C"
  3. enter a numeric value (i.e. like 98 or 100) in the entry box
  4. press the enter key.

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)
Walt Sorensen
  • 381
  • 3
  • 14
  • Please [edit] your question to include a [mcve]. – Bryan Oakley Jul 22 '22 at 03:49
  • @BryanOakley a link to the code is provided. Run the python code from the App.py file, in the GUI enter a numeric value (i.e. like 98 or 100) in the entry box and press the enter key. When you are on "F to C" 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 press "convert" 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 with an blank entry or non-numeric value. – Walt Sorensen Jul 22 '22 at 23:57
  • Links to code on other sites is discouraged. Please add the minimal example to the question. – Bryan Oakley Jul 23 '22 at 00:10
  • @BryanOakley So post the OOP content of 4 files ranging from 15 to 56 lines each? Seems a Github link would be better than an unwieldy mess of code posted as code chunks here which doesn't necessarily keep the code properly set up. Maybe some screen shots of the GUI would help? – Walt Sorensen Jul 23 '22 at 00:17
  • No, that's not what I said. You need to create a new [mcve] that illustrates your problem. It probably won't take more than a dozen or two lines of code. Github links aren't preferable since that project could go away, or you could replace the code, which would render this question useless for others. – Bryan Oakley Jul 23 '22 at 00:20
  • @BryanOakley minimal reproducible example only works with all the code running in the GUI. Anything less would be just the single binding line ```self.bind('', lambda e: self.convert)``` same as any other Python Tkinter binding example; but has lost the context of the OOP instance(s) breaking the focus for the binding in the GUI. I've added all of the code from the github repository for reference. – Walt Sorensen Jul 23 '22 at 00:27
  • _"Code posted per moderator's request."_ - FWIW, I'm not a moderator. I'm just an ordinary user. – Bryan Oakley Jul 23 '22 at 00:38
  • @BryanOakley ok, adjusted to "community request" – Walt Sorensen Jul 23 '22 at 13:48
  • @BryanOakley since you know TK/TTK do you have any suggestions on solving this key binding / focus issue – Walt Sorensen Jul 23 '22 at 13:57
  • @BryanOakley Seems you have some level of specific knowledge of how to solve this key binding / focus issue. I found your answer to https://stackoverflow.com/questions/46345039/python-tkinter-binding-keypress-event-to-active-tab-in-ttk-notebook I believe This "binding tag" concept is likely the correct solution to this key binding / focus issue. But I'm having difficulty implementing the solution as written. I added the code but was unsuccessful (example code at end of the question) – Walt Sorensen Jul 24 '22 at 16:37

1 Answers1

0

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

Walt Sorensen
  • 381
  • 3
  • 14