多个Kivy DropDown组件异常Bug:部分组件点击无响应问题求助
问题原因分析
你的问题核心是DropDown对象被Python垃圾回收器意外回收了,导致部分按钮点击后没有响应。
看你的make_ddl方法:
def make_ddl(self, a_node: str, a_node_list) -> DropDown: def select(drop_button, text, btn): drop_button.text = text ddl = DropDown() ddl_main = Button(text=a_node, size_hint=(1, None), height=50) # ... 绑定事件和添加选项的代码 ... ddl_main.bind(on_press=ddl.open) return ddl_main
在这里,你创建了ddl这个DropDown对象,但它只是函数内部的局部变量——没有任何外部对象(比如返回的按钮、App实例)持有它的引用。当make_ddl函数执行完毕后,Python的垃圾回收器会把这个“无主”的DropDown对象销毁掉。这就导致对应的按钮点击后,找不到要打开的DropDown,自然没有反应。
而你观察到的“修改列表长度后,异常组件变化”的现象,本质是垃圾回收的时机不同:列表长度变化会改变内存使用情况,垃圾回收器选择回收的DropDown对象也跟着变了。
解决方案
解决方法很简单,只要给DropDown对象保留一个外部引用,不让它被垃圾回收就行。这里推荐两种简洁的实现方式:
方法1:给按钮添加属性持有DropDown引用
修改make_ddl方法,在返回按钮前,把DropDown对象绑定到按钮的一个自定义属性上:
def make_ddl(self, a_node: str, a_node_list) -> DropDown: def select(drop_button, text, btn): drop_button.text = text ddl = DropDown() ddl_main = Button(text=a_node, size_hint=(1, None), height=50) for node_type in a_node_list: btn = Button(text=node_type, size_hint=(1, None), height=50) btn.bind(on_release=partial(select, ddl_main, btn.text)) btn.bind(on_release=ddl.dismiss) ddl.add_widget(btn) ddl_main.bind(on_press=ddl.open) # 关键:给按钮添加属性,持有DropDown的引用 ddl_main.associated_ddl = ddl return ddl_main
这样按钮一直引用着DropDown对象,垃圾回收器就不会把它销毁了。
方法2:在App实例中保存所有DropDown
在DropDownListBug类的build方法里初始化一个列表,用来存储所有DropDown对象:
def build(self): self.main_screen = MainScreen() smgr = ScreenManager() smgr.add_widget(self.main_screen) # 新增:用来保存所有DropDown,防止被回收 self.all_dropdowns = [] self.make_my_lists() return smgr
然后在make_ddl方法里把创建的DropDown添加到这个列表:
def make_ddl(self, a_node: str, a_node_list) -> DropDown: def select(drop_button, text, btn): drop_button.text = text ddl = DropDown() # 新增:把DropDown加入App的列表 self.all_dropdowns.append(ddl) ddl_main = Button(text=a_node, size_hint=(1, None), height=50) # ... 其余代码不变 ...
修改后的完整代码(用方法1实现)
from typing import List, OrderedDict from functools import partial from kivy.app import App from kivy.lang import Builder from kivy.uix.label import Label from kivy.uix.button import Button from kivy.uix.dropdown import DropDown from kivy.uix.boxlayout import BoxLayout from kivy.uix.screenmanager import Screen, ScreenManager from kivy.uix.widget import Widget _category_list = ["Food", "Colors", "Dogs", "Cars", "Fruits"] _food_list = ["Chicken", "Fish"] _cars_list = [ "Acura", "Audi", "BMW", "Hyundai", "Mercedes", "Nissan", "Toyota", "Volvo", ] _colors_list = ["Red", "Green", "Blue", "Yellow"] _dogs_list = [ "Bulldog", "Chihuahua", "Dobermann", "Husky", "Labrador", "Pomeranian", "Poodle", "Retriever", "Westie", ] _fruits_list = ["Apple", "Orange", "Watermelon"] _node_to_stuff_map = { "Cars": _cars_list, "Colors": _colors_list, "Dogs": _dogs_list, "Food": _food_list, "Fruits": _fruits_list, } class MainScreen(Screen): def add_list_node(self, node: Widget): self.mylist.add_widget(node) class Manager(ScreenManager): pass Builder.load_string( """ <MainScreen>: mylist: id_mylist BoxLayout: orientation: "vertical" BoxLayout: orientation: "horizontal" size_hint: 1.0, 0.1 Label: text: "Category" Label: text: "Item List" Label: text: "Parameters" BoxLayout: id: id_mylist orientation: "vertical" size_hint: 1.0, 0.9 """ ) class DropDownListBug(App): def build(self): self.main_screen = MainScreen() smgr = ScreenManager() smgr.add_widget(self.main_screen) self.make_my_lists() return smgr def make_ddl(self, a_node: str, a_node_list) -> DropDown: def select(drop_button, text, btn): drop_button.text = text ddl = DropDown() ddl_main = Button(text=a_node, size_hint=(1, None), height=50) for node_type in a_node_list: btn = Button(text=node_type, size_hint=(1, None), height=50) btn.bind(on_release=partial(select, ddl_main, btn.text)) btn.bind(on_release=ddl.dismiss) ddl.add_widget(btn) ddl_main.bind(on_press=ddl.open) # 关键修改:给按钮绑定DropDown引用 ddl_main.associated_ddl = ddl return ddl_main def make_my_lists(self): my_list = [ "Cars.Hyundai", "Colors.Green", "Dogs.Westie", "Food.Chicken", "Fruits.Apple", ] for node in my_list: toks = node.split(".") node_type, node_item = toks[0], toks[1] node_panel = BoxLayout(orientation="horizontal") dll_category = self.make_ddl(node_type, _category_list) node_panel.add_widget(dll_category) item_list = _node_to_stuff_map[node_type] ddl_items = self.make_ddl(node_item, item_list) node_panel.add_widget(ddl_items) lbl = Label(text="[params]", size_hint=(1, None), height=50) node_panel.add_widget(lbl) self.main_screen.add_list_node(node_panel) if __name__ == "__main__": DropDownListBug().run()
这个修改后,所有DropDown组件都能正常响应点击了,不管你怎么修改选项列表的长度,都不会再出现无响应的情况。我之前在Kivy开发中也踩过这个垃圾回收的坑,当时也是折腾了好一阵才发现是对象引用的问题😂
内容的提问来源于stack exchange,提问作者Frankie Heng
相关产品推荐
相关产品推荐

