Kivy节点生成器报错:'FloatLayout'对象无add_node属性
问题:Kivy节点生成器应用报错AttributeError: 'FloatLayout' object has no attribute 'add_node'
错误信息
/USER/Documents/python/test/Nueva carpeta (2)/connection_nodes_app.py", line 167, in <module> ConnectionNodesApp().run() File "C:\Users\USER\anaconda3\envs\kivy\lib\site-packages\kivy\app.py", line 955, in run self._run_prepare() File "C:\Users\USER\anaconda3\envs\kivy\lib\site-packages\kivy\app.py", line 925, in _run_prepare root = self.build() File "c:/Users/USER/Documents/python/test/Nueva carpeta (2)/connection_nodes_app.py", line 158, in build network_canvas.add_node(text="Start Node") AttributeError: 'FloatLayout' object has no attribute 'add_node'
用户代码
# connection_nodes_app.py from kivy.app import App from kivy.uix.widget import Widget from kivy.uix.label import Label from kivy.uix.floatlayout import FloatLayout from kivy.uix.boxlayout import BoxLayout from kivy.graphics import Line, Color from kivy.uix.button import Button from kivy.uix.textinput import TextInput from kivy.uix.popup import Popup from kivy.lang import Builder Builder.load_string( """ <Node>: size_hint: None, None size: 100, 60 BoxLayout: orientation: 'horizontal' size: root.size canvas.before: Color: rgba: 1, 1, 1, 0.8 Rectangle: size: self.size pos: self.pos Label: id: label text: root.text size_hint_x: 0.7 Button: text: "Edit" size_hint_x: 0.3 on_release: root.edit_node() <Network>: BoxLayout: orientation: 'vertical' spacing: 10 padding: 10 Button: text: "Add Node" size_hint_y: None height: 50 on_release: app.add_node() ScrollView: NetworkCanvas: id: network_layout FloatLayout: size_hint: None, None size: 2000, 2000 """ ) class Node(Widget): def __init__(self, text="Node", **kwargs): super().__init__(**kwargs) self.text = text self.connections = [] self._dragging = False def on_touch_down(self, touch): if self.collide_point(*touch.pos): self._dragging = True touch.grab(self) return True return super().on_touch_down(touch) def on_touch_move(self, touch): if touch.grab_current is self and self._dragging: self.center_x += touch.dx self.center_y += touch.dy for conn in self.connections: conn.update() return True return super().on_touch_move(touch) def on_touch_up(self, touch): if touch.grab_current is self and self._dragging: self._dragging = False touch.ungrab(self) return True return super().on_touch_up(touch) def edit_node(self): popup = Popup(title="Edit Node") layout = BoxLayout(orientation="vertical", spacing=10, padding=10) text_input = TextInput(text=self.ids.label.text) save_button = Button(text="Save", size_hint_y=None, height=40) def save_text(instance): self.ids.label.text = text_input.text popup.dismiss() save_button.bind(on_release=save_text) layout.add_widget(text_input) layout.add_widget(save_button) popup.content = layout popup.size_hint = (0.5, 0.5) popup.open() class ConnectionLine: def __init__(self, start_node, end_node, canvas): self.start_node = start_node self.end_node = end_node self.canvas = canvas with self.canvas: Color(1, 0, 0, 1) self.line = Line( points=[*self.start_node.center, *self.end_node.center], width=2 ) self.start_node.connections.append(self) self.end_node.connections.append(self) def update(self): self.line.points = [*self.start_node.center, *self.end_node.center] class NetworkCanvas(FloatLayout): def __init__(self, **kwargs): super().__init__(**kwargs) self.selected_node = None self.nodes = [] def add_node(self, text="New Node", pos=(100, 100)): node = Node(text=text, pos=pos) self.nodes.append(node) self.add_widget(node) def connect_nodes(self, node1, node2): ConnectionLine(node1, node2, self.canvas) def on_touch_down(self, touch): if super().on_touch_down(touch) is False: if touch.button == "right": for node in self.nodes: if node.collide_point(*touch.pos): if not self.selected_node: self.selected_node = node else: self.connect_nodes(self.selected_node, node) self.selected_node = None return True self.selected_node = None return True class Network(FloatLayout): pass class ConnectionNodesApp(App): def build(self): layout = Network() network_canvas = layout.ids.network_layout.children[0] network_canvas.add_node(text="Start Node") return layout def add_node(self): network_canvas = self.root.ids.network_layout.children[0] network_canvas.add_node(text="New Node") if __name__ == "__main__": ConnectionNodesApp().run()
问题分析与修复方案
错误原因
你在build和add_node方法中,错误获取了network_layout的第一个子控件(即KV定义里那个size:2000,2000的FloatLayout),而非NetworkCanvas实例本身。FloatLayout没有你定义的add_node方法,因此抛出AttributeError。同时,KV中NetworkCanvas直接嵌套的FloatLayout会覆盖自身布局空间,导致节点无法正确显示在滚动区域内。
修复步骤
- 修正KV结构:给
NetworkCanvas内部的FloatLayout添加id,方便后续引用 - 调整节点添加逻辑:将节点添加到内部FloatLayout中,而非
NetworkCanvas自身 - 修正实例获取方式:直接使用
layout.ids.network_layout获取NetworkCanvas实例 - 调整连线绘制图层:使用内部FloatLayout的canvas绘制连线,确保显示正确
修复后的完整代码
# connection_nodes_app.py from kivy.app import App from kivy.uix.widget import Widget from kivy.uix.label import Label from kivy.uix.floatlayout import FloatLayout from kivy.uix.boxlayout import BoxLayout from kivy.graphics import Line, Color from kivy.uix.button import Button from kivy.uix.textinput import TextInput from kivy.uix.popup import Popup from kivy.lang import Builder Builder.load_string( """ <Node>: size_hint: None, None size: 100, 60 BoxLayout: orientation: 'horizontal' size: root.size canvas.before: Color: rgba: 1, 1, 1, 0.8 Rectangle: size: self.size pos: self.pos Label: id: label text: root.text size_hint_x: 0.7 Button: text: "Edit" size_hint_x: 0.3 on_release: root.edit_node() <Network>: BoxLayout: orientation: 'vertical' spacing: 10 padding: 10 Button: text: "Add Node" size_hint_y: None height: 50 on_release: app.add_node() ScrollView: NetworkCanvas: id: network_layout FloatLayout: id: canvas_container size_hint: None, None size: 2000, 2000 """ ) class Node(Widget): def __init__(self, text="Node", **kwargs): super().__init__(**kwargs) self.text = text self.connections = [] self._dragging = False def on_touch_down(self, touch): if self.collide_point(*touch.pos): self._dragging = True touch.grab(self) return True return super().on_touch_down(touch) def on_touch_move(self, touch): if touch.grab_current is self and self._dragging: self.center_x += touch.dx self.center_y += touch.dy for conn in self.connections: conn.update() return True return super().on_touch_move(touch) def on_touch_up(self, touch): if touch.grab_current is self and self._dragging: self._dragging = False touch.ungrab(self) return True return super().on_touch_up(touch) def edit_node(self): popup = Popup(title="Edit Node") layout = BoxLayout(orientation="vertical", spacing=10, padding=10) text_input = TextInput(text=self.ids.label.text) save_button = Button(text="Save", size_hint_y=None, height=40) def save_text(instance): self.ids.label.text = text_input.text popup.dismiss() save_button.bind(on_release=save_text) layout.add_widget(text_input) layout.add_widget(save_button) popup.content = layout popup.size_hint = (0.5, 0.5) popup.open() class ConnectionLine: def __init__(self, start_node, end_node, canvas): self.start_node = start_node self.end_node = end_node self.canvas = canvas with self.canvas: Color(1, 0, 0, 1) self.line = Line( points=[*self.start_node.center, *self.end_node.center], width=2 ) self.start_node.connections.append(self) self.end_node.connections.append(self) def update(self): self.line.points = [*self.start_node.center, *self.end_node.center] class NetworkCanvas(FloatLayout): def __init__(self, **kwargs): super().__init__(**kwargs) self.selected_node = None self.nodes = [] def add_node(self, text="New Node", pos=(100, 100)): node = Node(text=text, pos=pos) self.nodes.append(node) self.ids.canvas_container.add_widget(node) def connect_nodes(self, node1, node2): ConnectionLine(node1, node2, self.ids.canvas_container.canvas) def on_touch_down(self, touch): if super().on_touch_down(touch) is False: if touch.button == "right": for node in self.nodes: if node.collide_point(*touch.pos): if not self.selected_node: self.selected_node = node else: self.connect_nodes(self.selected_node, node) self.selected_node = None return True self.selected_node = None return True class Network(FloatLayout): pass class ConnectionNodesApp(App): def build(self): layout = Network() network_canvas = layout.ids.network_layout network_canvas.add_node(text="Start Node") return layout def add_node(self): network_canvas = self.root.ids.network_layout network_canvas.add_node(text="New Node") if __name__ == "__main__": ConnectionNodesApp().run()
内容的提问来源于stack exchange,提问作者Abnesis Nesis
相关产品推荐
相关产品推荐

