Exception deep within kivy in certain focus changes

52 views
Skip to first unread message

John Perry

unread,
Oct 24, 2025, 12:21:00 PMOct 24
to Kivy users support
Hi

I've observed a problem that I think is a bug, but I want to run it by the folks here to see if there's an issue with my usage. I've worked hard to get an MWE; see below. To reproduce the problem, click first on the text input with hint text "one", then click on the text input with hint text "three". The result is:

Traceback (most recent call last):
  File "<frozen runpy>", line 198, in _run_module_as_main
  File "<frozen runpy>", line 88, in _run_code
...[snip -- you can recreate by running the MWE]...
  File "/home/jperry/.local/lib/python3.11/site-packages/kivy/uix/behaviors/focus.py", line 412, in _on_focus
    self._unbind_keyboard()
  File "/home/jperry/.local/lib/python3.11/site-packages/kivy/uix/textinput.py", line 3057, in _unbind_keyboard
    super()._unbind_keyboard()
  File "/home/jperry/.local/lib/python3.11/site-packages/kivy/uix/behaviors/focus.py", line 454, in _unbind_keyboard
    del FocusBehavior._keyboards[keyboard]
        ~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^
KeyError: <kivy.core.window.Keyboard object at 0x7f362d22e190>

You don't have to type anything for the failure to occur, and in any case typing doesn't help.

As far as I can tell, the problem goes away only if I remove either the validation or the focus monitoring, but I'm pretty sure I need both, since (a) I need to validate / modify the input for both Tab and Enter, (b) when the user pressed Enter, I need the indicated text input to acquire focus, and (c) I also need the grouping of text inputs, which seems to be related to the problem -- once I take that grouping away, the problem goes away.

Any advice would be appreciated, especially if there's a smarter way to do what I'm trying to do. If it is indeed a bug, I'll report it.

thanks in advance
john perry

# interface.kv

#:set CHAR_HEIGHT 30
#:set CHAR_WIDTH 10

<SingleLineInput@TextInput>:

text: ''
multiline: False
write_tab: False

Root:

BoxLayout:
size_hint_y: 0.1

SingleLineInput:

id: one
hint_text: 'one'
focus_next: two.__self__
on_focus: root.focused(self)
on_text_validate: root.validate(self, self.text)

SingleLineInput:

id: two
hint_text: 'two'
focus_next: one.__self__
on_focus: root.focused(self)
on_text_validate: root.validate(self, self.text)

SingleLineInput:

id: three
hint_text: 'three'
on_focus: root.focused(self)
on_text_validate: root.validate(self, self.text)



#main.py

from kivy.app import App
from kivy.factory import Factory

from kivy.uix.floatlayout import FloatLayout


class Root(FloatLayout):

def __init__(self, **kwargs) -> None:
super(Root, self).__init__(**kwargs)

def focus_first(self, _dt) -> None:
self.ids.one.focus = True

def validate(self, widget, value) -> None:
if widget == self.ids.one:
self.ids.one.text = value.capitalize()
self.ids.two.focus = True
if widget == self.ids.two:
self.ids.two.text = value.capitalize()
self.ids.one.focus = True
if widget == self.ids.three:
self.ids.three.text = value.capitalize()

def focused(self, widget) -> bool:
if not widget.focus:
self.validate(widget, widget.text)
return True


class Interface(App):
pass


Factory.register('Root', cls=Root)

if __name__ == '__main__':
Interface().run()


ElliotG

unread,
Oct 25, 2025, 10:17:19 AMOct 25
to Kivy users support
The focus behavior maintains a list, rather than setting focus dynamically, you can set the focus_next property.
Also there is no need to Register Root.  It is registered automatically..

If the goal is to capitalize the input, you can use the on_text event of the TextInput.  This will let you remove the on_focus and on_text_validate calls.  This create a better user experience.
<SingleLineInput@TextInput>:
    text: ''
    multiline: False
    write_tab: False
    on_text: self.text = self.text.capitalize()

Below are the update files.


# interface.kv

