Not all errors get flagged, example below: on_press inside a python Button does not complain (as it does for id)

120 views
Skip to first unread message

Gary Kuipers

unread,
Mar 3, 2023, 12:16:50 PM3/3/23
to Kivy users support
Button does not trigger method

    self.exit_button = Button(text="Regresar", on_press=self.switch_screens('home'), size_hint=(1, 0.3))
    The button renders on the screen and reacts when pressedbut it does not execute the method sepcified "on_press".The print tat the start of the method is not output.

    def switch_screens(self, switch_to, *args):
        print(f"PhotoScreen2: switch_screens:  {switch_to})")
        if switch_to == 'home':
            print(f"PhotoScreen2: switch_screens({switch_to})")
            self.screen_manager.current = '0'
           
Notes: the examples I researched all use the bind method after declaring the button, BUT! there are no colplaints on execution

I mangled the declaration by including an id:
self.exit_button = Button(id="kk" text="Regresar", on_press=self.switch_screens('home'), size_hint=(1, 0.3))

The execution complaint was:
TypeError: Properties ['id'] passed to __init__ may not be existing property names. Valid properties are ['always_release', 'anchors', 'background_color', 'background_disabled_down', 'background_disabled_normal', 'background_down', 'background_normal', 'base_direction', 'bold', 'border', 'center', 'center_x', 'center_y', 'children', 'cls', 'color', 'disabled', 'disabled_color', 'disabled_image', 'disabled_outline_color', 'ellipsis_options', 'font_blended', 'font_context', 'font_family', 'font_features', 'font_hinting', 'font_kerning', 'font_name', 'font_size', 'halign', 'height', 'ids', 'is_shortened', 'italic', 'last_touch', 'line_height', 'markup', 'max_lines', 'min_state_time', 'mipmap', 'motion_filter', 'opacity', 'outline_color', 'outline_width', 'padding', 'padding_x', 'padding_y', 'parent', 'pos', 'pos_hint', 'refs', 'right', 'shorten', 'shorten_from', 'size', 'size_hint', 'size_hint_max', 'size_hint_max_x', 'size_hint_max_y', 'size_hint_min', 'size_hint_min_x', 'size_hint_min_y', 'size_hint_x', 'size_hint_y', 'split_str', 'state', 'state_image', 'strikethrough', 'strip', 'text', 'text_language', 'text_size', 'texture', 'texture_size', 'top', 'underline', 'unicode_errors', 'valign', 'width', 'x', 'y']

on_press is not on the list of Valid properties. But its use does not get flagged as an error.

In my opinion this needs to complain, otherwise semi-beginners like me get extra confused!

The takeaway is to use the bind method.

"Experience is what you get ... when you don't get wat you want!"

Gary Kuipers

unread,
Mar 3, 2023, 12:36:13 PM3/3/23
to Kivy users support
Now I am really confused:

self.exit_button = Button(text="Regresar", on_press=self.switch_screens('home'), size_hint=(1, 0.3))
to:
self.exit_button = Button(text="Regresar", size_hint=(1, 0.3))
self.exit_button.bind(on_press=self.switch_screens('home'))


and the behaviour does not change.

Why?

Elliot Garbus

unread,
Mar 3, 2023, 12:52:28 PM3/3/23
to kivy-...@googlegroups.com

You want to use the name of the function in the on_press, if the function has the () then you are executing the function at the time you are doing the assignment and assigned the returned value to the on_press.

 

You could use functools partial or a lambda to set the callback.  I find it most coinvent to use lambda.  Example below.

 

from kivy.app import App
from kivy.lang import Builder
from kivy.uix.button import Button

from functools import partial

kv =
"""
BoxLayout:
    orientation: 'vertical'

"""

class BindButtonApp(App):
   
def build(self):
       
return Builder.load_string(kv)

   
def callback(self, message, widget):
       
print(message)

   
def on_start(self):
        p = partial(
self.callback, "the parameter in a partial")
        button = Button(
text='use partial', on_release=p)
       
self.root.add_widget(button)
        button = Button(
text='use lambda', on_release=lambda x: print('Used lambda'))
       
self.root.add_widget(button)


BindButtonApp().run()

--
You received this message because you are subscribed to the Google Groups "Kivy users support" group.
To unsubscribe from this group and stop receiving emails from it, send an email to kivy-users+...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/kivy-users/18aea999-d192-4e00-9c8c-a95c1efc03d9n%40googlegroups.com.

 

Gary Kuipers

