4

So, I'm working on a simple kivy app and I have three buttons (MDRaisedButton's) which each open the same KivyMD Menu (MDDropdownMenu) on release.

  1. Two of these buttons are in different ScreenManager screens.

  2. The other button is in a GridLayout in the Screen outside the ScreenManager.

When I open the menu using the buttons inside of the ScreenManager, the menu appears at the button that is outside of the ScreenManager, no matter which button I press.


So how can I change the caller, or the position of the menu when it appears to be that of the buttons in my ScreenManager screens?


Me clicking on the button inside the ScreenManager screen: enter image description here

The menu appears on the wrong button: enter image description here


Code:

from kivymd.app import MDApp
from kivymd.uix.menu import MDDropdownMenu
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager
from kivy.uix.screenmanager import Screen

Screens = ["First", "Second"]

KV = '''
Screen:
    screen_man:screen_man
    button_outside:button_outside
    GridLayout:
        rows: 2
        cols: 1
        MDRaisedButton:
            id: button_outside
            text: "Outside Screen Manager"
            pos_hint: {"center_x": .1, "center_y": .9}
            on_release: app.threeD_menu.open()

        ScreenManager:
            id:screen_man
            FirstScreen:
            SecondScreen:


<FirstScreen>:
    name:"First"
    screen_man1:screen_man1

    MDRaisedButton:
        id: screen_man1
        text: "Inside Screen Manager1"
        pos_hint: {"center_x": 0.5, "center_y": 0.5}
        on_release: app.threeD_menu.open()
    
    MDLabel:
        text: "Screen Manager1"
        font_size: "40dp"
        pos_hint: {"center_x": .9, "center_y": 0.9}

<SecondScreen>:
    name:"Second"
    screen_man2:screen_man2
    MDRaisedButton:
        id: screen_man2
        text: "Inside Screen Manager2"
        pos_hint: {"center_x": 0.5, "center_y": .5}
        on_release: app.threeD_menu.open()
    
    MDLabel:
        text: "Screen Manager2"
        font_size: "40dp"
        pos_hint: {"center_x": .9, "center_y": 0.9}

'''


class FirstScreen(Screen):
    pass


class SecondScreen(Screen):
    pass


class WindowManager(ScreenManager):
    pass


class ExampleApp(MDApp):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.title = 'Example'
        self.screen = Builder.load_string(KV)
        threeD_items = [
            {
                "text": f"{i}",
                "viewclass": "OneLineListItem",
                "on_release": lambda x=f"{i}": self.threeD_refresh(x),
            } for i in Screens
        ]

        self.threeD_menu = MDDropdownMenu(
            caller=self.screen.button_outside,
            items=threeD_items,
            width_mult=4,
        )

    def build(self):
        sm = ScreenManager()
        sm.add_widget(self.screen)
        return sm

    def threeD_refresh(self, operation):
        self.threeD_menu.dismiss()
        self.screen.screen_man.current = operation


ExampleApp().run()

Can someone please help with this? I've searched the docs and Google and found nothing so far.

Raed Ali
  • 549
  • 1
  • 6
  • 22

1 Answers1

1

Look at the following line in your code for the MDRaisedButton on_release event handler.

on_release: app.threeD_menu.open()

The menu has no idea who opened it. The key here is to pass a caller to whatever function opens the menu, so the MDDropdownMenu has that context of who called it.

Define a new function called launch_menu with the following code:

def launch_menu(self, my_caller):
    self.threeD_menu.caller=my_caller
    self.threeD_menu.open()

Then, change the event handlers to call this function, rather than just the menu open function:

MDRaisedButton:
    id: button_outside
    text: "Outside Screen Manager"
    pos_hint: {"center_x": .1, "center_y": .9}
    on_release: app.launch_menu(self)

Now, when your eventhandler is called, it passes to the menu object who called it, and the menu object can launch from there.

enter image description here

Full code:

from kivymd.app import MDApp
from kivymd.uix.menu import MDDropdownMenu
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager
from kivy.uix.screenmanager import Screen

Screens = ["First", "Second"]

KV = '''
Screen:
    screen_man:screen_man
    button_outside:button_outside
    GridLayout:
        rows: 2
        cols: 1
        MDRaisedButton:
            id: button_outside
            text: "Outside Screen Manager"
            pos_hint: {"center_x": .1, "center_y": .9}
            on_release: app.launch_menu(self)

        ScreenManager:
            id:screen_man
            FirstScreen:
            SecondScreen:


<FirstScreen>:
    name:"First"
    id:"first_screen"
    screen_man1:screen_man1

    MDRaisedButton:
        id: screen_man1
        text: "Inside Screen Manager1"
        pos_hint: {"center_x": 0.5, "center_y": 0.5}
        on_release: app.launch_menu(self)
    
    MDLabel:
        text: "Screen Manager1"
        font_size: "40dp"
        pos_hint: {"center_x": .9, "center_y": 0.9}

<SecondScreen>:
    name:"Second"
    screen_man2:screen_man2
    MDRaisedButton:
        id: screen_man2
        text: "Inside Screen Manager2"
        pos_hint: {"center_x": 0.5, "center_y": .5}
        on_release: app.launch_menu(self)
    
    MDLabel:
        text: "Screen Manager2"
        font_size: "40dp"
        pos_hint: {"center_x": .9, "center_y": 0.9}

'''


class FirstScreen(Screen):
    pass


class SecondScreen(Screen):
    pass


class WindowManager(ScreenManager):
    pass


class ExampleApp(MDApp):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.title = 'Example'
        self.screen = Builder.load_string(KV)
        threeD_items = [
            {
                "text": f"{i}",
                "viewclass": "OneLineListItem",
                "on_release": lambda x=f"{i}": self.threeD_refresh(x),
            } for i in Screens
        ]

        self.threeD_menu = MDDropdownMenu(
            caller=self.screen.button_outside,
            items=threeD_items,
            width_mult=4,
        )
        

    def build(self):
        sm = ScreenManager()
        sm.add_widget(self.screen)
        return sm

    def threeD_refresh(self, operation):
        self.threeD_menu.dismiss()
        self.screen.screen_man.current = operation
    
    def launch_menu(self, my_caller):
        self.threeD_menu.caller=my_caller
        self.threeD_menu.open()
        


ExampleApp().run()

Andrew James
  • 372
  • 1
  • 8