在 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
,因此您可以使用 ScreenManager
的 get_screen()
方法访问包含 route_id
Label
。尝试替换:
app.root.ids.route_id.text = current_waypoint
与:
app.root.get_screen('route').ids.route_id.text = current_waypoint
我是 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
,因此您可以使用 ScreenManager
的 get_screen()
方法访问包含 route_id
Label
。尝试替换:
app.root.ids.route_id.text = current_waypoint
与:
app.root.get_screen('route').ids.route_id.text = current_waypoint