在 ScreenManager 中 类 之间传递 kivy 属性时出错

Error passing kivy attribute between classes in ScreenManager

我是 python 和 kivy 的新手,正在学习如何在 kivy 对象和 python 之间传递信息。我已经掌握了基本概念,但这个问题让我很困惑。

我正在编写一个应用程序来管理 GPS waypoints,这些 class 已分组。目的是让用户从 Spinner 中 select 一个路点组,它在 RecycleView 中填充 waypoints 的列表。然后,用户 select 从 RecycleView 列表中选择一个路标。 selected 航路点被传递以进行进一步处理。失败的是最后一步(传球)。

我在自己的开发程序中开发了这个航点管理功能,并且按预期工作。当我将开发代码添加到 ScreenManager 时,问题就来了。这是一个更大项目的一小部分,因此我去除了下面代码中的所有干扰并重新组织以使其更易于调试。

该应用程序有多个屏幕由 ScreenManager 管理。航点 selection 屏幕显示用于选择航点组的微调器和用于选择航点的 RecycleView(称为 RV())。航路点选择在 class RVItem() 中处理。 Spinner、RecycleView 和 RVItem() 工作正常。当我尝试将所选航路点传递回 kivy 代码中的标签时,问题(在 ScreenManager 版本中)发生。 RVItem.on_release() 事件处理程序成功捕获了 selected 航路点,但我不知道如何将 selection 发送回屏幕上的标签。我的问题出在 RVItem.on_release() 代码中。 Label 在 .kv 文件中的 id 是 route_id。我在 RVItem.on_release() 代码中留下了一些尝试将航路点发送到 route_id.text 的列表,但我找不到任何有效的方法。我错过了什么?

我最后尝试使用 class Route() 中的 route_id = ObjectProperty(None) 访问标签。我也无法让它工作,但它不会影响程序的运行或崩溃方式,所以我在代码中保留了 属性 以防它有帮助。

重现问题:将代码复制到文件 main.py 和 ScreenManager.kv 中。启动程序,当主菜单打开时,单击“路线”按钮。单击选择一个组微调器,select 从下拉列表中选择一个组,然后从 RecycleView 列表中选择一个路径点。该程序将在 RVItem.on_release() 代码结束时崩溃。错误将是 KeyError: 'route_id'

     AttributeError: 'super' object has no attribute '__getattr__'

我花了几个小时试图自己解决这个问题。如果您可以提出解决方案,也请告诉我我应该如何自己调试它。

我是 运行 Python 3.8 和 Kivy 2.0。

# main.py
# BoatInstruments.222
# Stripped down version to demonstrate the problem passing the 
# RecycleView's response back to the kivy Label
  
from kivy.app import App
from kivy.uix.recycleview import RecycleView
from kivy.factory import Factory
from kivy.uix.screenmanager import ScreenManager, Screen, FadeTransition
from kivy.lang import Builder
from kivy.properties import ObjectProperty

Builder.load_file('ScreenManager.kv')


class ScreenManagement(ScreenManager):
    pass


class MainMenu(Screen):
    pass


class RVItem(Factory.Button):
    # Returns the waypoint selected by RecycleView()
    def get_data_index(self):
        return self.parent.get_view_index_at(self.center)

    @property
    def rv(self):
        return self.parent.recycleview

    def on_release(self):
        app = App.get_running_app()
        data_index = self.get_data_index()
        current_waypoint = app.waypoints[data_index]
        print("\r\ncurrent_waypoint = ", current_waypoint, "\r\n")                              # Successful to this point


        # Write the route (current_waypoint for debugging) to kivy label Route.route_id              # !!! FAIL !!!
        # These are some of the things that I tried.
        print("app.root is ", app.root)
        app.root.ids.route_id.text = current_waypoint                     # This works in dev code (without ScreenManager) because there class WMApp(App) returns the root widget Route()
        # root.ids.route_id.text = current_waypoint
        # root.route_id.text = current_waypoint
        # self.ids.route_id.text = current_waypoint
        # self.parent.ids.route_id.text = current_waypoint
        # scrRoute = app.root.ids.route_id.text
        # root.ids.screen_manager.get_screen('route')
        # scrRoute.ids.route_id.text = current_waypoint
        # self.route_id.text = current_waypoint




