我是 python 和 kivy 的新手,正在学习如何在 kivy 对象和 python 之间传递信息。我已经掌握了基本概念,但这个问题让我很难过。
我正在编写一个应用程序来管理分类为组的 GPS 航点。目的是让用户从 Spinner 中选择一个航路点组,该航路点组填充 RecycleView 中的航路点列表。然后用户从 RecycleView 列表中选择一个航路点。选择的航路点被传递以进行进一步处理。失败的是最后一步(通过)。
我在自己的开发程序中开发了这个航点管理功能,它按预期工作。当我将开发代码添加到 ScreenManager 时,问题就来了。这是一个更大项目的一小部分,所以我已经去除了下面代码中的所有干扰,并重新组织以使其更易于调试。
该应用程序有多个使用 ScreenManager 管理的屏幕。航点选择屏幕显示一个用于选择航点组的 Spinner 和一个用于选择航点的 RecycleView(称为 RV())。航点选择在类 RVItem() 中处理。Spinner、RecycleView 和 RVItem() 工作正常。当我尝试将所选航点传递回 kivy 代码中的标签时,会出现问题(在 ScreenManager 版本中)。RVItem.on_release() 事件处理程序成功捕获所选航点,但我不知道如何将选择发送回屏幕上的标签。我的问题出在 RVItem.on_release() 代码中。.kv 文件中 Label 的 id 是 route_id。我在 RVItem.on_release() 代码中留下了一些尝试将航点发送到 route_id.text 的列表,但我找不到任何可行的方法。我错过了什么?
我最后尝试使用route_id = ObjectProperty(None)
类 Route() 访问标签。我也无法让它工作,但它不会影响程序的运行或崩溃方式,所以我将属性留在代码中以防它有用。
复制问题:将代码复制到文件 main.py 和 ScreenManager.kv 中。启动程序,当主菜单打开时,单击路线按钮。单击选择组微调器,从下拉列表中选择一个组,然后从 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'