Kivy TreeView上下键盘选择异常:筛选后触发AttributeError问题咨询
解决Kivy TreeView筛选后上下键盘选择报错的问题
我帮你分析下这个问题的根源:当你用TextInput筛选行之后,TreeView里的可见节点结构和原始的节点层级已经不一样了,但Kivy默认的TreeView键盘导航逻辑还是会基于原始的节点树去查找上/下节点,结果很可能找到已经被隐藏或者移除的节点(变成了None),这时候访问它的parent_node自然就会抛出AttributeError了。
下面是具体的解决方案,核心思路是自定义TreeView的键盘导航逻辑,只在可见节点中进行切换:
1. 自定义可筛选的TreeView类
我们需要重写TreeView的键盘事件处理,每次按键前先收集所有可见节点,再在这个列表里切换选中项:
from kivy.uix.treeview import TreeView, TreeViewNode from kivy.core.window import Window class FilterableTreeView(TreeView): def __init__(self, **kwargs): super().__init__(**kwargs) # 绑定键盘按下事件,拦截上下键逻辑 Window.bind(on_key_down=self.handle_key_navigation) # 存储当前可见的节点列表,用于导航 self.visible_nodes = [] def refresh_visible_nodes(self): """重新收集所有可见的节点(包括展开的子节点)""" self.visible_nodes.clear() def traverse_node(node): if node.is_visible: self.visible_nodes.append(node) # 如果节点是展开状态,遍历它的子节点 if node.is_open: for child in node.children: traverse_node(child) # 从根节点开始遍历所有节点 for root_node in self.root.nodes: traverse_node(root_node) def handle_key_navigation(self, window, key, scancode, codepoint, modifiers): # 处理上下箭头键(keycode:273是上,274是下) if key in (273, 274) and self.selected_node: self.refresh_visible_nodes() if not self.visible_nodes: return False # 找到当前选中节点在可见列表中的位置 current_pos = self.visible_nodes.index(self.selected_node) if key == 273: # 上键:切换到前一个节点,到顶则循环到底部 new_pos = current_pos - 1 if current_pos > 0 else len(self.visible_nodes) - 1 else: # 下键:切换到后一个节点,到底则循环到顶部 new_pos = current_pos + 1 if current_pos < len(self.visible_nodes)-1 else 0 # 设置新的选中节点 self.selected_node = self.visible_nodes[new_pos] return True # 拦截默认事件,避免触发原生错误逻辑 return False
2. 自定义支持可见性控制的TreeView节点
默认的TreeViewLabel没有is_visible属性,我们需要自定义节点类来控制显示/隐藏:
class FilterableTreeViewNode(TreeViewNode): def __init__(self, text='', **kwargs): super().__init__(**kwargs) self.text = text self.is_visible = True # 控制节点是否可见 self.is_open = False # 控制节点是否展开子节点
3. 实现筛选逻辑(修改你的TextInput筛选函数)
筛选时不要直接删除节点,而是通过设置is_visible属性来控制显示,同时保留原始节点结构:
def filter_tree_content(self, filter_text): """根据输入文本筛选TreeView节点""" def check_node_visibility(node): # 判断当前节点文本是否匹配筛选条件(忽略大小写) node_match = filter_text.lower() in node.text.lower() if filter_text else True # 检查子节点是否有匹配的 child_match = False if node.children: for child in node.children: child_match = check_node_visibility(child) or child_match # 节点可见的条件:自身匹配 或者 有子节点匹配 node.is_visible = node_match or child_match # 如果节点不可见,自动关闭展开状态 if not node.is_visible: node.is_open = False return node.is_visible # 从根节点开始遍历所有节点 for root_node in self.ids.tree_view.root.nodes: check_node_visibility(root_node) # 刷新可见节点列表,确保导航逻辑能拿到最新数据 self.ids.tree_view.refresh_visible_nodes() # 如果当前选中的节点不可见,自动选中第一个可见节点 if self.ids.tree_view.selected_node and not self.ids.tree_view.selected_node.is_visible: if self.ids.tree_view.visible_nodes: self.ids.tree_view.selected_node = self.ids.tree_view.visible_nodes[0]
4. 在KV文件中使用自定义组件
把原来的TreeView和TreeViewLabel替换成我们自定义的组件:
<FilterableTreeViewNode>: Label: text: root.text size_hint_y: None height: self.texture_size[1] + 4 visible: root.is_visible # 绑定节点的可见性 <YourScreenName>: FilterableTreeView: id: tree_view size_hint: 1, 0.9 root_options: {'text': 'Root'} TextInput: id: filter_input size_hint: 1, 0.1 hint_text: "输入内容筛选..." on_text: root.filter_tree_content(self.text)
关键注意点
- 避免直接增删节点:筛选时通过
is_visible控制显示,保留原始节点树结构,防止导航逻辑找不到节点。 - 每次按键前刷新可见节点:确保上下键只在当前显示的节点中切换,不会拿到隐藏的None节点。
- 拦截默认键盘事件:阻止Kivy原生的TreeView导航逻辑,因为它会遍历所有原始节点,包括已隐藏的,从而导致报错。
内容的提问来源于stack exchange,提问作者Nirdesh Kumawat
相关产品推荐
相关产品推荐

