Kivy: How to reliably set [keyboard] focus on-mouse-down

239 views
Skip to first unread message

Klaas van Schelven

unread,
Jan 16, 2017, 5:54:38 AM1/16/17
to Kivy users support
Hi all,


I'm trying to assign focus to a widget when it's clicked. I am only partially successful in doing so.

Attempt 1:

class TreeWidget(Widget, FocusBehavior):

    def __init__(self, **kwargs):
        super(TreeWidget, self).__init__(**kwargs)

    def on_touch_down(self, touch):
        ret = super(TreeWidget, self).on_touch_down(touch)

        if not self.collide_point(*touch.pos):
            return ret

        self.focus = True

        # ...

        return ret

This sets the focus to the widget for the brief moment between mouse-down and mouse-up.

Attempt 2:

Attempt 2 was to add the below code below; this doesn't help in any (visible) way; i.e. after releasing the mouse my widget still has no focus.

class ...

    def on_touch_up(self, touch):
        if self.collide_point(*touch.pos):
            self.focus = True

        ret = super(TreeWidget, self).on_touch_down(touch)
        return ret  
        return True #  (attempt 2b, also unsuccessful)

Attempt 3:

class TreeWidget(Widget, FocusBehavior):

    def __init__(self, **kwargs):
        super(TreeWidget, self).__init__(**kwargs)

    def on_touch_down(self, touch):
        ret = super(TreeWidget, self).on_touch_down(touch)

        if not self.collide_point(*touch.pos):
            return ret

        touch.grab(self)

        self.focus = True

        # ...

        return ret

    def on_touch_up(self, touch):
        # Taken from the docs: https://kivy.org/docs/guide/inputs.html#grabbing-touch-events
        if touch.grab_current is self:

            # ok, the current touch is dispatched for us.
            # do something interesting here
            print('Hello world!')
            self.focus = True

            # don't forget to ungrab ourself, or you might have side effects
            touch.ungrab(self)

            # and accept the last up
            return True

Although this solution works, I don't understand why. This means I'm sure to run into a related problem very soon. Could someone explain to me what I'm doing wrong (or right). In particular, what's causing the loss of focus on mouse up (in all the solutions) and why isn't this repaired in solution 2?

Context: a desktop application on Kivy v1.9.1, Python v3.4.3.


Answers are much appreciated!

Klaas

ZenCODE

unread,
Jan 16, 2017, 12:44:15 PM1/16/17
to Kivy users support
It should not be that hard? And I doubt you need to grab it (not knowing your code). I think the trick is returning True to prevent the event from bubbling. How about:

    def on_touch_down(self, touch):        
        if self.collide_point(*touch.pos):
self.focus = True
return True
else:
return super(TreeWidget, self).on_touch_down(touch)
That should work, unless other widgets do funny stuff with the grab of on_touch_up?

Klaas van Schelven

unread,
Jan 23, 2017, 5:04:26 AM1/23/17
to Kivy users support
I agree it shouldn't be that hard, but it's not working for me.
Note that 'touch down" seems to be working fine. If I start typing while holding the mouse button it's obvious that I still have focus.
However, when I release the focus is lost, except in the case "attempt 3"
You're right that I don't think grabbing should be necessary... that's the whole point of my question.

The code is basically what you see in the question. This is in a widget that lives, together with one one other widget, in a simple test app.
One error I did spot is: in "attempt 2" the super call should be to touch_up... I reran the test after fixing that but with the same result.

Any help is much appreciated!

Klaas van Schelven

unread,
Jan 23, 2017, 5:24:00 AM1/23/17
to Kivy users support
from sys import argv

from kivy.app import App

from kivy.uix.widget import Widget
from kivy.uix.behaviors.focus import FocusBehavior


class Attempt(Widget, FocusBehavior):
    def keyboard_on_key_down(self, window, keycode, text, modifiers):
        result = FocusBehavior.keyboard_on_key_down(self, window, keycode, text, modifiers)

        code, textual_code = keycode
        print("I appear to have focus", code)

        return result


class Attempt1(Attempt):

    def on_touch_down(self, touch):
        ret = super(Attempt1, self).on_touch_down(touch)

        if not self.collide_point(*touch.pos):
            return ret

        self.focus = True
        return ret