class RV(RecycleView):
    def __init__(self, **kwargs):
        super(RV, self).__init__()
        self.data = []                                                          # Initialize the list of Groups


class Route(Screen):
    # Presents a list of waypoint groups in a spinner. After choosing, populates rv_id.data with that group's waypoints.

    route_id = ObjectProperty(None)


    def spinner_clicked(self, value):                                           # value is the selected waypoint group
        # Get the Group's list of waypoints and send them to RV
        app = App.get_running_app()
        self.ids.rv_id.data = [{'text': item} for item in app.waypoints]

    def new_route(self):
        print("Attempting Route.new_route()")
        app = App.get_running_app()
        app.wptroute = []
        app.root.ids.route_id.text = ""                                         # !!! FAIL !!!


    def done_route(self):
        print("Attempting Route.done_route()")



class BoatInstrumentsApp(App):

    groups = ['CYC', 'MHYC', 'GRYC', 'CLYC', 'Cleveland']        # accessed in kivy via app.list_of_groups
    waypoints = ['GRSC A', 'GRSC B', 'GRSC C', 'GRSC D', 'GRSC E', 'GRSC F']
    wptroute = []

    def __init__(self, **kwargs):
        super().__init__(**kwargs)


    def build(self):
        return ScreenManagement()


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




# ScreenManager.kv

<ScreenManagement>:
    id: screen_manager
    MainMenu:
        id: mainmenu                                 
        name: 'mainmenu'                           
        manager: 'screen_manager'
    Route:
        id: route
        name: 'route'
        manager: 'screen_manager'


# ##################################################################
<MainMenu>:
    BoxLayout:
        orientation: 'vertical'
        padding: 120
        spacing: 30

        Label:
            text: "Main Menu"
            font_size: 60

        Button:
            text: "Route"
            font_size: 40
            on_release: app.root.current = 'route'
    
# ##################################################################
<Route>:
    route_id: route_id                                      # I added this property late. It may or may not be useful
    BoxLayout:
        orientation: 'horizontal'
        padding: 5
        spacing: 5

        BoxLayout:                                                             # Left column: Groups and Waypoints
            orientation: 'vertical'
            Spinner:                                                           # Spinner: Waypoint Group
                id: spinner_id
                size_hint: (1, 0.15)
                text: "Choose a group"
                font_size: '40dp'
                values: app.groups
                on_text: root.spinner_clicked(spinner_id.text)

            Label:
                size_hint: (1, 0.04)

            RV:                                                                         # RecycleView: Waypoints
                id: rv_id
                viewclass: 'RVItem'
                RecycleBoxLayout:
                    default_size: None, 30                                              # Set the RV child box height
                    default_size_hint: 1, None
                    size_hint_y: None
                    height: self.minimum_height
                    orientation: 'vertical'
                    spacing: 5



        BoxLayout:                                                                      # Right column: Label, list of waypoints, two buttons
            id: box_id
            orientation: 'vertical'

            Label:
                text: "Route"
                size_hint: (1, 0.15)
                font_size: '40dp'


            # ########### HERE ###########################################
            #Display the route (or current_waypoint for debugging)
            Label:                                                  # This label will contain the waypoints of the route, line by line
                id: route_id
                text: "Route goes here"



            RelativeLayout:
                size_hint: 1, 0.24
                Button:                                                                 # Button: New Route
                    id: new_route_id
                    text: "New Route"
                    font_size: '40dp'
                    size_hint: 0.8, 0.48
                    pos_hint: {"x":0.1, "top":1}
                    on_release: root.new_route()

                Button:                                                                 # Button: Done
                    id: done_route_id
                    text: "Done"
                    font_size: '40dp'
                    size_hint: 0.8, 0.48
                    pos_hint: {"x":0.1, "bottom":1}
                    # on_release: root.done_route()
                    on_release: app.root.current = 'mainmenu'

由于您使用的是 ScreenManager,因此您可以使用 ScreenManagerget_screen() 方法访问包含 route_id Label。尝试替换:

app.root.ids.route_id.text = current_waypoint

与:

app.root.get_screen('route').ids.route_id.text = current_waypoint