Search code examples
pythonkivy

Kivy - TextInput on_focus behavior issue


I'm creating an app using kivy. So I don't think I'm understanding something correctly regarding the on_focus behavior of the FloatInput.

Let me first describe what I'm trying to do here. I'm trying to activate a pop-up numpad when the user clicks on a TextInput (this numpad just has buttons for numbers 0-9 and an "enter" button). Next, once the user provides a number in the pop-up numpad and hits an "enter" button, the pop-up should close and the text field of the TextInput should be updated to the user entered number.

However, I'm running into a problem. Essentially, I have a form with multiple TextInputs constructed as described above (I'll refer to them hereafter as "FloatInputs" b/c that's what I've named them). The first time I enter values into one of the FloatInputs, everything works as expected. But the problem occurs whenever I try to enter a number into a second FloatInput. Specifically, the on_focus function is called twice somehow and the user must enter the value into the pop-up twice as a result (this is obviously not ideal as it would serve to frustrate the user).

I apologize if this is a stupid question, but I've been puzzled by it for quite a while. I think the heart of the problem lies in the order of the focusing of the TextInput and the popup widgets, but I could be wrong here. If someone could please offer suggestions as to how I could fix this bug I'd greatly appreciate it.

Here is the relevant python code:

class FloatInput(TextInput):

    def on_focus(self, instance, value):

        print 'ON FOCUS CALLED - THIS SHOULD ONLY BE CALLED ONCE!'

        # Adding this helped part of the problem (however, on_focus is still being called twice???)
        self.unfocus_on_touch = False

        prompt_content = BoxLayout(orientation='vertical')      # Main layout

        num_pad = NumPadWidget()
        prompt_content.add_widget(num_pad)

        def my_callback(instance):

            self.text = num_pad.ids.num_label.text
            self.popup.dismiss()

        # Now define the popup, with the content being the layout:
        self.popup = Popup(id='num_pad_popup', title='Enter value', content=prompt_content, size_hint=(0.8,0.5), autodismiss=False)

        num_pad.ids.enter_btn.bind(on_press=my_callback)

        # Open the pop-up:
        self.popup.open()

And here is the relevant kv code:

<NumPadButton@Button>:
    font_size: '16dp'
    bold: True

<NumPadWidget>:
    BoxLayout:
        orientation: 'vertical'
        BoxLayout:
            size_hint_y: 1
            id: num_label
            Label:
                id: num_label
                text: ''
        BoxLayout:
            size_hint_y: 5
            orientation: 'vertical'
            BoxLayout:
                NumPadButton:
                    text: '7'
                    on_press:
                        num_label.text = num_label.text + '7'
                NumPadButton:
                    text: '8'
                    on_press:
                        num_label.text = num_label.text + '8'
                NumPadButton:
                    text: '9'
                    on_press:
                        num_label.text = num_label.text + '9'
            BoxLayout:
                NumPadButton:
                    text: '4'
                    on_press:
                        num_label.text = num_label.text + '4'
                NumPadButton:
                    text: '5'
                    on_press:
                        num_label.text = num_label.text + '5'
                NumPadButton:
                    text: '6'
                    on_press:
                        num_label.text = num_label.text + '6'
            BoxLayout:
                NumPadButton:
                    text: '1'
                    on_press:
                        num_label.text = num_label.text + '1'
                NumPadButton:
                    text: '2'
                    on_press:
                        num_label.text = num_label.text + '2'
                NumPadButton:
                    text: '3'
                    on_press:
                        num_label.text = num_label.text + '3'
            BoxLayout:
                NumPadButton:
                    text: '0'
                    on_press:
                        num_label.text = num_label.text + '0'
                NumPadButton:
                    text: '.'
                    on_press:
                        num_label.text = num_label.text + '.'
                NumPadButton:
                    text: 'del'
                    on_press:
                        num_label.text = num_label.text[:-1]
            BoxLayout:
                BoxLayout:
                    size_hint_x: 1
                    NumPadButton:
                        text: 'C'
                        on_press:
                            num_label.text = ''
                BoxLayout:
                    size_hint_x: 2
                    NumPadButton:
                        id: enter_btn
                        text: 'Enter'

Solution

  • Okay after playing with this a bit more I found a solution to achieve the functionality that I wanted. However, to be clear this does resolve why the on_focus behavior is acting in the manner that I've described in my original post.

    Here is a work around in case anyone is interested:

    class FloatInput(TextInput):
    
        def __init__(self, **kwargs):
            super(FloatInput, self).__init__(**kwargs)
    
        def on_touch_down(self, touch):
            if self.collide_point(touch.x, touch.y):
    
                prompt_content = BoxLayout(orientation='vertical')      # Main layout
    
                num_pad = NumPadWidget()
                prompt_content.add_widget(num_pad)
    
                def my_callback(instance):
                    self.text = num_pad.ids.num_label.text
                    self.popup.dismiss()
    
                # Now define the popup, with the content being the layout:
                self.popup = Popup(id='num_pad_popup', title='Enter value', content=prompt_content, size_hint=(0.8,0.5), autodismiss=False)
                num_pad.ids.enter_btn.bind(on_press=my_callback)
    
                # Open the pop-up:
                self.popup.open()
    

    Specifically, this solution works by using on_touch_down instead of looking for the focus of the TextInput. Additionally, looking for the collision with "if self.collide_point(touch.x, touch.y)" prevents an error where all widgets of the same type respond to the user touch.

    I hope this helps someone!