Question

I'm making a simple calculator using kivy, and I am attempting to use a TextInput for the screen. I wasn't sure which would be better between TextInput or Label, but TextInput seemed the better choice(perhaps I was wrong?). The GUI is basically in place, all buttons with their respective text fields( '0', '1', '2', '+', '='... etc.). Each time user presses a button, the text from that button is added to calc_string which, when the '=' button is pressed, will be 'eval'd'. Essentially, it's all working, but my output is all being printed to the console, as I can't get the string to insert into the TextInput screen. I am attempting to set the text of TextInput within a callback(screen_callback) that is triggered by an on_property method bound to the TextInput. It's not working. How can I achieve this? Is it possible?

main.py:

#!/usr/bin/env python

import kivy
kivy.require('1.8.0')
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.gridlayout import GridLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.textinput import TextInput
from kivy.properties import ListProperty, ObjectProperty, StringProperty, NumericProperty

class Screen(TextInput):
    pass

class Calculator(BoxLayout):

    function_btns = ListProperty(['+', '-', '*', '/', '^', 'Mem <-', 'Mem ->', 'Clear'])
    digit_btns = ListProperty(['7', '8', '9', '4', '5', '6', '1', '2', '3', '0', '.', '+/-'])
    eval_string = StringProperty() # one of these strings is probably unnecessary
    calc_string = StringProperty()
    result = NumericProperty(0)

    def build_calc(self):
        # add screen textinput
        self.add_widget(Screen(text='0', multiline=False, on_calc_string=self.screen_callback))

        #create inner boxlayout to hold 2 button gridlayouts
        main_buttons_box = BoxLayout(orientation='horizontal', size_hint=(1, 0.75))

        # create 2 button gridlayouts
        digit_grid = GridLayout(cols=3, rows=4, size_hint=(0.5, 1))
        function_grid = GridLayout(cols=1, size_hint=(0.5, 1))

        # populate grids with buttons
        for i in self.digit_btns:
            digit_grid.add_widget(Button(text=str(i), on_press=self.button_callback))

        for f in self.function_btns:
            function_grid.add_widget(Button(text=str(f), on_press=self.button_callback))

        # add grids to inner boxlayout
        main_buttons_box.add_widget(digit_grid)
        main_buttons_box.add_widget(function_grid)

        # add inner boxlayout to main grid(root)
        self.add_widget(main_buttons_box)

        # finally, add boxlayout at bottom of main grid and insert equals btn
        equals_box = BoxLayout(size_hint=(1, 0.25))
        equals_box.add_widget(Button(text='=', on_press=self.button_callback))
        self.add_widget(equals_box)

    def button_callback(self, instance):
        value = instance.text
        if value == '=':
            try:
                self.result = eval(self.eval_string)
                self.calc_string = str(self.result)
                self.eval_string = ''
            except SyntaxError:
                self.calc_string = 'Error: Invalid Input'
        else:
            self.eval_string += value
            self.calc_string = self.eval_string

        print self.calc_string

    def screen_callback(self, instance):
        instance.text = self.calc_string # not working
        print 'seeing this!!!' # not working


class CalcApp(App):

    def build(self):
        calc = Calculator()
        calc.build_calc()
        return calc

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

kv file:

#kivy: 1.0

<Screen>:
    size_hint: 1, 0.25

<Calculator>:
    orientation: 'vertical'
Was it helpful?

Solution

Your Screen widget does not have a calc_string property, so on_calc_string does nothing here:

self.add_widget(Screen(text='0', multiline=False, on_calc_string=self.screen_callback))

You need to bind on Calculator instead. Kivy has a useful setter method to help with this:

scr = Screen(text='0', multiline=False)
self.add_widget(scr)
self.bind(calc_string=scr.setter('text'))

Also, the kv language makes this much easier. You can define your entire widget layout in kv... I won't refactor your entire widget, but here's an example:

<Calculator>:
    orientation: 'vertical'

    Screen:
        text: root.calc_string

root refers to the root widget in the rule, which is Calculator here. Whenever root.calc_string is modified, the Screen's text property will be updated automatically.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top