Search code examples
pythonkivykivy-language

How to merge Python and Kivy to work as one project


I am new to Python and Kivy and have encountered a problem. My aim is to have buttons with bilingual texts that changes when proper buttons are pressed: button_polish and button_english. I wanted the 'logic' of the project in the .py file and the 'visual' in .kv file. In order to do so I have created a python file and a kivy file. But when I try to run it an error occurs: 'MainScreen' has no attribute 'metropolitalne_text'.

I believe that there's something wrong with the instantiation of the objects but I have no idea how to fix this. I would love to be able to refer to .kv objects in .py file using their ids.

I have tried instantiating objects in .py file but this didn't help. What I found out is that using StringProperty should help.

Here are my files:

Biletomat.py:

import kivy
kivy.require('1.11.1')

from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.gridlayout import GridLayout
from kivy.config import Config
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.properties import StringProperty
from kivy.uix.behaviors import ButtonBehavior

from datetime import datetime
from datetime import timedelta
from kivy.clock import Clock

Config.set('graphics', 'resizable', '0')
Config.set('graphics', 'width', '1200')
Config.set('graphics', 'height', '700')
Config.set('graphics', 'fullscreen', '0')

Builder.load_file('1mainscreen.kv')

language = "polish"

# 1 the first and main screen - 1st click
class MainScreen (Screen):
    def __init__(self,**kwargs):
        super().__init__()

        self.button_polish = Button(on_press=self.change_language_to_polish)
        self.button_english = Button(on_press=self.change_language_to_english)

    def change_language_to_polish(self):
        main_title_text = StringProperty('BILETY JEDNOPRZEJAZDOWE I JEDNORAZOWE')
        jednoprzejazdowy_text=StringProperty('Jednoprzejazdowy na linie zwyk\u0142e')
        jednogodzinny_text=StringProperty('1-godzinny na linie zwyk\u0142e')
        jednoprzejazdowylubjednoprzejazdowy_text=StringProperty('1-godzinny lub jednoprzejazdowy na linie nocne,\n pospieszne i zwyk\u0142e')        
        dwudziestoczterogodzinny_text="24-godzinny na linie nocne, pospieszne i zwyk\u0142e"
        subtitle_text=StringProperty('BILETY OKRESOWE I METROPOLITALNE')
        okresowe_text=StringProperty('Bilety okresowe')
        metropolitalne_text=StringProperty('Bilety metropolitalne')

    def change_language_to_english(self):
        main_title_text = StringProperty('SINGLE TRIP AND TIME TICKETS')
        jednoprzejazdowy_text=StringProperty('Single trip valid on standard lines')
        jednogodzinny_text=StringProperty('1-hour ticket valid on standard lines')
        jednoprzejazdowylubjednoprzejazdowy_text=StringProperty('1-hour ticket or single trip valid on night,\nrapid and standard lines')        
        dwudziestoczterogodzinny_text="24-hour ticket valid on night,\nrapid and standard lines"
        subtitle_text=StringProperty('TIME AND METROPOLITAN TICKETS')
        okresowe_text=StringProperty('Season tickets')
        metropolitalne_text=StringProperty('Metropolitan tickets')

# Create the screen manager
sm = ScreenManager()
sm.add_widget(MainScreen(name='menu'))

class MyApp(App):    
    def build(self):
        return sm

MyApp().run()

1mainscreen.kv:

