Search code examples
pythonkivykivy-language

kivy error from using a variable from python file to kv file


I'm making a Manga reader it uses the Images module in kivy and I found a bug that can be solved by using a variable from a py file to a kv file. I have a Variable in the App class called sourceToImage but when i call the sourceToImage variable in a kv file source: app.sourceToImage gives me a long error

py

class MyApp(App):
    sourceToImage = "Source" 

    def build(self):
        return sm

kv

<ReaderApp>:
    name: "reader"
    im: page

    canvas:
        Color:
            rgba: 1,1,1,0.17
        Rectangle:
            size: (root.width, root.height)
            pos: (0,0)
    Image:
        size: 500, 600
        pos: 0, 0
        id: page
        source: app.sourceToImage

but when i use app.sourceToImage it gives me an Error saying:

Traceback (most recent call last):
   File "C:\Users\USER\AppData\Local\Programs\Python\Python39\lib\site-packages\kivy\lang\builder.py", line 242, in create_handler
     return eval(value, idmap), bound_list
   File "D:\GameDev-Programming\Python\kivyProjects\firstKivy\my.kv", line 20, in <module>
     source: app.sourceToImage
   File "C:\Users\USER\AppData\Local\Programs\Python\Python39\lib\site-packages\kivy\lang\parser.py", line 75, in __getattribute__
     object.__getattribute__(self, '_ensure_app')()
   File "C:\Users\USER\AppData\Local\Programs\Python\Python39\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\USER\AppData\Local\Programs\Python\Python39\lib\site-packages\kivy\lang\builder.py", line 695, in _apply_rule
     value, bound = create_handler(
   File "C:\Users\USER\AppData\Local\Programs\Python\Python39\lib\site-packages\kivy\lang\builder.py", line 245, in create_handler
     raise BuilderException(rule.ctx, rule.line,
 kivy.lang.builder.BuilderException: Parser: File "D:\GameDev-Programming\Python\kivyProjects\firstKivy\my.kv", line 20:
 ...
      18:        pos: 0, 0
      19:        id: page
 >>   20:        source: app.sourceToImage
      21:
      22:
 ...
 AttributeError: 'NoneType' object has no attribute 'bind'
   File "C:\Users\USER\AppData\Local\Programs\Python\Python39\lib\site-packages\kivy\lang\builder.py", line 242, in create_handler
     return eval(value, idmap), bound_list
   File "D:\GameDev-Programming\Python\kivyProjects\firstKivy\my.kv", line 20, in <module>
     source: app.sourceToImage
   File "C:\Users\USER\AppData\Local\Programs\Python\Python39\lib\site-packages\kivy\lang\parser.py", line 75, in __getattribute__
     object.__getattribute__(self, '_ensure_app')()
   File "C:\Users\USER\AppData\Local\Programs\Python\Python39\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 "D:\GameDev-Programming\Python\kivyProjects\firstKivy\main.py", line 87, in <module>
     kv = Builder.load_file("my.kv")
   File "C:\Users\USER\AppData\Local\Programs\Python\Python39\lib\site-packages\kivy\lang\builder.py", line 306, in load_file
     return self.load_string(data, **kwargs)
   File "C:\Users\USER\AppData\Local\Programs\Python\Python39\lib\site-packages\kivy\lang\builder.py", line 408, in load_string
     self._apply_rule(
   File "C:\Users\USER\AppData\Local\Programs\Python\Python39\lib\site-packages\kivy\lang\builder.py", line 661, in _apply_rule
     child.apply_class_lang_rules(
   File "C:\Users\USER\AppData\Local\Programs\Python\Python39\lib\site-packages\kivy\uix\widget.py", line 463, in apply_class_lang_rules
     Builder.apply(
   File "C:\Users\USER\AppData\Local\Programs\Python\Python39\lib\site-packages\kivy\lang\builder.py", line 541, in apply
     self._apply_rule(
   File "C:\Users\USER\AppData\Local\Programs\Python\Python39\lib\site-packages\kivy\lang\builder.py", line 710, in _apply_rule
     raise BuilderException(rule.ctx, rule.line,
 kivy.lang.builder.BuilderException: Parser: File "D:\GameDev-Programming\Python\kivyProjects\firstKivy\my.kv", line 20:
 ...
      18:        pos: 0, 0
      19:        id: page
 >>   20:        source: app.sourceToImage
      21:
      22:
 ...
 BuilderException: Parser: File "D:\GameDev-Programming\Python\kivyProjects\firstKivy\my.kv", line 20:
 ...
      18:        pos: 0, 0
      19:        id: page
 >>   20:        source: app.sourceToImage
      21:
      22:
 ...
 AttributeError: 'NoneType' object has no attribute 'bind'
   File "C:\Users\USER\AppData\Local\Programs\Python\Python39\lib\site-packages\kivy\lang\builder.py", line 242, in create_handler
     return eval(value, idmap), bound_list
   File "D:\GameDev-Programming\Python\kivyProjects\firstKivy\my.kv", line 20, in <module>
     source: app.sourceToImage
   File "C:\Users\USER\AppData\Local\Programs\Python\Python39\lib\site-packages\kivy\lang\parser.py", line 75, in __getattribute__
     object.__getattribute__(self, '_ensure_app')()
   File "C:\Users\USER\AppData\Local\Programs\Python\Python39\lib\site-packages\kivy\lang\parser.py", line 70, in _ensure_app
     app.bind(on_stop=lambda instance:
 
   File "C:\Users\USER\AppData\Local\Programs\Python\Python39\lib\site-packages\kivy\lang\builder.py", line 695, in _apply_rule
     value, bound = create_handler(
   File "C:\Users\USER\AppData\Local\Programs\Python\Python39\lib\site-packages\kivy\lang\builder.py", line 245, in create_handler
     raise BuilderException(rule.ctx, rule.line,

full kv file:

WindowManager:
    SelectingApp:
    ReaderApp:


<ReaderApp>:
    name: "reader"
    im: page

    canvas:
        Color:
            rgba: 1,1,1,0.17
        Rectangle:
            size: (root.width, root.height)
            pos: (0,0)
    Image:
        size: 500, 600
        pos: 0, 0
        id: page
        source: ""



<SelectingApp>
    name: "selecting"
    listSpin: spinnerId
    canvas:
        Color:
            rgba: 1,1,1,0.17
        Rectangle:
            size: (root.width, root.height)
            pos: (0,0)
    FloatLayout:
        Label:
            text: app.sourceToImage
            size_hint: 0.1,0.1
            pos_hint: {"x": 0.4, "y": 0.5}
        Button:
            text: "Go to reader"
            on_press: root.Change()
            size_hint: 0.5, 0.05
            pos_hint: {"x": 0.2}
        Spinner:
            id: spinnerId
            text: "Choose Manga"
            values: ["hey", "ey"]
            size_hint: 0.3, 0.1
            pos_hint: {"x":0.3, "y":0.4}
        Button:
            text: "refresh list"
            size_hint: 0.5, 0.05
            pos_hint: {"x": 0.2, "y": 0.2}
            on_press: root.Refresh()

full py file:

import os
from kivy.app import App
from kivy.properties import ObjectProperty
from kivy.config import Config
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.lang import Builder
from kivy.properties import StringProperty


width = 360
height = 640    # How does this make the resolution 641

Config.set('graphics', 'width', width)
Config.set('graphics', 'height', height)

Manga = "Seirei Gensouki - Konna Sekai De Deaeta Kimi Ni"
page = 0
arr = os.listdir(f"Manga/{Manga}")
MangaList = os.listdir("Manga")
fileCount = len(arr)


class WindowManager(ScreenManager):
    pass


class ReaderApp(Screen):
    im = ObjectProperty(None)

    def NextPage(self):
        global page
        if fileCount > page:
            page += 1
            if arr[0] == "1.jpg":
                self.im.source = f"Manga/{Manga}/{page}.jpg"
            else:
                self.im.source = f"Manga/{Manga}/{page}.png"
            print("next page")

    def PreviousPage(self):
        global page
        if page > 1:
            page -= 1
            if arr[0] == "1.jpg":
                self.im.source = f"Manga/{Manga}/{page}.jpg"
            else:
                self.im.source = f"Manga/{Manga}/{page}.png"
            print("previous page")

    def on_touch_down(self, touch):
        global page
        xPos, yPos, = touch.pos
        xPos = int(xPos)
        yPos = int(yPos)
        print(f"X: {xPos}\nY: {yPos}")
        if xPos > 300 and yPos > 600:
            sm.current = "selecting"
            f = open("Records", "r+")
            f.writelines(f"{Manga};{page}")
            f.close()
        elif self.width * .30 > xPos:
            self.PreviousPage()
        elif xPos > self.width * .70:
            self.NextPage()


class SelectingApp(Screen):
    listSpin = ObjectProperty(None)

    def Refresh(self):
        self.listSpin.values = MangaList

    def Change(self):
        global page
        f = open("Records", "r")
        records = f.read().split("\n")
        print(records)
        for lines in records:
            print(lines)
            mangaTitle, mangaPage = lines.split(";")
            print(mangaTitle, mangaPage)
            if mangaTitle == Manga:
                page = int(mangaPage)
        sm.current = "reader"


kv = Builder.load_file("my.kv")

sm = WindowManager()
screens = [SelectingApp(name="selecting"), ReaderApp(name="reader")]
for screen in screens:
    sm.add_widget(screen)


class MyApp(App):
    sourceToImage = StringProperty(f"Manga/{Manga}/{page}.jpg")

    def build(self):
        return sm


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

Solution

  • Several problems with your code:

    • Your kv code references app, but the kv is loaded before the App is created. That is why you get the message about NoneType (app is None at that point). You can fix that by just moving the kv = Builder.load_file("my.kv") inside the build() method of the App. But see the following notes.

    • You are building the App widget tree twice. Once with the line kv = Builder.load_file("my.kv"), and again with the code:

       sm = WindowManager()
       screens = [SelectingApp(name="selecting"), ReaderApp(name="reader")]
       for screen in screens:
           sm.add_widget(screen)
      

    You can eliminate both the above code and the Builder.load_file() code. See below.

    • Since you have named the kv file as my.kv, it will be loaded automatically by kivy, so you don't need either of the above techniques for building the App widget tree.

    So your App class can be simply:

    class MyApp(App):
        sourceToImage = StringProperty(f"Manga/{Manga}/{page}.jpg")
    

    with no build() method at all. Then, in the methods where you reference sm.current, you can use self.manager.current.