I am trying to write make a Calculator GUI with Kivy.
This is the code:
experiment.py
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import ListProperty, NumericProperty, BooleanProperty, StringProperty
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.lang import Builder
import calculator_calc
Builder.load_file("calculatorgui.kv")
class CalculatorScreen1(Screen):
pass
class CalculatorScreen2(Screen):
pass
class CalculatorModeIndicator(BoxLayout):
pass
class CalculatorMonitor1(BoxLayout):
pass
class CalculatorButtons(BoxLayout):
pass
screen_manager = ScreenManager()
screen_manager.add_widget(CalculatorScreen1(name = "screen1"))
screen_manager.add_widget(CalculatorScreen2(name = "screen2"))
class Calculator(App):
expression_tokens = ListProperty([])
expression_value = StringProperty("")
angle_mode = StringProperty("degrees")
angle_mode_text = StringProperty("D")
button_mode = NumericProperty(1)
button_mode_text = StringProperty("1ST")
store_mode = BooleanProperty(False)
def build(self):
return screen_manager
def switch_screen(self, screen):
screen_manager.current = screen
def set_button_mode(self, button_mode):
self.button_mode = button_mode
if button_mode == 1:
self.button_mode_text = "1ST"
elif button_mode == 2:
self.button_mode_text = "2ND"
elif button_mode == 3:
self.button_mode_text = "3RD"
def set_angle_mode(self, angle_mode):
calculator_calc.evaluate(f"angle_mode {angle_mode}")
self.angle_mode = angle_mode
if angle_mode == "degrees":
self.angle_mode_text = "D"
elif angle_mode == "radians":
self.angle_mode_text = "R"
elif angle_mode == "gradians":
self.angle_mode_text = "G"
def add_token(self, token):
self.expression_tokens.append(token)
def calculate(self, expression):
result = calculator_calc.evaluate(expression)
self.expression_value = str(result)
calculator_calc.evaluate(f"ANS = {result}")
Calculator().run()
calculatorgui.kv
#:kivy 1.11.1
<CalculatorScreen1>:
BoxLayout:
orientation: "vertical"
CalculatorModeIndicator:
CalculatorMonitor1:
CalculatorButtons:
<CalculatorScreen2>:
BoxLayout:
orientation: "vertical"
Label:
text: "This is Screen 2"
Button:
text: "Screen2"
on_press: app.switch_screen("screen1")
<CalculatorModeIndicator>:
Label:
size_hint_x: None
width: "30dp"
text: app.button_mode_text
Label:
size_hint_x: None
width: "30dp"
text: app.angle_mode_text
<CalculatorMonitor1>:
orientation: "vertical"
Label:
size_hint_y: None
height: "30dp"
text_size: self.size
halign: "left"
valign: "top"
text: '"".join(app.expression_tokens)'
Label:
size_hint_y: None
height: "30dp"
text_size: self.size
halign: "right"
valign: "bottom"
text: 'app.expression_value'
<CalculatorButtons>:
orientation: "vertical"
BoxLayout:
Button:
text: "1ST"
on_press: app.set_button_mode(1)
Button:
text: "2ND"
on_press: app.set_button_mode(2)
Button:
text: "3RD"
on_press: app.set_button_mode(3)
BoxLayout:
Button:
text: "DEGREES"
on_press:
app.set_angle_mode("degrees")
Button:
text: "RADIANS"
on_press:
app.set_angle_mode("radians")
Button:
text: "GRADIANS"
on_press:
app.set_angle_mode("gradians")
BoxLayout:
Button:
text: "A"
on_press: app.add_token(self.text) if not app.store_mode else app.store("A")
Button:
text: "B"
on_press: app.add_token(self.text) if not app.store_mode else app.store("B")
Button:
text: "C"
on_press: app.add_token(self.text) if not app.store_mode else app.store("C")
Button:
text: "D"
on_press: app.add_token(self.text) if not app.store_mode else app.store("D")
Button:
text: "E"
on_press: app.add_token(self.text) if not app.store_mode else app.store("E")
# BoxLayout:
# Button:
# text: "sin" if app.button_mode == 1 else "asin" if app.button_mode == 2 else "sinh"
# on_press: app.add_token(f"{self.text}(")
# Button:
# text: "cos" if app.button_mode == 1 else "acos" if app.button_mode == 2 else "cosh"
# on_press: app.add_token(f"{self.text}(")
# Button:
# text: "tan" if app.button_mode == 1 else "atan" if app.button_mode == 2 else "tanh"
# on_press: app.add_token(f"{self.text}(")
# Button:
# text: "log"
# on_press: app.add_token(self.text + "(")
# Button:
# text: "ln"
# on_press: app.add_token(self.text + "(")
BoxLayout:
Button:
text: "("
on_press: app.add_token(self.text)
Button:
text: ")"
on_press: app.add_token(self.text)
Button:
text: "STO"
on_press: app.store_mode = True
Button:
text: "swt"
on_press: app.switch_screen("screen2")
Button:
text: ","
on_press: app.add_token(self.text)
BoxLayout:
Button:
text: "7"
on_press: app.add_token(self.text)
Button:
text: "8"
on_press: app.add_token(self.text)
Button:
text: "9"
on_press: app.add_token(self.text)
Button:
text: "DEL"
on_press: app.expression_tokens = app.expression_tokens[:-1]
Button:
text: "AC"
on_press:
app.expression_tokens = []
BoxLayout:
Button:
text: "4"
on_press: app.add_token(self.text)
Button:
text: "5"
on_press: app.add_token(self.text)
Button:
text: "6"
on_press: app.add_token(self.text)
Button:
text: "*"
on_press: app.add_token(self.text)
Button:
text: "/"
on_press: app.add_token(self.text)
BoxLayout:
Button:
text: "1"
on_press: app.add_token(self.text)
Button:
text: "2"
on_press: app.add_token(self.text)
Button:
text: "3"
on_press: app.add_token(self.text)
Button:
text: "+"
on_press: app.add_token(self.text)
Button:
text: "-"
on_press: app.add_token(self.text)
BoxLayout:
Button:
text: "0"
on_press: app.add_token(self.text)
Button:
text: "000"
on_press: app.add_token(self.text)
Button:
text: "."
on_press: app.add_token(self.text)
Button:
text: "ANS"
on_press: app.add_token(self.text)
Button:
text: "="
on_press: app.calculate("".join(app.expression_tokens))
When I try to run the script, my console returns this error message:
(venv) C:\Users\Wendelin\Documents\Programming\calculation>c:/Users/Wendelin/Documents/Programming/calculation/venv/Scripts/python.exe c:/Users/Wendelin/Documents/Programming/calculation/Calculator/experiment.py
[INFO ] [Logger ] Record log in C:\Users\Wendelin\.kivy\logs\kivy_19-12-19_29.txt
[INFO ] [deps ] Successfully imported "kivy_deps.gstreamer" 0.1.17
[INFO ] [deps ] Successfully imported "kivy_deps.glew" 0.1.12
[INFO ] [deps ] Successfully imported "kivy_deps.sdl2" 0.1.22
[INFO ] [Kivy ] v1.11.1
[INFO ] [Kivy ] Installed at "c:\Users\Wendelin\Documents\Programming\calculation\venv\lib\site-packages\kivy\__init__.py"
[INFO ] [Python ] v3.7.5 (tags/v3.7.5:5c02a39a0b, Oct 15 2019, 00:11:34) [MSC v.1916 64 bit (AMD64)]
[INFO ] [Python ] Interpreter at "c:\Users\Wendelin\Documents\Programming\calculation\venv\Scripts\python.exe"
[INFO ] [Factory ] 184 symbols loaded
[INFO ] [Image ] Providers: img_tex, img_dds, img_sdl2, img_pil, img_gif (img_ffpyplayer ignored)
[INFO ] [Window ] Provider: sdl2
[INFO ] [GL ] Using the "OpenGL" graphics system
[INFO ] [GL ] GLEW initialization succeeded
[INFO ] [GL ] Backend used <glew>
[INFO ] [GL ] OpenGL version <b'4.4.0 - Build 20.19.15.4463'>
[INFO ] [GL ] OpenGL vendor <b'Intel'>
[INFO ] [GL ] OpenGL renderer <b'Intel(R) Iris(TM) Graphics 540'>
[INFO ] [GL ] OpenGL parsed version: 4, 4
[INFO ] [GL ] Shading version <b'4.40 - Build 20.19.15.4463'>
[INFO ] [GL ] Texture max size <16384>
[INFO ] [GL ] Texture max units <32>
[INFO ] [Window ] auto add sdl2 input provider
[INFO ] [Window ] virtual keyboard not allowed, single mode, not docked
[INFO ] [Text ] Provider: sdl2
Traceback (most recent call last):
File "c:\Users\Wendelin\Documents\Programming\calculation\venv\lib\site-packages\kivy\lang\builder.py", line 249, in create_handler
return eval(value, idmap), bound_list
File "c:\Users\Wendelin\Documents\Programming\calculation\Calculator\calculatorgui.kv", line 38, in <module>
text: app.angle_mode_text
File "c:\Users\Wendelin\Documents\Programming\calculation\venv\lib\site-packages\kivy\lang\parser.py", line 75, in __getattribute__
object.__getattribute__(self, '_ensure_app')()
File "c:\Users\Wendelin\Documents\Programming\calculation\venv\lib\site-packages\kivy\lang\parser.py", line 70, in _ensure_app
app.bind(on_stop=lambda instance:
AttributeError: 'NoneType' object has no attribute 'bind'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "c:\Users\Wendelin\Documents\Programming\calculation\venv\lib\site-packages\kivy\lang\builder.py", line 692, in _apply_rule
rctx['ids'])
File "c:\Users\Wendelin\Documents\Programming\calculation\venv\lib\site-packages\kivy\lang\builder.py", line 254, in create_handler
cause=tb)
kivy.lang.builder.BuilderException: Parser: File "c:\Users\Wendelin\Documents\Programming\calculation\Calculator\calculatorgui.kv",
line 38:
...
36: size_hint_x: None
37: width: "30dp"
>> 38: text: app.angle_mode_text
39:
40:
...
AttributeError: 'NoneType' object has no attribute 'bind'
File "c:\Users\Wendelin\Documents\Programming\calculation\venv\lib\site-packages\kivy\lang\builder.py", line 249, in create_handler
return eval(value, idmap), bound_list
File "c:\Users\Wendelin\Documents\Programming\calculation\Calculator\calculatorgui.kv", line 38, in <module>
text: app.angle_mode_text
File "c:\Users\Wendelin\Documents\Programming\calculation\venv\lib\site-packages\kivy\lang\parser.py", line 75, in __getattribute__
object.__getattribute__(self, '_ensure_app')()
File "c:\Users\Wendelin\Documents\Programming\calculation\venv\lib\site-packages\kivy\lang\parser.py", line 70, in _ensure_app
app.bind(on_stop=lambda instance:
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "c:/Users/Wendelin/Documents/Programming/calculation/Calculator/experiment.py", line 39, in <module>
screen_manager.add_widget(CalculatorScreen1(name = "screen1"))
File "c:\Users\Wendelin\Documents\Programming\calculation\venv\lib\site-packages\kivy\uix\relativelayout.py", line 265, in __init__
super(RelativeLayout, self).__init__(**kw)
File "c:\Users\Wendelin\Documents\Programming\calculation\venv\lib\site-packages\kivy\uix\floatlayout.py", line 65, in __init__
super(FloatLayout, self).__init__(**kwargs)
File "c:\Users\Wendelin\Documents\Programming\calculation\venv\lib\site-packages\kivy\uix\layout.py", line 76, in __init__
super(Layout, self).__init__(**kwargs)
File "c:\Users\Wendelin\Documents\Programming\calculation\venv\lib\site-packages\kivy\uix\widget.py", line 361, in __init__
rule_children=rule_children)
File "c:\Users\Wendelin\Documents\Programming\calculation\venv\lib\site-packages\kivy\uix\widget.py", line 469, in apply_class_lang_rules
rule_children=rule_children)
File "c:\Users\Wendelin\Documents\Programming\calculation\venv\lib\site-packages\kivy\lang\builder.py", line 538, in apply
rule_children=rule_children)
File "c:\Users\Wendelin\Documents\Programming\calculation\venv\lib\site-packages\kivy\lang\builder.py", line 659, in _apply_rule
child, crule, rootrule, rule_children=rule_children)
File "c:\Users\Wendelin\Documents\Programming\calculation\venv\lib\site-packages\kivy\lang\builder.py", line 657, in _apply_rule
root=rctx['ids']['root'], rule_children=rule_children)
File "c:\Users\Wendelin\Documents\Programming\calculation\venv\lib\site-packages\kivy\uix\widget.py", line 469, in apply_class_lang_rules
rule_children=rule_children)
File "c:\Users\Wendelin\Documents\Programming\calculation\venv\lib\site-packages\kivy\lang\builder.py", line 538, in apply
rule_children=rule_children)
File "c:\Users\Wendelin\Documents\Programming\calculation\venv\lib\site-packages\kivy\lang\builder.py", line 707, in _apply_rule
e), cause=tb)
kivy.lang.builder.BuilderException: Parser: File "c:\Users\Wendelin\Documents\Programming\calculation\Calculator\calculatorgui.kv",
line 38:
...
36: size_hint_x: None
37: width: "30dp"
>> 38: text: app.angle_mode_text
39:
40:
...
BuilderException: Parser: File "c:\Users\Wendelin\Documents\Programming\calculation\Calculator\calculatorgui.kv", line 38:
...
36: size_hint_x: None
37: width: "30dp"
>> 38: text: app.angle_mode_text
39:
40:
...
AttributeError: 'NoneType' object has no attribute 'bind'
File "c:\Users\Wendelin\Documents\Programming\calculation\venv\lib\site-packages\kivy\lang\builder.py", line 249, in create_handler
return eval(value, idmap), bound_list
File "c:\Users\Wendelin\Documents\Programming\calculation\Calculator\calculatorgui.kv", line 38, in <module>
text: app.angle_mode_text
File "c:\Users\Wendelin\Documents\Programming\calculation\venv\lib\site-packages\kivy\lang\parser.py", line 75, in __getattribute__
object.__getattribute__(self, '_ensure_app')()
File "c:\Users\Wendelin\Documents\Programming\calculation\venv\lib\site-packages\kivy\lang\parser.py", line 70, in _ensure_app
app.bind(on_stop=lambda instance:
File "c:\Users\Wendelin\Documents\Programming\calculation\venv\lib\site-packages\kivy\lang\builder.py", line 692, in _apply_rule
rctx['ids'])
File "c:\Users\Wendelin\Documents\Programming\calculation\venv\lib\site-packages\kivy\lang\builder.py", line 254, in create_handler
cause=tb)
So it seems like I somehow can´t properly access the app instance. However, I looked into the Kivy documentation and I am using the app identifier as prescribed.
What am I supposed to do? Any help would be greatly appreciated.
The problem is that the Properties
in your App
have not been defined when you are loading the kv
file. You can correct that by moving your Builder.load_file()
into the App
, so that the Properties
are already defined:
screen_manager = ScreenManager()
class Calculator(App):
expression_tokens = ListProperty([])
expression_value = StringProperty("")
angle_mode = StringProperty("degrees")
angle_mode_text = StringProperty("D")
button_mode = NumericProperty(1)
button_mode_text = StringProperty("1ST")
store_mode = BooleanProperty(False)
def build(self):
Builder.load_file("calculatorgui.kv")
screen_manager.add_widget(CalculatorScreen1(name = "screen1"))
screen_manager.add_widget(CalculatorScreen2(name = "screen2"))
return screen_manager