<MainScreen>:
GridLayout:
    cols: 1
    rows: 5

    canvas.before:
        Rectangle:
            pos: self.pos
            size: self.size
            source: 'port.jpg'
        Color:
            rgba: 0, 0, 0, 0.5

    AnchorLayout:
        anchor_x: 'center'
        anchor_y: 'center'    

        Label:
            id: main_title
            text: root.main_title_text
            color: 0, 0, 0, 1
            font_size: '32dp'
            bcolor: 1, 1, 1, 0.5
            canvas.before:
                Color:
                    rgba: self.bcolor
                Rectangle:
                    pos: self.pos
                    size: self.size

        AnchorLayout:
            anchor_x: 'right'
            anchor_y: 'top'
            padding: [00, 40, 40, 0]
            Button:
                id:"button_polish"
                size: 50, 50
                size_hint: None, None # <---
                background_normal: ''
                background_color: 0, 0.7, 1, 0
                on_press:
                    root.change_language_to_polish()
                    print("Polish".format(self.text))
                Image:
                    source: 'poland_flag.png'
                    y: self.parent.y
                    x: self.parent.x 
                    size: 75, 75
                    allow_stretch: True

        AnchorLayout:
            anchor_x: 'right'
            anchor_y: 'top'
            padding: [00, 40, 130, 0]
            Button:
                id:"button_english"
                size: 50, 50
                size_hint: None, None # <---
                background_normal: ''
                background_color: 0, 0.7, 1, 0
                on_press:
                    root.change_language_to_english()
                    print("English".format(self.text))
                Image:
                    source: 'uk_flag.png'
                    y: self.parent.y
                    x: self.parent.x 
                    size: 75, 75
                    allow_stretch: True


    GridLayout:
        cols: 2
        rows: 1

        RelativeLayout:
            SmoothButton:
                id: jednoprzejazdowy
                size: 550, 120
                font_size: '24dp'
                size_hint: None, None # <---
                pos_hint: {'center_x': 0.5, 'center_y': 0.5}
                text: root.jednoprzejazdowy_text
                on_press: root.manager.current = 'jednorazowe_screen1'
                back_color: 0, 0.7, 1, 1
        RelativeLayout:
            SmoothButton:
                id: jednogodzinny
                size: 550, 120
                font_size: '24dp'
                size_hint: None, None # <---
                pos_hint: {'center_x': 0.5, 'center_y': 0.5}
                text: root.jednogodzinny_text
                on_press: root.manager.current = 'jednorazowe_screen2'
                back_color: 0, 0.7, 1, 1

    GridLayout:
        cols: 2
        rows: 1

        RelativeLayout:
            SmoothButton: 
                id: jednoprzejazdowylubjednoprzejazdowy
                size: 550, 120
                font_size: '24dp'
                halign: 'center'
                size_hint: None, None # <---
                pos_hint: {'center_x': 0.5, 'center_y': 0.5}
                text: root.jednoprzejazdowylubjednoprzejazdowy_text
                on_press: root.manager.current = 'jednorazowe_screen3'
                back_color: 0, 0.7, 1, 1
        RelativeLayout:
            SmoothButton:
                id: dwudziestoczterogodzinny
                size: 550, 120
                font_size: '24dp'
                halign: 'center'
                size_hint: None, None # <---
                pos_hint: {'center_x': 0.5, 'center_y': 0.5}
                text: root.dwudziestoczterogodzinny_text
                on_press: root.manager.current = 'jednorazowe_screen4'
                back_color: 0, 0.7, 1, 1

    Label:
        id: subtitle
        text: root.subtitle_text
        color: 0, 0, 0, 1
        font_size: '32dp'
        bcolor: 1, 1, 1, 0.5
        canvas.before:
            Color:
                rgba: self.bcolor
            Rectangle:
                pos: self.pos
                size: self.size

    GridLayout:
        cols: 2
        rows: 1

        RelativeLayout:
            SmoothButton:
                id: okresowe
                size: 550, 120
                font_size: '24dp'
                size_hint: None, None # <---
                pos_hint: {'center_x': 0.5, 'center_y': 0.5}
                text: root.okresowe_text
                on_press: root.manager.current = 'okresowe_screen'
                back_color: 0, 0.7, 1, 1
        RelativeLayout:
            SmoothButton:
                id: metropolitalne
                size: 550, 120
                font_size: '24dp'
                size_hint: None, None # <---
                pos_hint: {'center_x': 0.5, 'center_y': 0.5}
                text: root.metropolitalne_text
                on_press: root.manager.current = 'metropolitalne_screen'
                back_color: 0, 0.7, 1, 1

<SmoothButton@Button>:
    background_color: (0,0,0,0)
    background_normal: ''
    back_color: (0, 0.7, 1, 1)
    border_radius: [18]
    canvas.before:
        Color:
            rgba: self.back_color
        RoundedRectangle:
            size: self.size
            pos: self.pos
            radius: self.border_radius

The error I get:

[INFO   ] [Logger      ] Record log in C:\Users\Andrea\.kivy\logs\kivy_19-12-29_30.txt
[INFO   ] [Kivy        ] v1.11.1
[INFO   ] [Kivy        ] Installed at "D:\ANACONDA\anaconda\lib\site-packages\kivy\__init__.py"
[INFO   ] [Python      ] v3.7.4 (default, Aug  9 2019, 18:34:13) [MSC v.1915 64 bit (AMD64)]
[INFO   ] [Python      ] Interpreter at "D:\ANACONDA\anaconda\pythonw.exe"
[INFO   ] [Factory     ] 184 symbols loaded
[INFO   ] [Image       ] Providers: img_tex, img_dds, img_sdl2, img_pil, img_gif (img_ffpyplayer ignored)
[INFO   ] [Text        ] Provider: sdl2
[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.5.0 - Build 24.20.100.6286'>
[INFO   ] [GL          ] OpenGL vendor <b'Intel'>
[INFO   ] [GL          ] OpenGL renderer <b'Intel(R) UHD Graphics 620'>
[INFO   ] [GL          ] OpenGL parsed version: 4, 5
[INFO   ] [GL          ] Shading version <b'4.50 - Build 24.20.100.6286'>
[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   ] [GL          ] NPOT texture support is available
[INFO   ] [GL          ] Unpack subimage support is available
Traceback (most recent call last):

  File "<ipython-input-1-6b1cf59579f0>", line 1, in <module>
runfile('D:/studia - semestr 9/Wizualizacja i multimedia w technice/laborki/biletomat/biletomat.py', args='1 2 3', wdir='D:/studia - semestr 9/Wizualizacja i multimedia w technice/laborki/biletomat')

  File "D:\ANACONDA\anaconda\lib\site-packages\spyder_kernels\customize\spydercustomize.py", line 827, in runfile
execfile(filename, namespace)

  File "D:\ANACONDA\anaconda\lib\site-packages\spyder_kernels\customize\spydercustomize.py", line 110, in execfile
exec(compile(f.read(), filename, 'exec'), namespace)

  File "D:/studia - semestr 9/Wizualizacja i multimedia w technice/laborki/biletomat/biletomat.py", line 238, in <module>
sm.add_widget(MainScreen(name='menu'))

  File "D:/studia - semestr 9/Wizualizacja i multimedia w technice/laborki/biletomat/biletomat.py", line 69, in __init__
super().__init__()

  File "D:\ANACONDA\anaconda\lib\site-packages\kivy\uix\relativelayout.py", line 265, in __init__
super(RelativeLayout, self).__init__(**kw)

  File "D:\ANACONDA\anaconda\lib\site-packages\kivy\uix\floatlayout.py", line 65, in __init__
super(FloatLayout, self).__init__(**kwargs)

  File "D:\ANACONDA\anaconda\lib\site-packages\kivy\uix\layout.py", line 76, in __init__
super(Layout, self).__init__(**kwargs)

  File "D:\ANACONDA\anaconda\lib\site-packages\kivy\uix\widget.py", line 361, in __init__
rule_children=rule_children)

  File "D:\ANACONDA\anaconda\lib\site-packages\kivy\uix\widget.py", line 469, in apply_class_lang_rules
rule_children=rule_children)

  File "D:\ANACONDA\anaconda\lib\site-packages\kivy\lang\builder.py", line 538, in apply
rule_children=rule_children)

  File "D:\ANACONDA\anaconda\lib\site-packages\kivy\lang\builder.py", line 707, in _apply_rule
e), cause=tb)

BuilderException: Parser: File "D:\studia - semestr 9\Wizualizacja i multimedia w technice\laborki\biletomat\1mainscreen.kv", line 159:
...
157:                    size_hint: None, None # <---
158:                    pos_hint: {'center_x': 0.5, 'center_y': 0.5}
>>  159:                    text: root.metropolitalne_text
160:                    on_press: root.manager.current = 'metropolitalne_screen'
161:                    back_color: 0, 0.7, 1, 1
...
BuilderException: Parser: File "D:\studia - semestr 9\Wizualizacja i multimedia w technice\laborki\biletomat\1mainscreen.kv", line 159:
...
157:                    size_hint: None, None # <---
158:                    pos_hint: {'center_x': 0.5, 'center_y': 0.5}
>>  159:                    text: root.metropolitalne_text
160:                    on_press: root.manager.current = 'metropolitalne_screen'
161:                    back_color: 0, 0.7, 1, 1
...
AttributeError: 'MainScreen' object has no attribute 'metropolitalne_text'
  File "D:\ANACONDA\anaconda\lib\site-packages\kivy\lang\builder.py", line 249, in create_handler
return eval(value, idmap), bound_list
  File "D:\studia - semestr 9\Wizualizacja i multimedia w technice\laborki\biletomat\1mainscreen.kv", line 159, in <module>
text: root.metropolitalne_text
  File "kivy\weakproxy.pyx", line 32, in kivy.weakproxy.WeakProxy.__getattr__

  File "D:\ANACONDA\anaconda\lib\site-packages\kivy\lang\builder.py", line 692, in _apply_rule
rctx['ids'])
  File "D:\ANACONDA\anaconda\lib\site-packages\kivy\lang\builder.py", line 254, in create_handler
cause=tb)

Solution

  • When you create a variable in a method, it is a local variable by default, and is not accessible outside that method. Your lines:

    metropolitalne_text=StringProperty('Bilety metropolitalne')
    

    and

    metropolitalne_text=StringProperty('Metropolitan tickets')
    

    create local variables named metropolitalne_text. I suspect you really want metropolitalne_text to be a property of MainScreen. You can do this by adding a line to your definition of the MainScreen class:

    class MainScreen (Screen):
        metropolitalne_text = StringProperty()
    
        def __init__(self,**kwargs):
            super().__init__()
    
            self.button_polish = Button(on_press=self.change_language_to_polish)
            self.button_english = Button(on_press=self.change_language_to_english)
    

    Then, everywhare that you reference metropolitalne_text in the MainScreen class, change it to self.metropolitalne_text.