#:set CHAR_HEIGHT 30
#:set CHAR_WIDTH 10

<SingleLineInput@TextInput>:
    text: ''
    multiline: False
    write_tab: False

Root:
    BoxLayout:
        size_hint_y: 0.1

        SingleLineInput:
            id: one
            hint_text: 'one'
            focus_next: two.__self__
            on_focus: root.focused(self)
            on_text_validate: root.validate(self, self.text)
            focus_next: two


        SingleLineInput:
            id: two
            hint_text: 'two'
            focus_next: one.__self__
            on_focus: root.focused(self)
            on_text_validate: root.validate(self, self.text)
            focus_next: one


        SingleLineInput:
            id: three
            hint_text: 'three'
            on_focus: root.focused(self)
            on_text_validate: root.validate(self, self.text)


from kivy.app import App
# from kivy.factory import Factory


from kivy.uix.floatlayout import FloatLayout


class Root(FloatLayout):

    def __init__(self, **kwargs) -> None:
        super(Root, self).__init__(**kwargs)

    def focus_first(self, _dt) -> None:
        self.ids.one.focus = True


    def validate(self, widget, value) -> None:
        # if widget == self.ids.one:
        #     self.ids.one.text = value.capitalize()
        #     # self.ids.two.focus = True
        # if widget == self.ids.two:
        #     self.ids.two.text = value.capitalize()
        #     # self.ids.one.focus = True
        # if widget == self.ids.three:
        #     self.ids.three.text = value.capitalize()
        p = self.ids
        if widget in {p.one, p.two, p.three}:
            widget.text = value.capitalize()


    def focused(self, widget) -> bool:
        if not widget.focus:
            self.validate(widget, widget.text)
        return True


class Interface(App):
    pass


# Factory.register('Root', cls=Root)


if __name__ == '__main__':
    Interface().run()

John Perry

unread,
Oct 27, 2025, 9:48:47 AMOct 27
to Kivy users support
Hi ElliotG

Thank you, but your proposal doesn't satisfy requirement (b): when the user presses Enter, I need the indicated text input to acquire focus. As far as I can tell, no element has focus after the user presses Enter.

ElliotG

unread,
Oct 27, 2025, 11:16:05 AMOct 27
to Kivy users support
I've added setting the focus on enter:

from kivy.app import App

from kivy.uix.floatlayout import FloatLayout


class Root(FloatLayout):

    def __init__(self, **kwargs) -> None:
        super(Root, self).__init__(**kwargs)

    def focus_first(self, _dt) -> None:
        self.ids.one.focus = True


    def validate(self, widget) -> None:
        if widget == self.ids.one:

            self.ids.two.focus = True
        if widget == self.ids.two:
            self.ids.one.focus = True


    def focused(self, widget) -> bool:
        if not widget.focus:
            self.validate(widget, widget.text)
        return True


class Interface(App):
    pass


if __name__ == '__main__':
    Interface().run()



============================
# interface.kv

#:set CHAR_HEIGHT 30
#:set CHAR_WIDTH 10

<SingleLineInput@TextInput>:
    text: ''
    multiline: False
    write_tab: False
    on_text: self.text = self.text.capitalize()


Root:
    BoxLayout:
        size_hint_y: 0.1

        SingleLineInput:
            id: one
            hint_text: 'one'
            on_text_validate: root.validate(self)

            focus_next: two

        SingleLineInput:
            id: two
            hint_text: 'two'
            on_text_validate: root.validate(self)

            focus_next: one

        SingleLineInput:
            id: three
            hint_text: 'three'

John Perry

unread,
Oct 28, 2025, 5:28:48 PMOct 28
to Kivy users support
Thank you again! This seems to work, and it should satisfy my purposes. However, it validates whenever the user presses a key. In principle, it should work only when the user presses Tab or Enter. If validation requires a time-consuming computation, it would be a terrible idea to validate on every keypress. Is there a way to make it work without that?

elli...@cox.net

unread,
Oct 28, 2025, 5:39:18 PMOct 28
to kivy-...@googlegroups.com
It is not validating on evey key, it is running capitalize() on every key.    I think that creates a better user experience.  You could move the capialitze out of on_text, and into the validate method if you prefer.

