Search code examples
pythonkivykivy-languagekivymd

How to detect if a button in the ModalView was pressed?


I've been having issues with ModalView that opens after one of the button is pressed within the dropdown menu.

I need to find a way to detect somehow whether the button with the id "yes" that is within that MyPopup class in my Kivy file was pressed or not, because I need it to trigger a function in the screen class. For the purpose of this question I just named it function_to_trigger()

I didn't include the full code that I have within that screen class as first of all, it would be too long (my question is long enough without I think) and secondly, the only issue that I have is with this MyPopup class that is within my Kivy file.

ACTUAL RESULT:

  • The dropdown menu opens as expected.
  • Dropdown disappears after clicking on the "RETURN TO THE MAIN MENU" button just as expected.
  • MyPopup ModalView that appears after clicking on "RETURN TO THE MAIN MENU" button works as expected.
  • "Yes" button within the MyPopup ModalView redirects the user to the main_menu screen (that I didn't include here) as expected.
  • MyPopup ModalView disappears when the user is redirected to the main_menu screen as expected.
  • function_to_trigger() is NOT triggered when the "YES" button in the ModalView is pressed. It doesn't work as expected.

EXPECTED RESULT:

  • function_to_trigger() is triggered ONLY IF the "YES" button in the ModalView is pressed or if the user is redirected to the main_menu screen.

.py file:

from kivy.uix.screenmanager import Screen
from kivymd.uix.menu.menu import MDDropdownMenu
from kivy.factory import Factory
from kivy.uix.modalview import ModalView
from kivymd.uix.behaviors.hover_behavior import HoverBehavior
from kivy.uix.button import Button
from kivy.properties import ListProperty


class SomeScreen(Screen):

    def function_to_trigger(self):
        print('I was triggered by yes button in the ModalView!')
    

    def dropdown(self):
        menu_list = [
            {
                "viewclass": "OneLineListItem",
                "text" : "SOUND ON",
                "theme_text_color": "Custom",
                "on_release": lambda x="SOUND ON": self.sound_off(menu_list[1]["text"]),
                "text_color": [1, 1, 1, 1],
                "text_style": "BUTTON",
                "font_style": "H6"
            },
            {
                "viewclass": "OneLineListItem",
                "text": "RETURN TO MAIN MENU",
                "on_release": lambda x="RETURN TO MAIN MENU": [Factory.get('MyPopup')().open(), menu.dismiss()],
                "theme_text_color": "Custom",
                "text_color": [1, 1, 1, 1],
                "text_style": "BUTTON",
                "font_style": "H6"
            }
        ]

        menu = MDDropdownMenu(
            caller=self.ids.burger_menu,
            items=menu_list,
            width_mult=4.2,
            background_color=[0,0,0,1],
            radius=10

        )

        menu.open()

    ## By the way the function below doesn't work and I also need to find a way how to fix this, but this is a topic for another question I think.

    def sound_off(self, text_item):
        if text_item == "SOUND ON":
            text_item = "SOUND OFF"
            return text_item
        else:
            text_item = "SOUND ON"
            return text_item


class HamburgerButton(Button):
    pass


class ExitButton(Button, HoverBehavior):
    background = ListProperty((120/255, 120/255, 120/255, 1))
    color = ListProperty((1, 1, 1, 1))

    def on_enter(self):
        self.background = (1, 222/255, 89/255, 1)
        self.color = (0, 0, 0, 1)

    def on_leave(self):
        self.background = (120/255, 120/255, 120/255, 1)
        self.color = (1, 1, 1, 1)

.kv file:

<SomeScreen>

    name: "some_screen"


        FloatLayout:
            orientation: 'horizontal'
            size: 1280, 720
    


            HamburgerButton:
                id: burger_menu
                on_release: root.dropdown()



<HamburgerButton>
    size : 95, 95
    size_hint: None, None
    pos_hint: {'center_x': .205, 'center_y': .19}
    background_normal: 'burger_menu.png'


<ExitButton>
    background_color: 0, 0, 0, 0
    background_normal: ''
    font_name: 'Roboto'
    font_size: 18
    bold: True
    canvas.before:
        Color:
            rgba: self.background
        RoundedRectangle:
            size: self.size
            pos: self.pos
            radius: [20]

<MyPopup@ModalView>
    size_hint: None, None
    background_color: 0, 0, 0, 1
    size: 450, 250
    auto_dismiss: False
    canvas.before:
        Color:
            rgba: root.background_color
        RoundedRectangle:
            pos: self.pos
            size: self.size
            radius: [20,]
    BoxLayout:
        orientation: "vertical"
        Label:
            text: "ARE YOU SURE YOU WANT TO EXIT?"
            font_name: 'Roboto'
            font_size: 20
            bold: True
        BoxLayout:
            size_hint_y: 0.3
            ExitButton:
                id: yes
                text: "YES"
                on_release:
                    app.root.current = "main_menu"
                    root.dismiss()

            ExitButton:
                text: "NO"
                bold: True
                on_release: root.dismiss()

Burger Menu Icon:

Burger Menu Icon

What I tried to resolve this issue:

1. Creating a MyPopup class within the file with SomeScreen and referring it in the Kivy file under on_release parameter of "yes" button. It didn't work.

.py file:

from kivy.uix.screenmanager import Screen
from kivymd.uix.menu.menu import MDDropdownMenu
from kivy.factory import Factory
from kivy.uix.modalview import ModalView
from kivymd.uix.behaviors.hover_behavior import HoverBehavior
from kivy.uix.button import Button
from kivy.properties import ListProperty


class SomeScreen(Screen):

    def function_to_trigger(self):
        print('I was triggered by yes button in the ModalView!')
    

    def dropdown(self):
        menu_list = [
            {
                "viewclass": "OneLineListItem",
                "text" : "SOUND ON",
                "theme_text_color": "Custom",
                "on_release": lambda x="SOUND ON": self.sound_off(menu_list[1]["text"]),
                "text_color": [1, 1, 1, 1],
                "text_style": "BUTTON",
                "font_style": "H6"
            },
            {
                "viewclass": "OneLineListItem",
                "text": "RETURN TO MAIN MENU",
                "on_release": lambda x="RETURN TO MAIN MENU": [Factory.get('MyPopup')().open(), menu.dismiss()],
                "theme_text_color": "Custom",
                "text_color": [1, 1, 1, 1],
                "text_style": "BUTTON",
                "font_style": "H6"
            }
        ]

        menu = MDDropdownMenu(
            caller=self.ids.burger_menu,
            items=menu_list,
            width_mult=4.2,
            background_color=[0,0,0,1],
            radius=10

        )

        menu.open()

    # By the way the function below doesn't work and I also need to find a way how to fix this, but this is a topic for another question I think.

    def sound_off(self, text_item):
        if text_item == "SOUND ON":
            text_item = "SOUND OFF"
            return text_item
        else:
            text_item = "SOUND ON"
            return text_item



class HamburgerButton(Button):
    pass


class ExitButton(Button, HoverBehavior):
    background = ListProperty((120/255, 120/255, 120/255, 1))
    color = ListProperty((1, 1, 1, 1))

    def on_enter(self):
        self.background = (1, 222/255, 89/255, 1)
        self.color = (0, 0, 0, 1)

    def on_leave(self):
        self.background = (120/255, 120/255, 120/255, 1)
        self.color = (1, 1, 1, 1)


class MyPopup(ModalView):

    def trigger_function(self):

        screen = SomeScreen()
        screen.function_to_trigger()


    # I also tried the following but it didn't work either. 
    def trigger_function(self):
        if self.ids.yes.on_release is True:
            SomeScreen.function_to_trigger(SomeScreen())

.kv file:

<MyPopup@ModalView>
    size_hint: None, None
    background_color: 0, 0, 0, 1
    size: 450, 250
    auto_dismiss: False
    canvas.before:
        Color:
            rgba: root.background_color
        RoundedRectangle:
            pos: self.pos
            size: self.size
            radius: [20,]
    BoxLayout:
        orientation: "vertical"
        Label:
            text: "ARE YOU SURE YOU WANT TO EXIT?"
            font_name: 'Roboto'
            font_size: 20
            bold: True
        BoxLayout:
            size_hint_y: 0.3
            ExitButton:
                id: yes
                text: "YES"
                on_release:
                    app.root.current = "main_menu"
                    root.trigger_function()
                    root.dismiss()

            ExitButton:
                text: "NO"
                bold: True
                on_release: root.dismiss()

2. Referencing MyPopup class with the help of Kivy Factory within the dropdown function. It didn't work as expected as the function was triggered as soon as the dropdown was opened when it should have been triggered ONLY when the YES button is pressed and the user is redirected to the main_menu screen. It didn't even take into consideration the fact that the ModalView wasn't opened at all.

.py file:

from kivy.uix.screenmanager import Screen
from kivymd.uix.menu.menu import MDDropdownMenu
from kivy.factory import Factory
from kivy.uix.modalview import ModalView
from kivymd.uix.behaviors.hover_behavior import HoverBehavior
from kivy.uix.button import Button
from kivy.properties import ListProperty


class SomeScreen(Screen):

    def function_to_trigger(self):
        print('I was triggered by yes button in the ModalView!')
    

    def dropdown(self):
        menu_list = [
            {
                "viewclass": "OneLineListItem",
                "text" : "SOUND ON",
                "theme_text_color": "Custom",
                "on_release": lambda x="SOUND ON": self.sound_off(menu_list[1]["text"]),
                "text_color": [1, 1, 1, 1],
                "text_style": "BUTTON",
                "font_style": "H6"
            },
            {
                "viewclass": "OneLineListItem",
                "text": "RETURN TO MAIN MENU",
                "on_release": lambda x="RETURN TO MAIN MENU": [Factory.get('MyPopup')().open(), menu.dismiss()],
                "theme_text_color": "Custom",
                "text_color": [1, 1, 1, 1],
                "text_style": "BUTTON",
                "font_style": "H6"
            }
        ]

        menu = MDDropdownMenu(
            caller=self.ids.burger_menu,
            items=menu_list,
            width_mult=4.2,
            background_color=[0,0,0,1],
            radius=10

        )

        menu.open()

        if Factory.get('MyPopup')().ids.yes.on_release is True:
            self.function_to_trigger()

    # By the way the function below doesn't work and I also need to find a way how to fix this, but this is a topic for another question I think.

    def sound_off(self, text_item):
        if text_item == "SOUND ON":
            text_item = "SOUND OFF"
            return text_item
        else:
            text_item = "SOUND ON"
            return text_item



class HamburgerButton(Button):
    pass


class ExitButton(Button, HoverBehavior):
    background = ListProperty((120/255, 120/255, 120/255, 1))
    color = ListProperty((1, 1, 1, 1))

    def on_enter(self):
        self.background = (1, 222/255, 89/255, 1)
        self.color = (0, 0, 0, 1)

    def on_leave(self):
        self.background = (120/255, 120/255, 120/255, 1)
        self.color = (1, 1, 1, 1)

3. Creating a function within the SomeScreen class that would be triggered if App.get_running_app().root_current = 'main_menu' but it didn't work and I knew it wouldn't work because if the user had already been redirected to the main_menu screen, then the SomeScreen class wouldn't have been aware of this. Anyway, it didn't work.

4. Creating a function within a ScreenManager similar to the one in point number 3. Basically, what I attempted to do was to create a function that would trigger function_to_trigger() IF the App.get_running_app().root.current == 'main_screen' but it didn't work.

5. Creating a function like the one in the point number 1, BUT within SomeScreen class. It didn't work either.

py.file

from kivy.uix.screenmanager import Screen
from kivymd.uix.menu.menu import MDDropdownMenu
from kivy.factory import Factory
from kivy.uix.modalview import ModalView
from kivymd.uix.behaviors.hover_behavior import HoverBehavior
from kivy.uix.button import Button
from kivy.properties import ListProperty


class SomeScreen(Screen):

    def function_to_trigger(self):
        print('I was triggered by yes button in the ModalView!')

    def trigger_function(self):

        if Factory.get('MyPopup')().ids.yes.on_release is True:
            self.function_to_trigger()
    

    def dropdown(self):
        menu_list = [
            {
                "viewclass": "OneLineListItem",
                "text" : "SOUND ON",
                "theme_text_color": "Custom",
                "on_release": lambda x="SOUND ON": self.sound_off(menu_list[1]["text"]),
                "text_color": [1, 1, 1, 1],
                "text_style": "BUTTON",
                "font_style": "H6"
            },
            {
                "viewclass": "OneLineListItem",
                "text": "RETURN TO MAIN MENU",
                "on_release": lambda x="RETURN TO MAIN MENU": [Factory.get('MyPopup')().open(), menu.dismiss()],
                "theme_text_color": "Custom",
                "text_color": [1, 1, 1, 1],
                "text_style": "BUTTON",
                "font_style": "H6"
            }
        ]

        menu = MDDropdownMenu(
            caller=self.ids.burger_menu,
            items=menu_list,
            width_mult=4.2,
            background_color=[0,0,0,1],
            radius=10

        )

        menu.open()

        if Factory.get('MyPopup')().ids.yes.on_release is True:
            self.function_to_trigger()

    # By the way the function below doesn't work and I also need to find a way how to fix this, but this is a topic for another question I think.

    def sound_off(self, text_item):
        if text_item == "SOUND ON":
            text_item = "SOUND OFF"
            return text_item
        else:
            text_item = "SOUND ON"
            return text_item



class HamburgerButton(Button):
    pass


class ExitButton(Button, HoverBehavior):
    background = ListProperty((120/255, 120/255, 120/255, 1))
    color = ListProperty((1, 1, 1, 1))

    def on_enter(self):
        self.background = (1, 222/255, 89/255, 1)
        self.color = (0, 0, 0, 1)

    def on_leave(self):
        self.background = (120/255, 120/255, 120/255, 1)
        self.color = (1, 1, 1, 1)

EDIT

MINIMAL REPRODUCIBLE EXAMPLE:

main.py

from kivymd.app import MDApp
from kivy.lang.builder import Builder
from windowmanager import WindowManager


class MinimalApp(MDApp):
    def build(self):
        Builder.load_file('minimalapp.kv')

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

windowmanager.py

from kivy.uix.screenmanager import ScreenManager
from main_menu import MainMenu
from second_screen import SecondScreen

class WindowManager(ScreenManager):
    pass

main_menu.py

from kivy.uix.screenmanager import Screen
from kivymd.uix.behaviors.hover_behavior import HoverBehavior
from kivy.uix.button import Button
from kivy.properties import ListProperty


class MainMenu(Screen):
    pass


class ButtonMenu(Button, HoverBehavior):
    background = ListProperty((0, 0, 0, 1))
    color = ListProperty((1, 1, 1, 1))

    def on_enter(self):
        self.background = (1, 222/255, 89/255, 1)
        self.color = (0, 0, 0, 1)

    def on_leave(self):
        self.background = (0, 0, 0, 1)
        self.color = (1, 1, 1, 1)

main_menu.kv

<MainMenu>:
    name: 'main_menu'

    FloatLayout:
        orientation: 'horizontal'
        size: 1280, 720

        ButtonMenu:
            text: 'GO TO SOME SCREEN'
            pos_hint: {'center_x': .5, 'center_y': .42}
            on_release:
                app.root.current = "second_screen"
                root.manager.transition.direction = "left"


<ButtonMenu>
    background_color: 0, 0, 0, 0
    background_normal: ''
    size_hint: 0.5, 0.11
    font_size: 32
    font_name: 'Roboto'
    bold: True
    canvas.before:
        Color:
            rgba: self.background
        RoundedRectangle:
            size: self.size
            pos: self.pos
            radius: [28]
        Color:
            rgba: (0, 0, 0, 1)  # separate color for line border
        Line:
            rounded_rectangle: (self.pos[0], self.pos[1], self.size[0], self.size[1], 28)
            width: 2

minimalapp.kv

#: include main_menu.kv
#: include second_screen.kv

WindowManager:
    MainMenu:
    SecondScreen:

second_screen.py

from kivy.uix.screenmanager import Screen
from kivymd.uix.menu.menu import MDDropdownMenu
from kivy.factory import Factory
from kivy.uix.modalview import ModalView
from kivymd.uix.behaviors.hover_behavior import HoverBehavior
from kivy.uix.button import Button
from kivy.properties import ListProperty


class SecondScreen(Screen):

    def function_to_trigger(self):
        print('I was triggered by yes button in the ModalView!')

    def dropdown(self):
        menu_list = [
            {
                "viewclass": "OneLineListItem",
                "text": "SOUND ON",
                "theme_text_color": "Custom",
                "on_release": lambda x="SOUND ON": self.sound_off(menu_list[1]["text"]),
                "text_color": [1, 1, 1, 1],
                "text_style": "BUTTON",
                "font_style": "H6"
            },
            {
                "viewclass": "OneLineListItem",
                "text": "RETURN TO MAIN MENU",
                "on_release": lambda x="RETURN TO MAIN MENU": [Factory.get('MyPopup')().open(), menu.dismiss()],
                "theme_text_color": "Custom",
                "text_color": [1, 1, 1, 1],
                "text_style": "BUTTON",
                "font_style": "H6"
            }
        ]

        menu = MDDropdownMenu(
            caller=self.ids.burger_menu,
            items=menu_list,
            width_mult=4.2,
            background_color=[0, 0, 0, 1],
            radius=10

        )

        menu.open()

    ## By the way the function below doesn't work and I also need to find a way how to fix this, but this is a topic for another question I think.

    def sound_off(self, text_item):
        if text_item == "SOUND ON":
            text_item = "SOUND OFF"
            return text_item
        else:
            text_item = "SOUND ON"
            return text_item

class HamburgerButton(Button):
    pass


class ExitButton(Button, HoverBehavior):
    background = ListProperty((120/255, 120/255, 120/255, 1))
    color = ListProperty((1, 1, 1, 1))

    def on_enter(self):
        self.background = (1, 222/255, 89/255, 1)
        self.color = (0, 0, 0, 1)

    def on_leave(self):
        self.background = (120/255, 120/255, 120/255, 1)
        self.color = (1, 1, 1, 1)

second_screen.kv

<SecondScreen>
    name: 'second_screen'

    FloatLayout:
        orientation: 'horizontal'
        size: 1280, 720

        HamburgerButton:
            id: burger_menu
            on_release: root.dropdown()

<HamburgerButton>
    size : 95, 95
    size_hint: None, None
    pos_hint: {'center_x': .205, 'center_y': .19}
    background_normal: 'burger_menu.png'


<ExitButton>
    background_color: 0, 0, 0, 0
    background_normal: ''
    font_name: 'Roboto'
    font_size: 18
    bold: True
    canvas.before:
        Color:
            rgba: self.background
        RoundedRectangle:
            size: self.size
            pos: self.pos
            radius: [20]

<MyPopup@ModalView>
    size_hint: None, None
    background_color: 0, 0, 0, 1
    size: 450, 250
    auto_dismiss: False
    canvas.before:
        Color:
            rgba: root.background_color
        RoundedRectangle:
            pos: self.pos
            size: self.size
            radius: [20,]
    BoxLayout:
        orientation: "vertical"
        Label:
            text: "ARE YOU SURE YOU WANT TO EXIT?"
            font_name: 'Roboto'
            font_size: 20
            bold: True
        BoxLayout:
            size_hint_y: 0.3
            ExitButton:
                id: yes
                text: "YES"
                on_release:
                    app.root.current = "main_menu"
                    root.dismiss()

            ExitButton:
                text: "NO"
                bold: True
                on_release: root.dismiss()

Solution

  • Try adding:

    app.root.get_screen('second_screen').function_to_trigger()
    

    to the on_release: of the YES button.