Search code examples
pythonkivy

Link function from my py file python script to my button widget in my kv kivy file


I'm trying to program a simple test app where

  • First screen: says "hi", then below there is a "next" button to click and access to the next page
  • Second page: says "enter your email address", then below there is a text input box to write the email address, and a "validate" button below it, to send the email address to an online ElephantSQL database

I have a .py file with the python script where you have the "submit" function, which is supposed to send the email address to the online ElephantSQL database, and finally give a "thank you" message once it's done. It seems like I'm doing something wrong as I get an error which tells me the below:

 Traceback (most recent call last):
   File "C:\Users\User\Documents\Pers\Python\test\test.py", line 99, in <module>
     Yes().run()
   File "C:\Users\User\AppData\Local\Programs\Python\Python39\lib\site-packages\kivy\app.py", line 955, in run
     runTouchApp()
   File "C:\Users\User\AppData\Local\Programs\Python\Python39\lib\site-packages\kivy\base.py", line 574, in runTouchApp
     EventLoop.mainloop()
   File "C:\Users\User\AppData\Local\Programs\Python\Python39\lib\site-packages\kivy\base.py", line 339, in mainloop
     self.idle()
   File "C:\Users\User\AppData\Local\Programs\Python\Python39\lib\site-packages\kivy\base.py", line 383, in idle
     self.dispatch_input()
   File "C:\Users\User\AppData\Local\Programs\Python\Python39\lib\site-packages\kivy\base.py", line 334, in dispatch_input
     post_dispatch_input(*pop(0))
   File "C:\Users\User\AppData\Local\Programs\Python\Python39\lib\site-packages\kivy\base.py", line 302, in post_dispatch_input
     wid.dispatch('on_touch_up', me)
   File "kivy\_event.pyx", line 731, in kivy._event.EventDispatcher.dispatch   
   File "C:\Users\User\AppData\Local\Programs\Python\Python39\lib\site-packages\kivy\uix\behaviors\button.py", line 179, in on_touch_up
     self.dispatch('on_release')
   File "kivy\_event.pyx", line 727, in kivy._event.EventDispatcher.dispatch   
   File "kivy\_event.pyx", line 1307, in kivy._event.EventObservers.dispatch   
   File "kivy\_event.pyx", line 1191, in kivy._event.EventObservers._dispatch  
   File "C:\Users\User\AppData\Local\Programs\Python\Python39\lib\site-packages\kivy\lang\builder.py", line 55, in custom_callback
     exec(__kvlang__.co_value, idmap)
   File "C:\Users\User\Documents\Pers\Python\test\Test.kv", line 77, in <module>
     on_release: app.submit()
   File "C:\Users\User\Documents\Pers\Python\test\test.py", line 87, in submit
     'first': kv.input_box.text,
 AttributeError: 'WindowManager' object has no attribute 'input_box'

Below are both the scripts from my .py file and my .kv file

My test.py file:

from kivy.app import App
from kivy.uix.gridlayout import GridLayoutException
from kivy.uix.label import Label
from kivy.uix.image import Image
from kivy.uix.button import Button
from kivy.uix.textinput import TextInput
from kivy.uix.widget import Widget
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
import os
import urllib.parse as up
import psycopg2

#define our different windows (hence our different pages in our app)
class FirstWindow(Screen):
    pass

class SecondWindow(Screen):
    pass

#define our screen manager (hence what manages the different pages in our app)
class WindowManager(ScreenManager):
    pass 

# designate our .kv design file
kv = Builder.load_file('Test.kv')