unread,
Mar 4, 2023, 2:54:52 PM3/4/23
to kivy-...@googlegroups.com
ONe of the things about learning python is that it has many ways of expressing the same thing. Wonderful for experts, no so much for beginners.

I don't know what the correct term is for it but let's call them "dialects".

As one hunts about the internet for "how tos" one sees common idioms and uncommon idioms for the same problem.:

Here the problem is to get a button to execute a method "on_press", which is the button's raison d'etre:

Warning: I am probably misapplying the dialects, ergo the errors. My request  from "El Guru Eliot" would be to correct and explain each dialect so that we have sort of a "dialect cookbook" when we are finished.


The "embedded" dialect: the on_press is in the Button declaration and uses the method ad the parameter
=====================================================================================================
def __init__(self ...
        ...
        self.exit_button = Button(text="Regresar", on_press=self.switch_screens_home, size_hint=(1, 0.3))
        # self.exit_button.bind(on_press=self.switch_screens('home'))
       
    def switch_screens_home(self):
        self.switch_screens('home')


    def switch_screens(self, switch_to, *args):
        print(f"PhotoScreen2: switch_screens:  {switch_to})")
        if switch_to == 'home':
            print(f"PhotoScreen2: switch_screens({switch_to})")
            self.screen_manager.current = '0'
           
Came up with an error: TypeError: switch_screens_home() takes 1 positional argument but 2 were given
I do not see where. The on_press is a function, not a call and there are no parameters used
=====================================================================================================


The "bind to a method" dialect: the "on_press" is in a bind statement and uses the method as the parameter
=====================================================================================================
    def __init__(self ...
        ...
        self.exit_button = Button(text="Regresar", size_hint=(1, 0.3))
        self.exit_button.bind(on_press=self.switch_screen)
           
    def switch_screens_home(self):
        self.switch_screens('home')


    def switch_screens(self, switch_to, *args):
        print(f"PhotoScreen2: switch_screens:  {switch_to})")
        if switch_to == 'home':
            print(f"PhotoScreen2: switch_screens({switch_to})")
            self.screen_manager.current = '0'  
           
Same thing: TypeError: switch_screens_home() takes 1 positional argument but 2 were given
=====================================================================================================


The "bind to the execution" dialect: the "on_press" is in a bind statement and uses the method+parameters as an execution call
=====================================================================================================
    def __init__(self ...
        ...
        self.exit_button = Button(text="Regresar", size_hint=(1, 0.3))
        self.exit_button.bind(on_press=self.switch_screens('home')


    def switch_screens(self, switch_to, *args):
        print(f"PhotoScreen2: switch_screens:  {switch_to})")
        if switch_to == 'home':
            print(f"PhotoScreen2: switch_screens({switch_to})")
            self.screen_manager.current = '0'
           
This one, the button reacts but nothing happens:
=====================================================================================================


The "let's confuse the living heck out of beginners" dialect: the "on_press" enters a Rube Goldberg machine of linkages to do something after lots of activity
=====================================================================================================
    def __init__(self ...
        ...
        (all references to the exit button are pulled out of __init__)


    def callback(self, message, widget):
        print(message)

    # Eilot: I have no idea what the code is supposed to accomplish. I subtituted whet I wthough was required into the code you provided but ... no F,ing idea!
    def on_start(self):
        p = partial(self.switch_screens_home, "the parameter in a partial")
        #button = Button(text='use partial', on_release=p)
        self.exit_button = Button(text="Regresar", on_release=p, size_hint=(1, 0.3))
        #self.root.add_widget(button)
        self.main_grid.add_widget(self.exit_button)
        #button = Button(text='use lambda', on_release=lambda x: print('Used lambda'))
        self.exit_button = Button(text="Regresar2?", on_release=lambda x: print('Used lambda'))
        self.main_grid.add_widget(self.exit_button)n)
       
    def switch_screens_home(self):
        self.switch_screens('home')


    def switch_screens(self, switch_to, *args):
        print(f"PhotoScreen2: switch_screens:  {switch_to})")
        if switch_to == 'home':
            print(f"PhotoScreen2: switch_screens({switch_to})")
            self.screen_manager.current = '0'

In this case the button did not even render so i couldnot test it:

  (Honestly Elliot, In all my searches I have not seen anything like this. if this is the simplest way to do this then I will make a valiant effort to learn, but it seems way too difficult a way to perform the button's primary function, which is to communicate the user's intent to have something specific happen.)
 
 This complicates things because it takes the Button declaration outside the __init__ where everything is being declared. Is that so that the button does not get executed on instantiation of the class? If so, what is the purpose of the bind statement in the bind dialect and why is "on_press" allowed in the Button's creation?
  



--
Gary Kuipers, President, Casinfo

Elliot Garbus

unread,
Mar 4, 2023, 7:00:13 PM3/4/23
to kivy-...@googlegroups.com

I hope the discussion below helps.  I’m trying to answer your question, and offer explanations and options.  Let me know if this cause more confusion than illumination.

 

If you look at the documentation for bind: https://kivy.org/doc/stable/api-kivy.event.html?highlight=bind#kivy.event.EventDispatcher.bind

You see that:

In general, property callbacks are called with 2 arguments (the object and the property’s new value) and event callbacks with one argument (the object).

 

The on_press method is being called from the event loop, and it is being called with the object that caused the event.

 

from kivy.app import App
from kivy.lang import Builder
from kivy.uix.button import
Button

kv =
"""
BoxLayout:
    orientation: 'vertical'
    TestButton:
        text: 'Button 1'
    TestButton:
        text: 'Button 2'
"""


class TestButton(Button):
   
def __init__(self, **kwargs):
       
super().__init__(**kwargs)
       
self.bind(on_press=self.on_press_cb)
       
self.bind(on_release=self.on_release_cb)

   
def on_press_cb(self, button):
       
print(button, button.text)
       
# this is redundant because we can do the same with self
       
print(self, self.text)

   
def on_release_cb(self, _):
       
# we can use _ to indicate we are not using the returned value
       
print(f'{self.text} released')


class BindButtonApp(App):
   
def build(self):
       
return
Builder.load_string(kv)


BindButtonApp().run()

 

If you want to pass an argument to the callback there are 4 options.

  1. Use a partial, https://docs.python.org/3/library/functools.html#functools.partial
  2. Use a lambda – A lambda is an anonymous  function.  This is effectively the same as creating a stub, but more convenient.
  3. Create a stub function, so you call a function with no parameters, and from that actually make the call you want
  4. Use kv – this is my preferred option.

 

 

Here is an example of each type

from functools import partial

from kivy.app import App
from kivy.lang import Builder
from kivy.uix.button import
Button

kv =
"""
<TestButtonKV>:
    on_press: self.on_press_cb('You can easily pass the message from kv')
    on_release: self.on_release_cb('You can also access the args from kv', *args)

BoxLayout:
    orientation: 'vertical'
    TestButtonPartial:
        text: 'Button Partial'
    TestButtonLambda:
        text: 'Button Lambda'
    TestButtonStub:
        text: 'Button Stub'
    TestButtonKV:
        text: 'Button KV'
"""


class TestButtonPartial(Button):
   
def __init__(self, **kwargs):
       
super().__init__(**kwargs)

        p = partial(
self.on_press_cb, "Callback message with partial")
       
self.bind(on_press=p)

   
def on_press_cb(self, message, obj):  # obj os the same as self, so I would typically make this a _
       
print(self, obj, self.text, message, obj)


class TestButtonLambda(Button):
   
def __init__(self, **kwargs):
       
super().__init__(**kwargs)
       
self.bind(on_press=lambda obj: self.on_press_cb("Callback message from lambda"))
       
# the on_press event is called and passes obj to the lambda function, it does not pass it on

        # in the on_release, we forward the obj to the on_release_cb
        # there is no real need to do this - I'm just showng how a lambda works.
       
self.bind(on_release=lambda obj: self.on_release_cb("passing a message and the obj", obj))

   
def on_press_cb(self, message):  # obj os the same as self, so I would typically make this a _
       
print(self, self.text, message)

   
def on_release_cb(self, message, obj):
       
print(self, self.text, message, obj)


class TestButtonStub(Button):
   
def __init__(self, **kwargs):
       
super().__init__(**kwargs)
       
self.bind(on_press=self.on_press_cb)

   
def on_press_cb(self, _):
       
# not parameter is passed, create a seperate method for each paramater you want passed..
        # you might for example create a stub for each screen you want to change to (yuck).
       
print(f'{self.text} No parameter was passed')


class TestButtonKV(Button):
   
def on_press_cb(self, message):
       
print(f'{self.text} {message}')

   
def on_release_cb(self, message, obj):
       
print(f'{self.text} {message} {obj}')


class BindButtonApp(App):
   
def build(self):
       
return
Builder.load_string(kv)


BindButtonApp().run()

Elliot Garbus

unread,
Mar 4, 2023, 7:08:13 PM3/4/23
to kivy-...@googlegroups.com

I did not answer this questions in my longer response:

 

The "bind to the execution" dialect: the "on_press" is in a bind statement and uses the method+parameters as an execution call
=====================================================================================================
    def __init__(self ...
        ...
        self.exit_button = Button(text="Regresar", size_hint=(1, 0.3))
        self.exit_button.bind(on_press=self.switch_screens('home')

    def switch_screens(self, switch_to, *args):
        print(f"PhotoScreen2: switch_screens:  {switch_to})")
        if switch_to == 'home':
            print(f"PhotoScreen2: switch_screens({switch_to})")
            self.screen_manager.current = '0'
           
This one, the button reacts but nothing happens:

In the line:

self.exit_button.bind(on_press=self.switch_screens('home'))

The call to the method self.switch_screens(‘home’) is made when the code is executed, and self.switch_screens(‘home’) returns None. 

This is equivalent to:  self.exit_button.bind(on_press=None)

 

 

 

From: Gary Kuipers
Sent: Saturday, March 4, 2023 12:54 PM
To: kivy-...@googlegroups.com

Gary Kuipers

unread,
Mar 5, 2023, 5:43:55 AM3/5/23
to kivy-...@googlegroups.com
I much appreciate your patience and explanations. Unfortunately, when executed, the example program presentes only a blank screen.

My rabbit hole heap has overflowed! Perhaps we should revisit the start of all of this: The c4K_photo_example. This can be found at: https://github.com/Android-for-Python/c4k_photo_example/blob/main/main.py

Looking specifically at applayout/photoscreen1.py:
class ButtonsLayout1(RelativeLayout):
   
    def __init__(self, **args):
        Builder.load_string(BL1)
        super().__init__(**args)

I needed to get an object in there for some purpose. I forget which one it was (rabbit hole heap overflow).

The problem with my limited experience was how to drill down through 3 layers of KV Land to insert the object where I needed it.

That caused me to try to eliminate KV Lang and go to python, which caused another problem: I could not switch screens.

That caused me to abandon SwipeScreen and use buttons, which is where I am now.

Perhaps you could explain using applayout/photoscreen1.py: how I can get an object from main.py into ButtonsLayout1

(By the way, the rest of my program works incredibly well! Android functionality, communications, server, database, everything is close to ready. Just this switching screens thing!)

The only reason I have to switch screens is because if the user makes a mistake in one screen he can use the second screen to fix mistakes. Worst case I just split this into 2 separate apps of one screen each).

Again, thanks for the help. I am a backend and comms guy (40 years!) so front end is an area of inexperience for me. 








Elliot Garbus

unread,
Mar 5, 2023, 6:44:05 AM3/5/23
to kivy-...@googlegroups.com
It looks like one line in my code was modified by the site.  

class BindButtonApp(App):
   
def build(self):
       
return Builder.load_string(kv)
Make sure build() is returning Builder.load_string(kv)
I’ll take a look at your question later today 


Sent from my iPhone

On Mar 5, 2023, at 3:43 AM, Gary Kuipers <gary.k...@casinfosystems.com> wrote:



Elliot Garbus

unread,
Mar 5, 2023, 12:03:59 PM3/5/23
to kivy-...@googlegroups.com

I’m not sure exactly the question you are asking – my interpretation is you are trying to set the value of a property that is in ButtonLayout1 from the App class.

I stripped down the code to fit in one file, but maintained the same widget hierarchy.

In MyApp on_start, I show how to access properties in ButtonLayout1.  Notice in ButtonLayout1 I have added a property called gk, it is used to set the text on one of the buttons.

 

from kivy.app import App
from kivy.lang import Builder
from kivy.properties import ObjectProperty, StringProperty
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.relativelayout import RelativeLayout
from kivy.core.window import Window
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.utils import platform



PS1 =
"""
<PhotoScreen1>:
    photo_preview: photo_layout.ids.preview
    PhotoLayout1:
        id:photo_layout
"""


class PhotoScreen1(Screen):
    photo_preview = ObjectProperty(
None)

   
def __init__(self, **args):
        Builder.load_string(PS1)
       
super().__init__(**args)


   
# def on_enter(self):
    #     self.photo_preview.connect_camera(filepath_callback=self.capture_path)
    #
    # def on_pre_leave(self):
    #     self.photo_preview.disconnect_camera()



PL1 = """
<PhotoLayout1>:
    Background1:
        id: pad_end
    Button: # Preview:
        id: preview
        # letterbox_color: .8, .3, 1, .5
    ButtonsLayout1:
        id: buttons

<Background1@Label>:
    canvas:
        Color:
            rgba: .8, .3, 1, .5
        Rectangle:
            pos: self.pos
            size: self.size
"""


class PhotoLayout1(BoxLayout):

   
def __init__(self, **args):
        Builder.load_string(PL1)
       
super().__init__(**args)

   
def on_size(self, layout, size):
       
if Window.width < Window.height:
           
self.orientation = 'vertical'
           
self.ids.preview.size_hint = (1, .8)
           
self.ids.buttons.size_hint = (1, .2)
           
self.ids.pad_end.size_hint = (1, .1)
       
else:
           
self.orientation = 'horizontal'
           
self.ids.preview.size_hint = (.8, 1)
           
self.ids.buttons.size_hint = (.2, 1)
           
self.ids.pad_end.size_hint = (.1, 1)


BL1 =
"""
<ButtonsLayout1>:
    Background1:
    Button:
        id:other
        text: root.gk
        # on_press: root.select_camera('toggle')
        height: self.width
        width: self.height
        # background_normal: 'icons/camera-flip-outline.png'
        # background_down:   'icons/camera-flip-outline.png'
    Button:
        id:flash
        # on_press: root.flash()
        height: self.width
        width: self.height
        # background_normal: 'icons/flash-off.png'
        # background_down:   'icons/flash-off.png'
    Button:
        id:photo
        # on_press: root.photo()
        height: self.width
        width: self.height
        # background_normal: 'icons/camera_white.png'
        # background_down:   'icons/camera_red.png'
"""

HS0 = """
<HomeScreen0>:
    AnchorLayout:
        Button:
            size_hint: None, None
            size: dp(200), dp(48)
            on_release: root.manager.current = root.manager.next()
            text: 'Next'
"""


class HomeScreen0(Screen):
   
def __init__(self, **kwargs):
        Builder.load_string(HS0)
       
super().__init__(**kwargs)


class ButtonsLayout1(RelativeLayout):
    gk = StringProperty()

   
def __init__(self, **args):
        Builder.load_string(BL1)
       
super().__init__(**args)

   
def on_size(self, layout, size):
       
if platform in ['android', 'ios']:
           
self.ids.photo.min_state_time = 0.3
       
else:
           
self.ids.photo.min_state_time = 1
       
if Window.width < Window.height:
           
self.ids.other.pos_hint = {'center_x': .2, 'center_y': .5}
           
self.ids.other.size_hint = (.2, None)
           
self.ids.photo.pos_hint = {'center_x': .5, 'center_y': .5}
           
self.ids.photo.size_hint = (.24, None)
           
self.ids.flash.pos_hint = {'center_x': .8, 'center_y': .5}
           
self.ids.flash.size_hint = (.15, None)
       
else:
           
self.ids.other.pos_hint = {'center_x': .5, 'center_y': .8}
           
self.ids.other.size_hint = (None, .2)
           
self.ids.photo.pos_hint = {'center_x': .5, 'center_y': .5}
           
self.ids.photo.size_hint = (None, .24)
           
self.ids.flash.pos_hint = {'center_x': .5, 'center_y': .2}
           
self.ids.flash.size_hint = (None, .15)

class MyApp(App):

   
def build(self):
       
self.enable_swipe = False
       
self.sm = ScreenManager()
       
self.screens = [HomeScreen0(name='0'),
                       
PhotoScreen1(name='1')]
       
for s in self.screens:
           
self.sm.add_widget(s)
       
return self.sm

   
def on_start(self):
       
# set text of flash button, using ids to 'walk' the widget hierarchy
       
self.root.get_screen('1').ids.photo_layout.ids.buttons.ids.flash.text = 'Flash'
       
# write to a property in ButtonLayout1
       
self.root.get_screen('1').ids.photo_layout.ids.buttons.gk = 'The gk property'

MyApp().run()

Gary Kuipers

unread,
Mar 6, 2023, 4:59:15 PM3/6/23
to kivy-...@googlegroups.com
HI Elliot:

What I am trying to pass to the ButtonsLayout1 class (ButtonsLayout0 in my case) is the screen manager that is created in the build method of the MyApp
I am still lost, but in the context of your example, whatI am trying to do is to get the "Flash" button to return to homescreen0

My analogue to the your Flash Button is the REGRESAR button

THis is part on my main.py:
def build(self):
logger = Logger()
self.enable_swipe = False
self.sm = MyScreenManager()
home_screen = HomeScreen0(filemanager, logger.log, name='0')
self.sm.add_widget(home_screen)
photo_screen = PhotoScreen1(self.sm, filemanager, name='1')
photo_screen.screen_manager = self.sm
self.sm.add_widget(photo_screen)
review_screen = PhotoScreen2(self.sm, filemanager, name='2')
review_screen.screen_manager = self.sm
self.sm.add_widget(review_screen)

# ps1 = PhotoScreen1(name='1')
# self.sm.add_widget(ps1)
if platform == 'android':
Window.bind(on_resize=hide_landscape_status_bar)

return self.sm

Here is my PhotoScreen1 (partially)
class PhotoScreen1(Screen):
photo_preview = ObjectProperty(None)

    def __init__(self, screen_manager, filemanager, **kwargs):
Builder.load_string(PS1)
super().__init__(**kwargs)
self.screen_manager = screen_manager
self.filemanager = filemanager
print(f"PhotoScreen1: Init")

def on_enter(self):
self.screen_manager = self.manager
self.photo_preview.connect_camera(filepath_callback=self.capture_path)

# BL0 = """
# <ButtonsLayout0>:
# canvas.before:
# Color:
# rgba: 1, 1, 1, 1
# Rectangle:
# size: self.size
# pos: self.pos
# GridLayout:
# cols: 2
# Image:
# source: 'logo_transparent.png'
# size_hint: .6, 1
# background_color: 0, 0, 1, 1
# Button:
# id: btnExit
# canvas.before:
# Color:
# rgba: 1, .647, 0, 1
# Line:
# width: 8
# rectangle: self.x, self.y, self.width, self.height
# background_color: 0, 0, 0, 0
# text: "REGRESAR"
# bold: True
# #on_press: app.stop()
# on_press: root.screen_manager.current = '0'
# size_hint: .4, 1
# """
# class ButtonsLayout0(RelativeLayout):
# def __init__(self, **args):
# super().__init__(**args)
# Builder.load_string(BL0)
#
# TODO: should go back to homescreen 0
class ButtonsLayout0(RelativeLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)

# with self.canvas.before:
# Color(1, 1, 1, 1)
# self.rect = Rectangle(size=self.size, pos=self.pos)

self.grid = GridLayout(cols=2, size_hint=(1, 1))
self.add_widget(self.grid)

self.image = Image(source='logo_transparent.png', size_hint=(0.6, 1))
self.grid.add_widget(self.image)

self.btnExit = Button(background_color=(0, 0, 0, 0), text="REGRESAR", bold=True, size_hint=(0.4, 1))
# with self.btnExit.canvas.before:
# Color(1, 0.647, 0, 1)
self.btnExit.bind(on_press=lambda instance: setattr(self.screen_manager, 'current', '0'))
self.grid.add_widget(self.btnExit)






Elliot Garbus

unread,
Mar 6, 2023, 6:27:47 PM3/6/23
to kivy-...@googlegroups.com

You don’t need to pass the screenmanager through all those classes, although I’ll show you how to do that in follow up message.

 

You know the ScreenManager is the root widget.  In python in the App class, you can access the ScreenManager as self.root.

In any other python class you can access the screenmanager (the root widget) as follows

 

class MyClass(MyWidet):

    def my_method(self):

        app = App.get_running_app() # returns the app instance

        app.root.current = ‘0’ # will change to screen ‘0’

 

In kv you can simply access as:

 

Button:

    on_release: app.root.current = ‘0’

 

Be carful copying this code – the ‘’ are the wrong type for code.

Elliot Garbus

unread,
Mar 6, 2023, 7:03:14 PM3/6/23
to kivy-...@googlegroups.com

Here are some alternate ways to access the screenmanager.

In kv, I would use app.root to access the sm.  This is most directy, and easy to maintain.  This is how I would do it.

 

I showed a number of alternatives:

 

The ButtonsLayout1 is instanced in kv, you could create a kivy property in the python code for the class, and then set that property in the kv code where ButtonLayout1 is instanced.  In this context “root” is PhotoLayout1, “root.parent” is PhotoScreen1, a Screen has a “manager” attribute.  The manager attribute is the ScreenManager the screen is under.   

 

Another approach would be to “flatten” the screen definition, so the code under the screen was not so deeply nested.  If this were the case you could access the screen manager as “root.manager “  anywhere under that Screen.

 

Review the code below and let me know what your think.

 

 

 

from kivy.app import App
from kivy.lang import Builder
from kivy.properties import ObjectProperty, StringProperty
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.relativelayout import RelativeLayout
from kivy.core.window import Window
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.utils import platform


PS1 =
"""
<PhotoScreen1>:
    photo_preview: photo_layout.ids.preview
    PhotoLayout1:
        id:photo_layout
"""


class PhotoScreen1(Screen):
    photo_preview = ObjectProperty(
None)

   
def __init__(self, **args):
        Builder.load_string(PS1)
       
super().__init__
(**args)


PL1 =
"""

<PhotoLayout1>:
    Background1:
        id: pad_end
    Button: # Preview:
        id: preview
        # letterbox_color: .8, .3, 1, .5
    ButtonsLayout1:
        id: buttons
        screen_manager: root.parent.manager  # set the screen_manager property
"""
        text: 'Screen 0 with KV'
        on_press: app.root.current = '0'  # access the sm directly, THIS IS WHAT I WOULD DO

        height: self.width
        width: self.height
        # background_normal: 'icons/camera-flip-outline.png'
        # background_down:   'icons/camera-flip-outline.png'
    Button:
        id:flash
        height: self.width
        width: self.height
        text: 'Screen 0 using property'
        on_press: root.screen_manager.current = '0'  # use the kivy property

        # background_normal: 'icons/flash-off.png'
        # background_down:   'icons/flash-off.png'
    Button:
        id:photo
        text: 'using python'
        on_press: root.go_home()

        height: self.width
        width: self.height
        # background_normal: 'icons/camera_white.png'
        # background_down:   'icons/camera_red.png'
"""

HS0 = """
<HomeScreen0>:
    AnchorLayout:
        Button:
            size_hint: None, None
            size: dp(200), dp(48)
            on_release: root.manager.current = root.manager.next()
            text: 'Next'
"""


class HomeScreen0(Screen):
   
def __init__(self, **kwargs):
        Builder.load_string(HS0)
       
super().__init__(**kwargs)


class ButtonsLayout1
(RelativeLayout):
    screen_manager = ObjectProperty()  # this is set in kv, where the ButtonLayout1 is instanced

   
def __init__(self, **args):
        Builder.load_string(BL1)
       
super().__init__(**args)

   
def go_home(self):
       
# go to the top of the widget tree (app.root), app.root is the screen manager
       
app = App.get_running_app()
        app.root.current =
'0'
       
print(f'app.root: {app.root}')
       
# or you can access the screen manager as:
        
print(f'Screen manager using property: {self.screen_manager}')
       
# or "manually" walk up the widget tree - yuck!
       
print(f'Use the parent attributes: {self.parent.parent.parent}')
.sm


MyApp().run()

1.       Use a partial, https://docs.python.org/3/library/functools.html#functools.partial

2.       Use a lambda – A lambda is an anonymous  function.  This is effectively the same as creating a stub, but more convenient.

3.       Create a stub function, so you call a function with no parameters, and from that actually make the call you want

4.       Use kv – this is my preferred option.

Gary Kuipers

unread,
Mar 7, 2023, 12:58:49 PM3/7/23
to kivy-...@googlegroups.com
Attempting to apply the lessons of the conversation: The button no longer kicks me out of the app, but it does nothing. PLease, the kv lang vs python thing adds confusion for me. If you could just help me in python for now. When I get the hang of that I will circle back and start using kv lang.


class ButtonsLayout0(RelativeLayout):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)

        # with self.canvas.before:
        #     Color(1, 1, 1, 1)
        #     self.rect = Rectangle(size=self.size, pos=self.pos)

        self.grid = GridLayout(cols=2, size_hint=(1, 1))
        self.add_widget(self.grid)

        self.image = Image(source='logo_transparent.png', size_hint=(0.6, 1))
        self.grid.add_widget(self.image)
        self.screen_manager = ObjectProperty()  # this is set in kv, where the ButtonLayout1 is instanced (I am using python declarations)
        self.btnExit = Button(background_color=(0, 0, 0, 0), text="REGRESAR", bold=True, size_hint=(0.4, 1))  # instatiating here, not in KV

        # with self.btnExit.canvas.before:
        #     Color(1, 0.647, 0, 1)
        # self.btnExit.bind(on_press=lambda instance: setattr(self.screen_manager, 'current', '0'))
        self.btnExit.bind(on_press=lambda _: self.go_home)
        self.grid.add_widget(self.btnExit)


    def go_home(self):
        # go to the top of the widget tree (app.root), app.root is the screen manager
        app = App.get_running_app()
        app.root.current = '0'
        print(f'app.root: {app.root}')
        # or you can access the screen manager as:
        print(f'Screen manager using property: {self.screen_manager}')
        # or "manually" walk up the widget tree - yuck!
        print(f'Use the parent attributes: {self.parent.parent.parent}')



> --
> You received this message because you are subscribed to the Google Groups "Kivy users support" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to kivy-users+...@googlegroups.com.
> To view this discussion on the web visit https://groups.google.com/d/msgid/kivy-users/CAAT1hNC%3D4kbVzeEL%3DzYHZ_aewHFrOMHT8u06AXTt%2Bd2yS6z-MQ%40mail.gmail.com.
>
>  
>
> --
> You received this message because you are subscribed to the Google Groups "Kivy users support" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to kivy-users+...@googlegroups.com.
> To view this discussion on the web visit https://groups.google.com/d/msgid/kivy-users/6403db88.e90a0220.1011a.b178SMTPIN_ADDED_MISSING%40gmr-mx.google.com.
>
>
>  
>
> --
>
> Gary Kuipers, President, Casinfo
>
> cell +1 805 443 9446   other +1 702 608 6558
>
>  
>
> --
> You received this message because you are subscribed to the Google Groups "Kivy users support" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to kivy-users+...@googlegroups.com.
> To view this discussion on the web visit https://groups.google.com/d/msgid/kivy-users/CAAT1hNCDsAaGmuwk4a6Y5mVkTV1vBYR2mNQ49sJJPb8P%2B05FHg%40mail.gmail.com.
>
>  
>
> --
> You received this message because you are subscribed to the Google Groups "Kivy users support" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to kivy-users+...@googlegroups.com.
> To view this discussion on the web visit https://groups.google.com/d/msgid/kivy-users/6404cb76.050a0220.62348.d706SMTPIN_ADDED_MISSING%40gmr-mx.google.com.
>
>
>  
>
> --
>
> Gary Kuipers, President, Casinfo
>
> cell +1 805 443 9446   other +1 702 608 6558
>
>  
>
> --
> You received this message because you are subscribed to the Google Groups "Kivy users support" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to kivy-users+...@googlegroups.com.
> To view this discussion on the web visit https://groups.google.com/d/msgid/kivy-users/CAAT1hNAYr_Ny4fDNvjGGLv6PvfmZ0GCuMi-1bY5gZ%2B9uODRxkw%40mail.gmail.com.
>
>  
>
> --
> You received this message because you are subscribed to the Google Groups "Kivy users support" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to kivy-users+...@googlegroups.com.

Elliot Garbus

unread,
Mar 7, 2023, 5:11:37 PM3/7/23
to kivy-...@googlegroups.com
self.btnExit.bind(on_press=self.go_home)
And add an extra argument…

def go_home(self, _)

Sent from my iPhone

On Mar 7, 2023, at 10:58 AM, Gary Kuipers <gary.k...@casinfosystems.com> wrote:



Gary Kuipers

unread,
Mar 7, 2023, 6:19:25 PM3/7/23
to kivy-...@googlegroups.com
That worked, thank you.

Interestingly, when you said "add an extra argument" and Isaw the underscore my thought was "pick a name" soI put in "whatever" instead of the underscore.

In that case it failed, whenI used the underscore it worked.

This is what I found about the underscore:

_ is used to indicate that the input variable is a throwaway variable/parameter and thus might be required or expected, but will not be used in the code following it.

I was surprised that giving it a name would cause it to fail. But anyway, now I have what I hope is a reproducible recipe. Thanks again.

Gary Kuipers, President, Casinfo

Elliot Garbus

unread,
Mar 7, 2023, 7:06:02 PM3/7/23
to kivy-...@googlegroups.com
Very strange. Any valid identifier should work. Using the _ is nice, it makes it clear the car in not used. If you are using an editor that does some checking (pyCharm for example). It tells the editor not to warn you of an unused variable. 

Sent from my iPhone

On Mar 7, 2023, at 4:19 PM, Gary Kuipers <gary.k...@casinfosystems.com> wrote:


Reply all
Reply to author
Forward
Message has been deleted
0 new messages