From: kivy-...@googlegroups.com <kivy-...@googlegroups.com> on behalf of John Perry <cantani...@gmail.com>
Sent: Tuesday, October 28, 2025 2:28 PM
To: Kivy users support <kivy-...@googlegroups.com>
Subject: [kivy-users] Re: Exception deep within kivy in certain focus changes
 
--
You received this message because you are subscribed to the Google Groups "Kivy users support" group.
To unsubscribe from this group and stop receiving emails from it, send an email to kivy-users+...@googlegroups.com.
To view this discussion visit https://groups.google.com/d/msgid/kivy-users/1eb8c16d-f6bc-44b2-9ca5-f91d38e687b3n%40googlegroups.com.

John Perry

unread,
Oct 28, 2025, 7:10:01 PMOct 28
to Kivy users support
Sure, but if I move it out of on_text and into validate, it no longer capitalizes (i.e., validates) when I press Tab, which is something I need. I could uncomment the on_focus for the TextInputs, but then we're back where we started with the crashing.

ElliotG

unread,
Oct 28, 2025, 7:47:49 PMOct 28
to Kivy users support
The code below works... I changed the name validate() to focus_next() to match it's role.
Is this what your looking for? 


from kivy.app import App
from kivy.uix.boxlayout import BoxLayout


class Root(BoxLayout):
    def focus_next(self, widget) -> None:

        if widget == self.ids.one:
            self.ids.two.focus = True
        if widget == self.ids.two:
            self.ids.one.focus = True


class Interface(App):
    pass


if __name__ == '__main__':
    Interface().run()


======================================
# interface.kv
<SingleLineInput@TextInput>:
    multiline: False
    write_tab: False
    on_focus: if not self.focus: self.text = self.text.capitalize()
    on_text_validate: app.root.focus_next(self)

BoxLayout:
    size_hint_y: None
    height: dp(48)


    SingleLineInput:
        id: one
        hint_text: 'one'
        focus_next: two

    SingleLineInput:
        id: two
        hint_text: 'two'
        focus_next: one

    SingleLineInput:
        id: three
        hint_text: 'three'

ElliotG

unread,
Oct 28, 2025, 8:03:52 PMOct 28
to Kivy users support
I was thinking that another way to fix the problem was to delay calling validate to it is called after the focus change from on_touch_validate had ended.
This also works.
from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.clock import Clock


class Root(FloatLayout):


    def validate(self, widget, value) -> None:
        print('validate called')

        if widget == self.ids.one:
            self.ids.one.text = value.capitalize()
            self.ids.two.focus = True
        if widget == self.ids.two:
            self.ids.two.text = value.capitalize()
            self.ids.one.focus = True
        if widget == self.ids.three:
            self.ids.three.text = value.capitalize()

    def focused(self, widget) -> bool:
        if not widget.focus:
            # delay validate by one clock... 
            Clock.schedule_once(lambda dt: self.validate(widget, widget.text))



class Interface(App):
    pass


if __name__ == '__main__':
    Interface().run()

==========================================
# interface.kv

#:set CHAR_HEIGHT 30
#:set CHAR_WIDTH 10

<SingleLineInput@TextInput>:
    multiline: False
    write_tab: False

Root:
    BoxLayout:
        size_hint_y: 0.1

        SingleLineInput:
            id: one
            hint_text: 'one'
            focus_next: two.__self__
            on_focus: root.focused(self)
            on_text_validate: root.validate(self, self.text)

        SingleLineInput:
            id: two
            hint_text: 'two'
            focus_next: one.__self__
            on_focus: root.focused(self)
            on_text_validate: root.validate(self, self.text)

        SingleLineInput:
            id: three
            hint_text: 'three'
            on_focus: root.focused(self)
            on_text_validate: root.validate(self, self.text)

John Perry

unread,
Oct 28, 2025, 8:54:32 PMOct 28
to Kivy users support
This code crashes when I press "Enter" in a box:
AttributeError: 'BoxLayout' object has no attribute 'focus_next'