class Yes(App):
    def build(self):

        url = up.urlparse("DBURL")

        # connect to the database 
        conn = psycopg2.connect("dbname='DBNAME' user='DBUSER' host='DBHOST' password='DBPWD'")

        # create the database cursor
        cur = conn.cursor()
        # insert the query to create the table (if not yet
        #  created) in which the data (email addresses
        # will be stored)
        cur.execute("CREATE TABLE if not exists emails(id INTEGER PRIMARY KEY, address text)")
        # commit changes
        conn.commit()
        # close the connection to the database
        conn.close()

        return kv
    
    # define our submit function, i.e. the function which will be
    # triggered when we click to send the email address to be
    # stored in the database    
    def submit(self):

        # database URL
        url = up.urlparse("DBURL")

        # connect to the database 
        conn = psycopg2.connect("dbname='DBNAME' user='DBUSER' host='DBHOST' password='DBPWD'")
            
        # create the database cursor
        cur = conn.cursor()
        # insert the query to submit the data,
        # i.e. email addresses to the database 
        psycopg_command = "INSERT INTO emails (address) VALUES (%s)"
        values = (kv.get_screen('second').ids.input_box.text,)
        cur.execute(psycopg_command, values)

        # add message to user once email address has been submitted
        kv.get_screen('second').ids.input_box.text = "Thanks for submitting your email address"
        # clear the input box once the email address has been submitted
        '''kv.get_screen('second').ids.input_box.text = ""'''
        # commit changes
        conn.commit()
        # close the connection to the database
        conn.close()

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

My test.kv file:

#:import NoTransition kivy.uix.screenmanager.NoTransition

WindowManager:
    FirstWindow:
    SecondWindow:

<FirstWindow>:
    name: "first"

    BoxLayout:
        orientation: "vertical"
        size: root.width, root.height

        Label:
            text: "Hi"
            font_size: 32

        Button:
            text: "Next"
            size_hint: 0.3, 0.3
            pos_hint: {"center_x":0.5}
            background_color: 0.06666666666, 0.82352941176, 0.90980392156, 1
            background_normal: ""
            on_release: 
                app.root.current = "second"
                root.manager.transition.direction = "left"

        Widget:
            size_hint: 0.3, None
            height: 6
            canvas:
                Color: 
                    rgb: 0, 0, 0, 1

<SecondWindow>:
    name: "second"

    BoxLayout:
        orientation: "vertical"
        size: root.width, root.height


        Label:
            id: word_label
            text: "Enter your email address"
            font_size: 32

        Widget:
            size_hint: 0.3, None
            height: 6
            canvas:
                Color: 
                    rgb: 0, 0, 0, 1

        TextInput:
            id: input_box
            size_hint: (0.3, 0.2)
            pos_hint: {"center_x":0.5}
            multiline: False
            valign: "center"
            halign: "center"
            text:""

        Widget:
            size_hint: 0.3, None
            height: 6
            canvas:
                Color: 
                    rgb: 0, 0, 0, 1

        Button:
            text: "Validate"
            size_hint: 0.3, 0.3
            pos_hint: {"center_x":0.5}
            background_color: 0.06666666666, 0.82352941176, 0.90980392156, 1
            background_normal: ""
            on_release: app.submit()
                

        Widget:
            size_hint: 0.3, None
            height: 6
            canvas:
                Color: 
                    rgb: 0, 0, 0, 1

        Button:
            text: "Home"
            size_hint: 0.3, 0.3
            pos_hint: {"center_x":0.5}
            background_color: 0.06666666666, 0.82352941176, 0.90980392156, 1
            background_normal: ""
            on_release:
                app.root.current = "first"   
                root.manager.transition = NoTransition()

        Widget:
            size_hint: 0.3, None
            height: 6
            canvas:
                Color: 
                    rgb: 0, 0, 0, 1

I understand that it says my "WindowsManager" doesn't have the attribute "input_box", but I'm clueless as to what to do to solve the issue. I tried some tweaks, but I won't overcharge the content of my question and leave it at asking: How to solve my mistake?

Thks


Solution

  • So your variable named kv is a reference to your WindowManager, which is an instance of ScreenManager. You can use the get_screen() method of ScreenManager (see the docs) to access the Screen that contains the TextInput that you want to update. So, try changing:

    kv.input_box.text = "Thanks"
    

    to:

    kv.get_screen('second').ids.input_box.text = "Thanks"
    

    Also note that if your set the text of a TextInput, then immediately clear that text, then you will never see the first text.