如何将 KivyMD 菜单调用者更改为 ScreenManager 屏幕中的小部件

How to change a KivyMD Menu caller to a Widget in a ScreenManager Screen

所以,我正在开发一个简单的 kivy 应用程序,并且在发布时我有三个按钮 (MDRaisedButton's) which each open the same KivyMD Menu (MDDropdownMenu)。

  1. 其中两个按钮位于不同的 ScreenManager 屏幕中。

  2. 另一个按钮是在GridLayout里的Screen外面 ScreenManager.

当我使用 ScreenManager 内部的按钮打开菜单时,菜单出现在 ScreenManager 外部的按钮上,无论我按哪个按钮.


那么,如果我的 ScreenManager 屏幕上显示的是按钮的位置,我该如何更改来电者或菜单的位置?


我点击 ScreenManager 屏幕内的按钮:

菜单出现在错误的按钮上:


代码:

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()

有人可以帮忙吗?我已经搜索了 docs 和 Google,但到目前为止一无所获。

查看 MDRaisedButton on_release 事件处理程序代码中的以下行。

on_release: app.threeD_menu.open()

菜单不知道是谁打开的。这里的关键是将 caller 传递给打开菜单的任何函数,因此 MDDropdownMenu 具有调用者的上下文。

使用以下代码定义一个名为 launch_menu 的新函数:

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

然后,更改事件处理程序以调用此函数,而不仅仅是菜单打开函数:

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

现在,当您的事件处理程序被调用时,它会传递给调用它的菜单对象,菜单对象可以从那里启动。

完整代码:

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()