Search code examples
pythonkivykivy-language

Product of 2 numbers exclusively in Kivy language (.kv file) - why the order is important?


I want to multiply 2 numbers, performing it exclusively in the .kv file.

So I created this minimalistic main.py file:

from kivy.app import App


class Test(App):
    pass

Test().run()

and this minimalistic test.kv file:

 

  1. My 1st approach:

    BoxLayout:
        TextInput:
            id: num_1
            text: "8"
        TextInput:
            id: num_2
            text: "4"
        Label:
            text: f"Product: {int(num_1.text) * int(num_2.text)}"
    

    After launching the application, I obtained the error

     >>    9:        text: f"Procuct: {int(num_1.text) * int(num_2.text)}"
     ...
     ValueError: invalid literal for int() with base 10: ''
    

 

  1. My 2nd approach – I changed only the order of widgets, putting the Label before TextInputs:

    BoxLayout:
        Label:
            text: f"Procuct: {int(num_1.text) * int(num_2.text)}"
        TextInput:
            id: num_1
            text: "8"
        TextInput:
            id: num_2
            text: "4"
    

    and I obtained the expected result

    enter image description here

    (which correctly reflects changes in TextInputs).

So the question is: Why my first approach didn't work?


Note:

I'm not interested in a better solution or some workaround, I want only understand, why my first test.kv file don't work.


EDIT:

If in my 1st, not working approach, I change the text of Label to num_1.text + num_2.text (concatenation of strings), it works!


Solution

  • The issue is the order that kivy.builder does things. It creates widgets from top to bottom in your kv file, but it assigns attributes of those widgets in the reverse order (I have no idea why). Here is a slight modification of your code that shows the order of things:

    from kivy.app import App
    from kivy.lang import Builder
    from kivy.uix.label import Label
    from kivy.uix.textinput import TextInput
    
    kv = '''
    BoxLayout:
        MyTextInput:
            id: num_1
            text: "8"
        MyTextInput:
            id: num_2
            text: "4"
        MyLabel:
            text: "Product: " + str(int(num_1.text) * int(num_2.text)) if num_1.text != '' and num_2.text != '' else 'unknown'
    '''
    
    class MyTextInput(TextInput):
        def __init__(self, **kwargs):
            super(MyTextInput, self).__init__(**kwargs)
            print('Called TextInput init, text =', self.text)
    
        def on_text(self, instance, new_text):
            print('on_text:')
            print('\ttext set to', new_text)
    
    class MyLabel(Label):
        def __init__(self, **kwargs):
            super(MyLabel, self).__init__(**kwargs)
            print('Called Label init, text = ', self.text)
    
        def on_text(self, instance, new_text):
            print('on_text:')
            print('\ttext set to', new_text)
    
    
    class Test(App):
        def build(self):
            return Builder.load_string(kv)
    
    Test().run()