我想在up
上使用down
和Tree View
键盘界面。当我点击name
时,Tree View
弹出显示并up
,down
关键工作正常。但是在使用TextInput
和row
键选择行之后,我在up
中为过滤器down
键入了一些内容,然后它给出了错误{{ 1}}。如何AttributeError: 'NoneType' object has no attribute 'parent_node'
使用row
和up
键进行选择?
down
from kivy.uix.screenmanager import Screen
from kivy.app import App
from kivy.lang import Builder
from kivy.core.window import Window
from kivy.uix.popup import Popup
from kivy.uix.treeview import TreeView, TreeViewLabel, TreeViewNode
from kivy.uix.label import Label
from kivy.properties import ObjectProperty, ListProperty, StringProperty
Window.size = (500, 400)
def populate_tree_view(tree_view, parent, node):
if parent is None:
tree_node = tree_view.add_node(TreeViewLabel(text=node['node_id'],
is_open=True))
else:
tree_node = tree_view.add_node(TreeViewLabel(text=node['node_id'],
is_open=True), parent)
for child_node in node['children']:
populate_tree_view(tree_view, tree_node, child_node)
class TreeViewLabel(Label, TreeViewNode):
pass
class TreeViewGroup(Popup):
tree_view = ObjectProperty(None)
tv = ObjectProperty(None)
filter_text = StringProperty('')
tree = ListProperty([])
def __init__(self, **kwargs):
super(TreeViewGroup, self).__init__(**kwargs)
self.create_tree_view_root()
rows = ['test{}'.format(i) for i in range(1, 20)]
self.tree = [{'node_id': r, 'children': []} for r in rows]
self.tv.bind(minimum_height=self.tree_view.setter('height'))
self.create_tree_view_branch(self.tree)
def create_tree_view_root(self):
self.tv = TreeView(root_options=dict(text=""),
hide_root=False,
indent_level=4)
def create_tree_view_branch(self, obj):
for branch in obj:
populate_tree_view(self.tv, None, branch)
self.tree_view.add_widget(self.tv)
def on_open(self, *args):
self.filter_text = App.get_running_app().root.name.text
self._request_keyboard()
self.ti.focus = True
def dismiss_callback(self):
if self._keyboard is not None:
self._keyboard.release()
self.tree_view.clear_widgets()
self.dismiss()
App.get_running_app().root.name.focus = True
def _request_keyboard(self):
self._keyboard = Window.request_keyboard(self._keyboard_closed, self)
self._keyboard.bind(on_key_down=self._on_keyboard_down)
if self.tv.selected_node is None:
self.tv.select_node(self.tv.root.nodes[0])
def _keyboard_closed(self):
self._keyboard.unbind(on_key_down=self._on_keyboard_down)
self._keyboard.release()
self._keyboard = None
def _on_keyboard_down(self, keyboard, keycode, text, modifiers):
node = self.tv.selected_node
_, key = keycode
if key in ('down', 'up'):
parent = node.parent_node
ix = parent.nodes.index(node)
nx = ix+1 if key == 'down' else ix-1
next_node = parent.nodes[nx % len(parent.nodes)]
self.tv.select_node(next_node)
self.scroll.scroll_to(next_node)
elif key in ('enter', 'numpadenter'):
App.get_running_app().root.name.text = node.text
print(node.text)
self.dismiss_callback()
# Keycode is composed of an integer + a string
# If we hit escape, release the keyboard
if keycode[1] == 'escape':
keyboard.release()
# Return True to accept the key. Otherwise, it will be used by
# the system.
return True
def filter(self, value):
self.tree_view.clear_widgets()
self.create_tree_view_root()
filtered_tree = []
for node in self.tree:
if value.lower() in node['node_id'].lower():
filtered_tree.append(node)
self.create_tree_view_branch(filtered_tree)
self._request_keyboard()
self.ti.focus = True
class GroupScreen(Screen):
name = ObjectProperty(None)
popup = ObjectProperty(None)
def display_groups(self, instance):
if len(instance.text) > 0:
if self.popup is None:
self.popup = TreeViewGroup()
self.popup.open()
class Group(App):
def build(self):
self.root = Builder.load_file('test.kv')
return self.root
if __name__ == '__main__':
Group().run()
答案 0 :(得分:1)
问题是您的keyboard
正在关闭,因此当您点击TextInput
时,系统会忽略键盘事件。当您点击keyboard
Enter
Select
时,可以安排再次请求TextInput
,方法是将其限制为单行并添加on_text_validate:
项,在你的kv文件中这样:
TextInput:
id : ti
size_hint_y: .13
multiline: False
on_text_validate: root.validate()
on_text: root.filter(self.text)
在您的TreeViewGroup
课程中,将验证方法添加为:
def validate(self):
self.on_open()
这将调用您的on_open
方法并再次设置键盘事件。
答案 1 :(得分:1)
使用向上或向下箭头滚动,然后按ENTER选择特定行。 注意:替代方案,鼠标单击以选择特定行。
通过整个单词,例如 test ,将 on_focus 替换为 on_text_validate 。
有重复的代码,我已将它们转换为模块以便于维护。
def create_treeview_root(self):
...
def create_treeview_branch(self, obj):
...
def dismiss_callback(self):
GroupScreen:
...
SingleLineTextInput:
id: name
focus: True
multiline: False
on_text_validate: root.display_groups(self)
删除 __ init __ 中的绑定,因为 on_open 会自动绑定,并在弹出Popup时触发。 Popup Events » API
class TreeviewGroup(Popup):
...
def __init__(self,obj, **kwargs):
super(TreeviewGroup, self).__init__(**kwargs)
...
self.bind(on_open=self.on_open)
from kivy.uix.screenmanager import Screen
from kivy.app import App
from kivy.lang import Builder
from kivy.core.window import Window
from kivy.uix.popup import Popup
from kivy.uix.treeview import TreeView, TreeViewLabel, TreeViewNode
from kivy.uix.label import Label
from kivy.uix.textinput import TextInput
from kivy.properties import ObjectProperty, ListProperty, StringProperty, NumericProperty
Window.size = (500, 400)
def populate_tree_view(tree_view, parent, node):
if parent is None:
tree_node = tree_view.add_node(TreeViewLabel(text=node['node_id'],
is_open=True))
else:
tree_node = tree_view.add_node(TreeViewLabel(text=node['node_id'],
is_open=True), parent)
for child_node in node['children']:
populate_tree_view(tree_view, tree_node, child_node)
class CustomTextInput(TextInput):
def do_cursor_movement(self, action, control=False, alt=False):
if not self._lines:
return
if action in ('cursor_up', 'cursor_down'):
App.get_running_app().root.popup._request_keyboard()
return super(CustomTextInput, self).do_cursor_movement(action, control=control, alt=alt)
class TreeViewLabel(Label, TreeViewNode):
pass
class TreeViewGroup(Popup):
tree_view = ObjectProperty(None)
filter_text_input = ObjectProperty(None)
tv = ObjectProperty(None)
filter_text = StringProperty('')
tree = ListProperty([])
obj = ObjectProperty(None)
keycodes = {
# specials keys
'backspace': 8, 'tab': 9, 'enter': 13, 'rshift': 303, 'shift': 304,
'alt': 308, 'rctrl': 306, 'lctrl': 305,
'super': 309, 'alt-gr': 307, 'compose': 311, 'pipe': 310,
'capslock': 301, 'escape': 27, 'spacebar': 32, 'pageup': 280,
'pagedown': 281, 'end': 279, 'home': 278, 'left': 276, 'up':
273, 'right': 275, 'down': 274, 'insert': 277, 'delete': 127,
'numlock': 300, 'print': 144, 'screenlock': 145, 'pause': 19,
# F1-15
'f1': 282, 'f2': 283, 'f3': 284, 'f4': 285, 'f5': 286, 'f6': 287,
'f7': 288, 'f8': 289, 'f9': 290, 'f10': 291, 'f11': 292, 'f12': 293,
'f13': 294, 'f14': 295, 'f15': 296,
}
def __init__(self, **kwargs):
super(TreeViewGroup, self).__init__(**kwargs)
self._keyboard = None
self.create_tree_view_root()
rows = ['test{}'.format(i) for i in range(1, 20)]
self.tree = [{'node_id': r, 'children': []} for r in rows]
self.tv.bind(minimum_height=self.tree_view.setter('height'))
self.create_tree_view_branch(self.tree)
def create_tree_view_root(self):
self.tv = TreeView(root_options=dict(text=""),
hide_root=False,
indent_level=4)
def create_tree_view_branch(self, obj):
for branch in obj:
populate_tree_view(self.tv, None, branch)
self.tree_view.add_widget(self.tv)
def on_open(self, *args):
self.obj = self.filter_text_input
self.filter_text_input.focus = True
self.filter_text = App.get_running_app().root.name.text
def dismiss_callback(self):
if self._keyboard is not None:
self._keyboard.release()
self.tree_view.clear_widgets()
self.dismiss()
App.get_running_app().root.name.focus = True
def _request_keyboard(self):
self._keyboard = Window.request_keyboard(self._keyboard_closed, self)
self._keyboard.bind(on_key_down=self._on_keyboard_down)
if (self.tv.selected_node is None) \
and (len(self.tv.root.nodes) > 0):
self.tv.select_node(self.tv.root.nodes[0])
else:
self.filter_text_input.focus = True
def _keyboard_closed(self):
self._keyboard.unbind(on_key_down=self._on_keyboard_down)
self._keyboard.release()
self._keyboard = None
def _on_keyboard_down(self, keyboard, keycode, text, modifiers):
node = self.tv.selected_node
_, key = keycode
if key in ('down', 'up'):
parent = node.parent_node
ix = parent.nodes.index(node)
nx = ix+1 if key == 'down' else ix-1
next_node = parent.nodes[nx % len(parent.nodes)]
self.tv.select_node(next_node)
self.scroll.scroll_to(next_node)
elif key in ('enter', 'numpadenter'):
App.get_running_app().root.name.text = node.text
print(node.text)
self.dismiss_callback()
# Keycode is composed of an integer + a string
# If we hit escape, release the keyboard
if keycode[1] == 'escape':
keyboard.release()
if self.string_to_keycode(key) == -1:
self.filter_text += key
self.obj.focus = True
# Return True to accept the key. Otherwise, it will be used by
# the system.
return True
def string_to_keycode(self, value):
'''Convert a string to a keycode number according to the
:attr:`TreeViewGroup.keycodes`. If the value is not found in the
keycodes, it will return -1.
'''
return TreeViewGroup.keycodes.get(value, -1)
def filter(self, value):
self.tree_view.clear_widgets()
self.create_tree_view_root()
filtered_tree = []
for node in self.tree:
if value.lower() in node['node_id'].lower():
filtered_tree.append(node)
self.create_tree_view_branch(filtered_tree)
class GroupScreen(Screen):
name = ObjectProperty(None)
popup = ObjectProperty(None)
def display_groups(self, instance):
if len(instance.text) > 0:
if self.popup is None:
self.popup = TreeViewGroup()
self.popup.open()
class Group(App):
def build(self):
self.root = Builder.load_file('test.kv')
return self.root
if __name__ == '__main__':
Group().run()
#:kivy 1.10.0
<CustomTextInput>:
size_hint_y: .13
multiline: False
<TreeViewLabel>:
color_selected: [1, 0, 0, 1] if self.is_selected else [.1, .1, .1, 1] # red
on_touch_down:
app.root.name.text = self.text
app.root.popup.dismiss_callback()
<TreeviewGroup>:
tree_view: tree_view
filter_text_input: filter_text_input
title: "Select"
title_size: 17
size: 800, 800
auto_dismiss: False
scroll: scroll
BoxLayout
orientation: "vertical"
CustomTextInput:
id: filter_text_input
text: root.filter_text
on_text:
root.filter_text = self.text
root.filter(self.text)
ScrollView:
id: scroll
size_hint: 1, .9
BoxLayout:
size_hint_y: None
id: tree_view
GridLayout:
cols: 2
row_default_height: '20dp'
size_hint: .5, 0.1
pos_hint: {'x': .25, 'y': 1}
Button:
text: 'Ok'
on_release:
root.dismiss_callback()
Button:
text: 'Cancel'
on_release:
root.dismiss_callback()
<CustomLabel@Label>:
text_size: self.size
valign: "middle"
padding_x: 5
<SingleLineTextInput@TextInput>:
multiline: False
<GreenButton@Button>:
background_color: 1, 1, 1, 1
size_hint_y: None
height: self.parent.height * 0.150
GroupScreen:
name: name
GridLayout:
cols: 2
padding : 30,30
spacing: 10, 10
row_default_height: '40dp'
CustomLabel:
text: ' '
CustomLabel:
text: ' '
CustomLabel:
text: 'Name'
SingleLineTextInput:
id: name
focus: True
multiline: False
on_text: root.display_groups(self)
GreenButton:
text: 'Ok'
GreenButton:
text: 'Cancel'
on_press: app.stop()