tamj0rd2
tamj0rd2

Reputation: 5700

Kivy - Make buttons change the text of TextInput?

I'm trying to make an app with 3 buttons and a display (which I'm using TextInput for).

When a button is pressed, I want the text of the button to be shown in the dispaly. e.g, if you press 1 1 2, I want 112 to show up in the display.

Is there a way to do this without adding on_press to every single button manually? Here's the code that isn't working. I think it doesn't work because "self.ids.textbox.text" is referring to the wrong thing. I'm not sure how to correct this.

Not working main2.py:

import kivy
kivy.require("1.9.0")

from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.core.window import Window

class Buttons(Button):

    def callback(self, text):
        self.ids.textbox.text = "Hi"


class Main2Widget(BoxLayout):

    pass

class Main2App(App):
    '''docstring for Main2App'''
    def build(self):
        Window.size = (300, 200)
        return Main2Widget()


if __name__ == "__main__":
    Main2App().run()

Not working main2.kv:

#:kivy 1.9.0

<Buttons>:
    on_press: root.callback(self.text)

<Main2Widget>:
    id: mainapp

    orientation: 'vertical'

    TextInput:
        id: textbox
        multiline: False
        readonly: True

        hint_text: "I'm an input box!"
        font_size: 20

    GridLayout:
        cols: 3

        Buttons:
            id: btn1
            text: "1"

        Buttons:
            id: btn2
            text: "2"

        Buttons:
            id: btn3
            text: "3"

If I do this, it works:

Working main2.py:

import kivy
kivy.require("1.9.0")

from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.core.window import Window


class Main2Widget(BoxLayout):

    def callback(self, text):
        self.ids.textbox.text += text

    pass

class Main2App(App):
    '''docstring for Main2App'''
    def build(self):
        Window.size = (300, 200)
        return Main2Widget()


if __name__ == "__main__":
    Main2App().run()

Working main2.kv:

#:kivy 1.9.0

<Main2Widget>:
    orientation: 'vertical'

    TextInput:
        id: textbox
        multiline: False
        readonly: True

        hint_text: "I'm an input box!"
        font_size: 20

    GridLayout:
        cols: 3

        Button:
            id: btn1
            text: "1"
            on_press: root.callback(self.text)

        Button:
            id: btn2
            text: "2"
            on_press: root.callback(self.text)

        Button:
            id: btn3
            text: "3"
            on_press: root.callback(self.text)

Upvotes: 0

Views: 6001

Answers (1)

bj0
bj0

Reputation: 8223

The reason it doesn't work is because root referrs to the root of the current rule (<Buttons> in this case). The root of the rule also contains the ids contained within that rule, so the function is called on Buttons, but since it has no ids, self.ids is empty.

In your second example, root refers to <Main2Widget>, which does contain a widget in it's rule with an id of textbox.

In general, a widget rule (<Buttons>) cannot access widgets outside of its own rule. This is because it can be inserted anywhere and may or may not have anything to access.

The exception is the App object. You can access your application object anywhere in kv.

I can think of 3 workarounds for your problem. The simplest is to put the function call in the <Main2Widget> rule (which you tried). If you really want a separate rule for the buttons, than the second option is to put the callback function in your App class, and call app.callback. Then you can just keep a reference to your root widget:

class Main2App(App):
    '''docstring for Main2App'''    
    def build(self):
        Window.size = (300, 200)
        self.root =  Main2Widget()
        return self.root

    def callback(self, text):
        self.root.ids.textbox.text = "Hi"

The last, and one I would probably use, is to utilize a StringProperty (also on the App class):

class Main2App(App):
    '''docstring for Main2App'''
    the_text = StringProperty()
    def build(self):
        Window.size = (300, 200)
        return Main2Widget()

Then you can bind the textinput to the property with text: app.the_text and set it in the button with on_press: app.the_text = self.text. This is cleaner in my opinion because you don't need to use a callback or access the ids on the python side.

Upvotes: 1

Related Questions