Home > Blockchain >  kivy python: how to update form label based on external function response in multi-screen configur
kivy python: how to update form label based on external function response in multi-screen configur

Time:11-27

Hi to all kivy/python experts. I am looking for an advice/solution, after trying everything I found for the last week.

Here's my scenario:

  1. I have a kivy app with 2 screens, therefore I am using a Screen manager, with each screen (1 and 2) defined in my .kv file
  2. after clicking log-in on my 1st screen, app jumps to 2nd one. In this second screen, I have a button and a label (keeping it simple for the example). Now the issue:
  • when clicking on the button, I am calling a method foo1() which is in the same class: Screen2. foo1() is called on_press with a specific argument (from within .kv file, using on_press = root.foo1(arg))
  • based on the arg received, the function calculates something, then calls another function foo2(), passing the result. The foo2() is located in another python file(external.py), within another class (so we have different file, different class and in that class a method: foo2). All good so far
  • after finalizing some calculations, foo2() should return the result. Where? Well, inside my kivy label, on Screen2.

Problem encountered:

When trying to write the result from foo2() into my text label (my_label) in screenTwo, **foo2() **fails to instantiate the ids of the screen 2 (throwing all sorts of errors, depending on the things I tried (see below).

I can understand the reason: The moment I am calling foo2() from foo1() we are exiting the screenTwo as a parent for my_label, which changes the "self" context (of self.ids.my_label.text = result from foo2()). This now no longer refers to screenTwo, but probably to the in-memory address of the foo2() function which I think now acts as a parent (at least this is what I concluded, I am still a beginner in python).

Things I tried:

Basically everything I could find, to try and find the "real" parent that I need to enumerate, to find the children IDs where my_label actually is, but wasn't able to:

  1. tried declaring an ObjectProperty = None inside my Screen2 class, then give an id to my label and then a variable name which refers to that id inside my Screen2 class. This is recommended by many tutorials, but this is working when passing things between screens, not in my case.

  2. tried changing (inside foo2()) the reference to the destination of the label, from self.ids to

  • root.self.ids.my_label.text
  • root.manager.get_current('screenTwo').ids.my_label.text
  • main.root.manager.get_current('screenTwo').ids.my_label.text
  • app.main.root.manager.ids('screenTwo').ids.my_label.text
  • and maaaany other... :(

Depending on what I've tried (many tries) I received various errors. Here's some:

- kivy AttributeError: 'super' object has no attribute 'getattr' thread here also

- NameError: name 'root' is not defined

- AttributeError: 'str' object has no attribute 'text'

- Kivy AttributeError: 'NoneType' object has no attribute 'ids'

I do not seem to understand how this self/root/app context works, despite looking up at various resources (stackoverflow, pdf files, kivy official documentation) (if someone could recommend a good tutorial or explain for all newbies like me out there...wow how helpful it would be).

Also, I couldn't find anything related to whether this is actually possible, considering that from within the class that holds the current screen you're actually calling an external function located in another py file: **does kivy even support passing down responses from external functions back to the main function? **(I assume it does, since this is pure python, not kivy. kivy just manages the writing, within the correct context....if I were just able to figure it out :( ).

Anyway, here's my sample code (py kv file). If any of you could give me a hint or a solution to how I could call a function which calls an external function which then writes the response back on the screen from which I started the events, in a label, I would be very thankful!

main.py

from kivy.uix.screenmanager import ScreenManager, Screen
from kivymd.app import MDApp
from kivy.app import App
from kivymd.uix.label import MDLabel
from kivy.properties import ObjectProperty, StringProperty
import external


class Screen1Main(Screen):
    pass

class Screen2(Screen):
    def foo1():
        # 1. do something 
        myArg = "x"

        # 2. then call foo2()
        external.myExternalClass.foo2(myArg)


class WindowManager(ScreenManager):
    pass



class MainApp(MDApp):
    def __init__(self, **kwargs):
        self.title = "My Application"
        super().__init__(**kwargs)


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

external.py

from kivy.app import App
import main

class myExternalClass:

    def foo2(arg1):

        #1. does something
            blabla
            
        #2. gets the result
            myResult = "anything"
       

       #3. tries to write the result into my_label (located in Screen 2, which is a child 
       # of my main app (in file: main.py), who manages the multi-screen via a screen manager within 
       # WindowsManager class)
        
       Screen2.manager.get_screen("screenTwo").ids.my_label.text = myResult    

---

main.kv

WindowManager:
    Screen1Main:
        id: id_screenOne
    Screen2:
        id: id_screenTwo
        

<Screen1Main>:
    name: "screenOne"
    GridLayout:
       <rest of layout here>

<Screen2>:
    name: "screenTwo"

    GridLayout:
        cols: 2        
        MDLabel:
            id: my_label
            text: "-"

        MDIconButton:
            id: my_button
            icon: "message-arrow-left"
            on_release: root.foo1(arg0)


# Things I tried:
Basically everything I could find, as described above.

CodePudding user response:

I was not sure of the utility of having the separate class - but tried to give some examples here of passing information around. I think the key is to set up object references in your main App.

main.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from kivy.uix.screenmanager import ScreenManager, Screen
from kivymd.app import MDApp
from kivymd.uix.label import MDLabel
from kivymd.uix.textfield import MDTextField
from kivy.lang import Builder


class Screen1Main(Screen):
    kv_text_field: MDTextField


class Screen2(Screen):
    my_label: MDLabel

    def __init__(self, **kwargs):
        super().__init__(**kwargs)


class myExternalClass:

    def foo2(self, arg):
        self.myResult = "anything"
        self._screen2.my_label.text = arg

    def __init__(self, screen2):
        self.myResult = ""
        # get a reference to this screen so you can access it in this class instance
        self._screen2: Screen2 = screen2


class WindowManager(ScreenManager):
    pass


class MainApp(MDApp):

    def foo1(self, my_button):
        # 1. do something
        myArg = "from main App"
        print(f"{my_button}, {type(my_button)}")

        # 2. then call foo2()
        self.my_external_class.foo2(myArg)

    def main_screen(self, my_button):
        print(my_button)
        self.my_top_widget.current = "Screen1Main"

    def kv_change_screen(self, my_button, arg: str):
        print(f"{self}, {my_button}, {arg}")
        self.screen2.my_label.text = arg
        self.my_top_widget.current = "Screen2"

    def build(self) -> WindowManager:

        self.my_top_widget.add_widget(self.screen1main)
        self.my_top_widget.add_widget(self.screen2)
        self.my_top_widget.current="Screen1Main"
        return self.my_top_widget

    def __init__(self, **kwargs):
        self.title = "My Application"
        super().__init__(**kwargs)
        self.my_top_widget = WindowManager()
        self.screen2 = Screen2(name="Screen2", )
        self.screen1main = Screen1Main(name="Screen1Main")
        self.screen1main.kv_text_field.text = "message to screen 2"
        self.my_external_class = myExternalClass(screen2=self.screen2)


if __name__ == "__main__":
    # load the file by name - this is more clear
    Builder.load_file("main_app.kv")
    my_app = MainApp()
    my_app.run()

main_app.kv

#:import kivy kivy
# main_app.kv
<Screen1Main>:
    kv_text_field: kv_text_field
    MDBoxLayout:
        orientation: 'vertical'
        MDTextField:
            id: kv_text_field
        MDIconButton:
            text: "button"
            icon: "message-arrow-right"
            on_press: app.kv_change_screen(self, kv_text_field.text)


<Screen2>:
    my_label: my_label
    GridLayout:
        cols: 2
        Label:
            text: "Screen2"
        MDLabel:
            id: my_label
            text: "-"

        MDFlatButton:
            id: my_button
            text: "foo1"
            on_release: app.foo1(self)
        MDIconButton:
            id: my_button
            icon: "message-arrow-left"
            on_release: app.main_screen(self)

you may not need your class definition WindowManager and you can directly use ScreenManager in place if you are not going to augment that class.

I hope the examples here can help you even if I didn't directly follow the exact flow and structure of your app.

CodePudding user response:

Try using:

MDApp.get_running_app().root.get_screen('screenTwo').ids.my_label.text = myResult

in your foo2() method. This gets the MDApp and references its root (the WindowManager) to get the the desired Screen.

  • Related