You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

多个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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.30 08:07:34