Search code examples
pythonbindkivysettertextinput

Bind two TexInput in Kivy through a function


I have two TextInputs. Now, when I write a float into one, the same float appears into the other. All I need now is for the other float to be worked by the function sp_it or it_sp. How do I insert the function?

def sp_it(num):
    if num not in range(0,11):
        print "Error!"
    else:
        return ((num-5)*9)/5.0+21

def it_sp(num):
    if num not in range(0,31):
        print "Error!"
    else:
        return ((num-21)*5)/9.0+5

class GradeApp(App):
    def build(self):
        b = BoxLayout(orientation='vertical')
        f = FloatLayout()
        t1 = TextInput(input_filter='float')
        t2 = TextInput(input_filter='float')
        l1 = Label(text='Welcome to Grader!')
        l2 = Label(text='Spanish Grade')
        l3 = Label(text='Italian Grade')
        b.add_widget(l1)
        b.add_widget(l2)
        b.add_widget(t1)
        b.add_widget(l3)
        b.add_widget(t2)
        t1.bind(text=t2.setter('text'))
        return b

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

Solution

  • It might be tempting to do something like

    t1.bind(text=lambda inst, txt: t2.setter('text')(inst, sp_it(txt)))
    

    This works fine for one text field, but as soon as it becomes circular (i.e. another callback added to t2 changing t1), this breaks. Instead, here we attach the text property handler when a text field receives focus and remove it again when focus is lost. Also, sp_it and it_sp are cleaned.

    def sp_it(num):
        try:
            num = float(num)
        except ValueError:
            return ""
        if num not in range(0,11):
            return "Error!"
        else:
            return str(((num-5)*9)/5.0+21)
    
    def it_sp(num):
        try:
            num = float(num)
        except ValueError:
            return ""
        if num not in range(0,31):
            return "Error!"
        else:
            return str(((num-21)*5)/9.0+5)
    
    def set_callback(target, modifier, object, text):
        target.text = modifier(text)
    
    def add_callback(inst, focus, target=None, modifier=None):
        if focus:
            inst.fbind('text', set_callback, target, modifier)
        else:
            inst.funbind('text', set_callback, target, modifier)
    
    class GradeApp(App):
        def build(self):
            b = BoxLayout(orientation='vertical')
            f = FloatLayout()
            t1 = TextInput(input_filter='float')
            t2 = TextInput(input_filter='float')
            l1 = Label(text='Welcome to Grader!')
            l2 = Label(text='Spanish Grade')
            l3 = Label(text='Italian Grade')
            b.add_widget(l1)
            b.add_widget(l2)
            b.add_widget(t1)
            b.add_widget(l3)
            b.add_widget(t2)
            t1.fbind('focus', add_callback, target=t2, modifier=sp_it)
            t2.fbind('focus', add_callback, target=t1, modifier=it_sp)
            return b
    

    edit: explanation The set_callback is very similar to the anonymous function of the first example I wrote (except that it takes two additional arguments, target (whose property to change) and modifier (a function to apply to the text before it gets passed on)). That way we can reuse this callback function for different targets etc. The reason we do this is that we later want to unbind the callback using funbind. If we had used an anonymous function we wouldn't know what to unbind. Finally, add_callback gets called whenever focus changes. When the text field goes into focus, we attach set_callback to the text property, and when the field loses focus we remove that callback. (That also means that if text is changed programmatically, the other text field may not be updated automatically). The details of bind/unbind and fbind/funbind are explained here: EventDispatcher.