John Perry

unread,
Oct 28, 2025, 8:56:45 PMOct 28
to Kivy users support
...and this code, whenever I do something in box A or B, then click in box C, immediately focuses on one of A or B.

ElliotG

unread,
Oct 29, 2025, 11:24:24 AMOct 29
to Kivy users support
 I must not have been running the correct code... embarrassing.

Here is a fix for the first one:

from kivy.app import App
from kivy.uix.boxlayout import BoxLayout


class Root(BoxLayout):
    def focus_next(self, widget) -> None:
        if widget == self.ids.one:
            self.ids.two.focus = True
        if widget == self.ids.two:
            self.ids.one.focus = True


class Interface(App):
    pass


if __name__ == '__main__':
    Interface().run()

==============================
# interface.kv
<SingleLineInput@TextInput>:
    multiline: False
    write_tab: False
    on_focus: if not self.focus: self.text = self.text.capitalize()
    on_text_validate: app.root.focus_next(self)

Root:

    size_hint_y: None
    height: dp(48)

    SingleLineInput:
        id: one
        hint_text: 'one'
        focus_next: two

    SingleLineInput:
        id: two
        hint_text: 'two'
        focus_next: one

    SingleLineInput:
        id: three
        hint_text: 'three'

ElliotG

unread,
Oct 29, 2025, 11:44:29 AMOct 29
to Kivy users support
and here is a fix for the second version using the delay...

from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.clock import Clock


class Root(FloatLayout):
    def validate(self, widget) -> None:
        if not widget.focus:
            widget.text = widget.text.capitalize()
            return

        if widget == self.ids.one:
            self.ids.two.focus = True
        if widget == self.ids.two:
            self.ids.one.focus = True

    def focused(self, widget):
        if not widget.focus:
            Clock.schedule_once(lambda dt: self.validate(widget))



class Interface(App):
    pass


if __name__ == '__main__':
    Interface().run()
=========================================
# interface.kv

<SingleLineInput@TextInput>:
    multiline: False
    write_tab: False
    on_focus: app.root.focused(self)
    on_text_validate: app.root.validate(self)


Root:
    BoxLayout:
        size_hint_y: 0.1

        SingleLineInput:
            id: one
            hint_text: 'one'
            focus_next: two

        SingleLineInput:
            id: two
            hint_text: 'two'
            focus_next: one

        SingleLineInput:
            id: three
            hint_text: 'three'

John Perry

unread,
Oct 30, 2025, 11:01:08 AMOct 30
to Kivy users support
Thank you, that first version seems to work very well, even when I extend it. It's a clever solution, as well.

That aside, was there anything specifically wrong about the approach I used in the OP? That is, should I report it as a bug? If nothing else, it seems that kivy shouldn't crash gracelessly that way; it should log an error, or something.

elli...@cox.net

unread,
Oct 30, 2025, 11:07:36 AMOct 30
to kivy-...@googlegroups.com
Feel free to report an issue on the kivy github.  FWIW I'm not sure what the fix would be - if nothing else a more useful error message would be helpful.

It seems to me that the way the code was setting focus was causing a conflict.
When the focus changes the widget that loses focus fires, and the next clock the widget that get focus fires.  There was a problem in there somewhere.

Sent: Thursday, October 30, 2025 8:01 AM

To: Kivy users support <kivy-...@googlegroups.com>
Subject: Re: [kivy-users] Re: Exception deep within kivy in certain focus changes
 

John Perry

unread,
Oct 30, 2025, 11:13:16 AMOct 30
to Kivy users support
> When the focus changes the widget that loses focus fires, and the next clock the widget that get focus fires.  There was a problem in there somewhere.

That was my impression, too. Ultimately I think on_text_validate should automatically be called when an item loses focus, since information may have changed, and it is a common practice to trigger action on focus changes. But I could see how it might be too late to change that now.

I'll report the issue, if only in the hope that it handles things better than crashing. Thanks again.
Reply all
Reply to author
Forward
0 new messages