class Attempt2(Attempt):

    def on_touch_down(self, touch):
        ret = super(Attempt2, self).on_touch_down(touch)

        if not self.collide_point(*touch.pos):
            return ret

        self.focus = True
        return ret

    def on_touch_up(self, touch):
        if self.collide_point(*touch.pos):
            self.focus = True

        ret = super(Attempt2, self).on_touch_up(touch)
        return ret


class Attempt2b(Attempt):

    def on_touch_down(self, touch):
        ret = super(Attempt2b, self).on_touch_down(touch)

        if not self.collide_point(*touch.pos):
            return ret

        self.focus = True
        return ret

    def on_touch_up(self, touch):
        if self.collide_point(*touch.pos):
            self.focus = True

        super(Attempt2b, self).on_touch_up(touch)
        return True


class Attempt3(Attempt):

    def on_touch_down(self, touch):
        ret = super(Attempt3, self).on_touch_down(touch)

        if not self.collide_point(*touch.pos):
            return ret

        touch.grab(self)

        self.focus = True

        # ...

        return ret

    def on_touch_up(self, touch):
        if touch.grab_current is self:
            self.focus = True

            # don't forget to ungrab ourself, or you might have side effects
            touch.ungrab(self)

            # and accept the last up
            return True


class TestApp(App):

    def build(self):
        d = {
            '1': Attempt1,
            '2': Attempt2,
            '2b': Attempt2b,
            '3': Attempt3,
        }
        clazz = d[argv[1]] if len(argv) > 0 else Attempt1

        return clazz()


def main():
    TestApp().run()


if __name__ == "__main__":
    main()



I've included a program that demonstrates what I'm talking about.
In all cases except the third attempt typing only results in the print-outs between mouse down and mouse up.

Help is much appreciated!

Klaas

ZenCODE

unread,
Jan 23, 2017, 2:51:48 PM1/23/17
to Kivy users support
If I run as is, I get:


     clazz = d[argv[1]] if len(argv) > 0 else Attempt1
 IndexError: list index out of range

If I make than > 1, I just get a black screen? And the build method does not return a widget?? 

ZenCODE

unread,
Jan 23, 2017, 2:53:46 PM1/23/17
to Kivy users support
Okay, it does, but it still all black.

ZenCODE

unread,
Jan 23, 2017, 3:28:05 PM1/23/17
to Kivy users support
I think there is some misunderstanding here about the FocusBehavior. Have a look here. All very simple, seems to do what you want?

from kivy.lang import Builder
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.behaviors import FocusBehavior


kv = '''
<FocusLabel>:
canvas.before:
Color:
rgba: [0, 1, 0, 0.25] if self.focus else [0, 0, 0, 0]
Rectangle:
pos: self.pos
size: self.size

BoxLayout:
orientation: 'horizontal'
FocusLabel:
FocusLabel:
'''


class FocusLabel(FocusBehavior, Label):
def on_touch_down(self, touch):

if self.collide_point(*touch.pos):
self.focus = True
        return super(FocusLabel, self).on_touch_down(touch)


def keyboard_on_key_down(self, window, keycode, text, modifiers):
        print("{0} got key down!".format(self))
return super(FocusLabel, self).keyboard_on_key_down(window, keycode, text, modifiers)

def on_focus(self, widget, value):
print("{0} got focus!".format(self))
return False


class TestApp(App):
def build(self):
return Builder.load_string(kv)

TestApp().run()

Klaas van Schelven

unread,
Jan 24, 2017, 6:13:57 AM1/24/17
to Kivy users support
Sorry about the off by one error in the original example.

And thank you very much... using your example I was able to find the problem.

It seems to be some kind of method-resolution-order related problem: if I put FocusBehavior first in the ordering, as in your example, all is well. I'm not sure whether this is a bug or a feature TBH.

Klaas

ZenCODE

unread,
Jan 24, 2017, 6:59:11 AM1/24/17
to Kivy users support
It's the suggested practise for the mix-in classes. And glad you found it :-)
Reply all
Reply to author
Forward
0 new messages